<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://inn.nightbugclub.cn/</id>
  <title type="text">Nightbug Inn</title>
  <subtitle type="text">萤火的微光点亮梦幻的夏夜</subtitle>
  <updated>2026-10-16T00:00:00.000Z</updated>
  <author><name>Eternity_Riguru</name></author>
  <link rel="alternate" href="https://inn.nightbugclub.cn/"/>
  <link rel="self" href="https://inn.nightbugclub.cn/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.9</generator>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/261016-wwi-5-1917-america-russian-revolution/</id>
      <title type="text">WWI-5 战争的转折：美国参战与俄国革命</title>
      <published>2026-10-16T00:00:00.000Z</published>
      <updated>2026-10-16T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/261016-wwi-5-1917-america-russian-revolution/"/>
      <summary type="text">1917 年，西线的炮火仍未打破僵局，战争的转折却先从海上与后方到来。一个工业强国的加入与一个帝国的崩塌，如何能改变交战双方的力量对比，并把战争逐渐推向终点？</summary>
      <content type="html"><![CDATA[
<p>1917 年初，欧洲战场依然看不到战争即将结束的迹象。</p>
<p>凡尔登和索姆河战役已经结束，英国的海上封锁仍在继续，俄国也没有因为此前的大撤退和惨重损失退出战争。各国政府仍在征兵、筹款，准备下一轮进攻。</p>
<p>但这一年，许多足以改变战局的变化，发生在战场之外。</p>
<p>柏林的决策者准备让潜艇放开手脚，切断英国的海上运输。他们知道，这样做可能把美国推入战争，却相信美国来不及发挥作用。</p>
<p>彼得格勒的工厂与街头，则酝酿着另一场危机：政府仍能让军队留在前线，却越来越难说服后方民众相信，这场战争值得继续打下去。</p>
<p>几个月内，俄国沙皇退位，美国也向德国宣战。到了年底，英军仍在比利时的泥地里争夺高地，俄国的新政府却已经开始与德国谈判停战。</p>
<p><strong>战场上的僵局尚未真正打破，支撑战争的国家力量、资源分配与政治秩序，却已经发生了根本变化。</strong></p>
<hr />
<section><h1>德国的海上豪赌：在美国赶到以前结束战争<a href="#德国的海上豪赌在美国赶到以前结束战争"><span>#</span></a></h1><p>上一年的日德兰海战，并未使德国摆脱英国的海上封锁。</p><p>德国主力舰队虽然得以保全，却无力打破英国对海上交通的控制。英国仍能够从海外输入粮食、原料与军需，德国则要在封锁、农业劳动力减少、运输紧张和收成不佳的共同压力下维持战争。1916—1917 年冬季的食物短缺，使继续消耗的代价更加明显。</p><p>兴登堡与鲁登道夫接掌德国最高军事领导权以后，开始在西线加强防御，减少兵力消耗。但防御本身无法让英国退出战争。德国海军因而再次提出一个方案：用潜艇打击商船，切断英国赖以维持战争的海上运输。</p><section><h2>用击沉吨位推算胜利<a href="#用击沉吨位推算胜利"><span>#</span></a></h2><p>德国早已清楚潜艇战可能带来的外交风险。</p><p>1915 年“卢西塔尼亚”号被击沉以后，美国持续抗议。1916 年“苏塞克斯”号遭袭又引发危机，德国随后作出限制潜艇攻击的承诺。如今若重新放开攻击，此前靠外交让步勉强维系的德美关系，很可能彻底破裂。</p><p>德国海军参谋长 <strong>亨宁·冯·霍尔岑多夫（Henning von Holtzendorff）</strong> 却认为，这个风险可以接受。</p><p>他在 1916 年 12 月的备忘录中，以每月击沉总吨位约六十万吨的商船为前提，推算英国的进口量将迅速下降；如果中立国船东也因害怕遭到袭击而停止向英国运货，英国便可能在数月内被迫求和。</p><p>英国确实不能长期失去海外粮食和原料供应。但这套计算低估了英国调整运输、压缩进口的能力，也将中立国船东会停止赴英当成了既定前提。美国即使来不及派出大军，仍可提供贷款、派出舰艇，并动用造船业支援协约国。这些应对措施，都不是单凭击沉吨位就能估算的。</p><p>德国首相贝特曼—霍尔韦格担忧美国参战，军方则更担忧继续拖延。他们既看到英法损失惨重，也看到德国自身越来越难以承受长期战争。</p><p>无限制潜艇战因此获得支持：决策者既相信潜艇的威力，也越来越不相信其他办法能够结束战争。</p><p>1917 年 2 月 1 日，德国恢复无限制潜艇战。在划定的战区内，潜艇可以不经传统的警告与搜查程序，攻击驶向敌方的商船，包括中立国船只。</p><p>几个月内，商船损失急剧增加。德国确实让英国面临严重的运输危机，却也使美国船只和人员不断面临遭到攻击的危险。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Bundesarchiv_Bild_102-00159,_U-Bootkrieg,_britisches_Schiff__Maplewood_--ff-13612d34ff34.8dxnw2uesu.webp" alt="1917 年 4 月 7 日，遭德国 U-35 潜艇攻击的英国货船“梅普尔伍德”号" /><figcaption>1917 年 4 月 7 日，遭德国 U-35 潜艇攻击的英国货船“梅普尔伍德”号</figcaption></figure><p></p></section><section><h2>美国越来越难以保持中立<a href="#美国越来越难以保持中立"><span>#</span></a></h2><p>美国总统 <strong>伍德罗·威尔逊（Woodrow Wilson）</strong> 并不急于把美国投入欧洲战场。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Woodrow_Wilson-H&amp;E--ff-c52d881ff1fb.73uqprcfgp.webp" alt="美国总统伍德罗·威尔逊，哈里斯与尤因摄影工作室肖像" /><figcaption>美国总统伍德罗·威尔逊，哈里斯与尤因摄影工作室肖像</figcaption></figure><p></p><p>1916 年竞选期间，民主党曾以美国仍置身战争之外作为政绩。美国国内对欧洲战争也有不同看法：有人同情协约国，有人反感英国的海上管制，有人因族群背景而对交战各方抱有不同立场，也有人出于和平主义信念或对扩军的担忧反对参战。美国社会尚未形成参战共识，威尔逊还需要争取公众支持。</p><p>不过，美国在法律上保持中立，并不意味着它在经济上也如此。英国控制海上运输，德美贸易受到封锁限制，美国对英法的供货和贷款则不断增加。协约国的采购牵动着美国企业的经营、银行的收益和民众的就业，也让美国的利益同欧洲战局越绑越紧。</p><p>这些经济关系解释了美国为什么更亲近协约国，却不足以单独解释宣战为何发生在 1917 年春天。</p><p>德国恢复攻击以后，美国船只能否继续安全航行、外交抗议能否保障美国权益，成了威尔逊必须重新权衡的问题。</p><p>2 月 3 日，美国与德国断交。威尔逊仍未放弃避免参战的希望，期待德国潜艇不再攻击美国船只。随后，他又尝试为商船配备武器，以保障中立国的航行权利。</p><p>就在这时，一封秘密外交电报，进入了美国公众的视野。</p></section><section><h2>齐默尔曼电报：把欧洲战争带到美国边境<a href="#齐默尔曼电报把欧洲战争带到美国边境"><span>#</span></a></h2><p>1917 年 1 月，德国外交国务秘书 <strong>阿图尔·齐默尔曼（Arthur Zimmermann）</strong> 发出密电：如果无法使美国保持中立，德国就准备向墨西哥提出结盟，共同作战、共同媾和，并支持墨西哥收复得克萨斯、新墨西哥和亚利桑那等失地。</p><p>这是一项以德美开战为前提的备选方案，德墨两国并未据此结成军事同盟。墨西哥刚经历长期革命与内战，缺少同美国大规模作战的条件；德国与墨西哥之间隔着英国控制的大西洋，也很难提供实际援助。电报还设想由墨西哥推动日本加入，而日本当时已经站在协约国一边，德国对其外交转向的期待同样缺乏现实基础。</p><p>但电报的政治杀伤力，并不取决于这套计划能否执行。</p><p>英国海军情报机构截获并破译了电报，在保护情报来源与推动美国参战之间权衡后，将内容转交美国。</p><p>3 月 1 日，美国报纸刊出消息。许多美国人原本认为，这只是远在欧洲的一场战争。如今得知德国打算借墨西哥牵制美国，他们开始感到，本国的安全也受到了威胁。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Zimmermann_Telegram--ff-aa38a60d643d.67y9ab2r0r.webp" alt="1917 年齐默尔曼电报的密码文本，美国国家档案馆藏" /><figcaption>1917 年齐默尔曼电报的密码文本，美国国家档案馆藏</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Zimmermann-telegramm-offen--ff-0ac0e355dc92.83au2xf6mk.webp" alt="齐默尔曼电报破译后的英文译文，美国国家档案馆藏" /><figcaption>齐默尔曼电报破译后的英文译文，美国国家档案馆藏</figcaption></figure></div><p>英国当然希望借此改变美国舆论，但电报并不是英国伪造的宣传材料。齐默尔曼随后公开承认其真实性，使“这只是英国的圈套”更难成为维持中立的理由。</p><p>威尔逊仍没有在消息公布当天要求宣战。</p><p>但此后美国商船接连被击沉，武装中立也难以保障航行安全。4 月 2 日，他向国会提出参战请求；4 月 6 日，美国正式向德国宣战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/President_Woodrow_Wilson_asking_Congress_to_declare_war_on_Germany,_2_April_1917--ff-da0fca3fc457.2a5vtmrqdc.webp" alt="1917 年 4 月 2 日，威尔逊向美国国会提出对德宣战请求" /><figcaption>1917 年 4 月 2 日，威尔逊向美国国会提出对德宣战请求</figcaption></figure><p></p><p>在演说中，威尔逊把参战同维护海上权利、反对专制统治和建立更稳定的国际秩序联系起来。俄国沙皇刚刚倒台，也使他更容易把协约国的战争描述为捍卫自由的事业。</p><p>不过，协约国仍包含庞大的殖民帝国，美国国内也存在种族隔离和政治权利不平等，这些宣战理由与现实之间仍有明显的矛盾。</p><p>电报使美国更难信任德国，潜艇战则不断造成船只和人员损失；两者共同使威尔逊放弃了武装中立的道路。</p></section><section><h2>宣战以后，大军还没有到来<a href="#宣战以后大军还没有到来"><span>#</span></a></h2><p>美国虽然工业发达、人口众多，却没有一支可以立即投入西线的百万大军。</p><p>征兵、训练、培养军官和扩充后勤机构，都需要时间。新兵需要运过大西洋，美军还要在法国建设港口、仓库、铁路等设施，建立训练体系。美国远征军总司令潘兴也希望逐步形成能够独立作战的美国军队，英法则更希望尽早得到可以补入现有战线的兵力，双方的需求并不完全一致。</p><p>1917 年 6 月，首批美国远征军作战部队抵达法国。到年底，在法美军约有十七万余人，其中还有大量后勤等支援人员。美军要到 1918 年才能大规模投入作战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Americas_First_Convoy_1917--ff-337a1b8716ee.2ocbki019c.webp" alt="1917 年 6 月，美国首批跨大西洋运兵船队中的运输船" /><figcaption>1917 年 6 月，美国首批跨大西洋运兵船队中的运输船</figcaption></figure><p></p><p>更早发挥作用的是政府信贷、物资和海军力量。协约国获得了新的融资来源，美国驱逐舰也开始参与反潜与护航。这些支援帮助英国撑过了海运危机。</p><p>从春末开始，英国逐步扩大远洋商船的编队护航。商船集中成队，由军舰在危险航段护送，使潜艇更难找到落单的目标，发动袭击也更为困难。组织船队需要解决航速、港口调度和护航舰艇分配的问题，但实践证明，这些困难可以克服。</p><p>再加上进口管制、船舶调配、增造船只和其他反潜措施，英国没有按德国预想的时间被迫求和。潜艇战仍在继续，却未能在美国充分动员之前迫使英国退出战争。</p><hr /></section></section>
<section><h1>俄国二月革命：维持战争的权威崩塌<a href="#俄国二月革命维持战争的权威崩塌"><span>#</span></a></h1><p>当美国还在努力维持中立时，俄国已经为这场战争付出了两年多的代价。</p><p>1915 年的大撤退使大片领土丧失，大量难民和被迫迁移的居民涌入内地。1916 年，俄国军工生产有所恢复，勃鲁西洛夫攻势也证明俄军仍能发动有力进攻。但弹药供应改善，并不能同时解决铁路运输、财政、粮食供应和政府威信等问题。</p><p>俄国的工业化发展不均，大量人口仍生活在农村。军队征走了大批劳动力和牲畜，铁路要同时满足前线、城市与工业的运输需求，物价上涨又使工人越来越难以靠工资维持生计。国家还在生产粮食，不代表城市居民就能按时买到面包。</p><section><h2>面包队伍背后的国家危机<a href="#面包队伍背后的国家危机"><span>#</span></a></h2><p>彼得格勒是帝国首都，也是重要工业城市。到 1916—1917 年冬季，运输受阻、燃料短缺、食品供应不稳和涨价相互叠加，排队买面包成了许多家庭日常生活的一部分。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Petrograd._Women_discussing_the_bread_allowance_LCCN2011647887_%28cropped%29--ff-a0afabd3d6e5.etb10farc.webp" alt="1917—1918 年间，彼得格勒街头讨论面包配给的妇女，詹姆斯·麦克斯韦·普林格尔摄" /><figcaption>1917—1918 年间，彼得格勒街头讨论面包配给的妇女，詹姆斯·麦克斯韦·普林格尔摄</figcaption></figure><p></p><p>妇女承受的压力尤其直接。丈夫可能已经入伍，她们既要挣钱，又要设法买到食物；排队耽误上班，工资又追不上物价。供应危机压在一个个家庭身上，也使人们逐渐怀疑政府解决问题的能力。</p><p>与此同时，政府在政治上也越来越孤立。</p><p>1905 年革命以后，俄国虽然设立了国家杜马，却没有形成由议会多数派组建政府的制度。沙皇仍掌握任免大臣和决定国家大事的权力。战争期间，部分杜马议员、社会组织和精英希望建立更能获得公众信任的政府，尼古拉二世却不愿接受会削弱皇权的安排。</p><p>1915 年，沙皇亲自接掌最高军事统帅权，也使战争结果与皇室威望更加紧密地绑在一起。宫廷干政、大臣频繁更换，以及围绕皇后和拉斯普京的传言，进一步损害了皇室威望。拉斯普京被杀，也没能挽回民众对政府的信任，因为这些危机早已不是除掉某个宫廷人物就能解决的。</p><p>当地方官员、议会精英、军队领导与街头民众都开始怀疑现有统治能否维持战争时，一次供应危机便有可能转变为政权危机。</p><div><div><div></div><div>Tip</div></div><div><p><strong>俄历与公历中的革命日期</strong></p><p>俄国当时使用儒略历，比英法等国使用的公历晚十三天。二月革命开始于俄历 2 月 23 日，即公历 3 月 8 日；十月革命的主要夺权行动发生于俄历 10 月 25 日前后，即公历 11 月 7 日前后。下文日期以公历为主，事件名称仍沿用“二月革命”“十月革命”等惯称。</p></div></div></section><section><h2>从罢工到驻军倒戈<a href="#从罢工到驻军倒戈"><span>#</span></a></h2><p>3 月 8 日，国际妇女节，彼得格勒的妇女和纺织女工走上街头，要求供应面包，并号召其他工厂的工人加入示威。</p><p>美国驻彼得格勒领事 <strong>诺思·温希普（North Winship）</strong> 在 3 月 20 日的报告中，记下了示威初期的一幕：3 月 9 日，妇女们来到领事馆对面的喀山大教堂附近，要求得到面包，并表示有了面包就回去工作。她们随后和平散去。次日，人群中的反政府口号已经更加明显。对这些妇女而言，走上街头首先是为了让家里有饭吃；随着罢工扩大，生计诉求逐渐同反战、反专制的要求结合起来。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/1917_International_Womens_Day_-_Petrograd--ff-1397961f636e.4g5afeje4v.webp" alt="1917 年 3 月 8 日，彼得格勒的妇女示威队伍" /><figcaption>1917 年 3 月 8 日，彼得格勒的妇女示威队伍</figcaption></figure><p></p><p>这场革命不是布尔什维克领导层按照既定计划发动的。列宁当时还在国外，各社会主义组织的基层活动者虽然参与运动，却没有哪一方能够预先控制人群、驻军与政府的全部反应。</p><p>最初，政府仍有可能通过镇压控制局势。政府下令恢复秩序，部队曾向示威人群开火，造成伤亡。但彼得格勒驻军中有大量后备和训练部队，士兵与工人、农民家庭有着直接联系，也担心自己被送上前线。他们愿不愿意继续服从开枪命令，成了政府能否控制首都的关键。</p><p>3 月 12 日，兵变大规模扩散。部分部队拒绝服从命令，士兵走出营房，与示威者一起夺取武器、攻击警察机构。政府已经无法维持街头秩序，就连执行命令所依靠的武装力量也不再听命。</p><p>杜马方面成立临时委员会，工人和士兵代表也开始组织彼得格勒苏维埃。与此同时，沙皇本人远离首都，难以及时掌握局势，更无法有效控制事态的发展。</p></section><section><h2>沙皇退位与帝制终结<a href="#沙皇退位与帝制终结"><span>#</span></a></h2><p>迫使尼古拉二世退位的，除了街头示威和驻军兵变，还有来自军队高层与杜马的压力。</p><p>军队高级将领与杜马领导人担心，首都的混乱如果继续扩大，铁路运输和军队补给都会受到威胁。他们中的许多人希望牺牲沙皇个人，保住国家、社会秩序以及继续对德作战的能力。对于这些人来说，退位一度被视为阻止革命扩大、恢复战争动员的办法。</p><p>3 月 15 日，尼古拉二世退位。次日，他的弟弟米哈伊尔大公没有直接接受皇位，而是表示，是否继位应由未来的制宪会议依据民意决定。罗曼诺夫王朝的统治随之结束。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/%D0%9E%D1%82%D1%80%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BE%D1%82_%D0%BF%D1%80%D0%B5%D1%81%D1%82%D0%BE%D0%BB%D0%B0_%D0%B8%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%B0_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D1%8F_II._2_%D0%BC%D0%B0%D1%80%D1%82%D0%B0_1917--ff-4c5c3e581879.3d5l4ink99.webp" alt="1917 年 3 月 15 日（俄历 3 月 2 日），尼古拉二世退位诏书的打字稿影像" /><figcaption>1917 年 3 月 15 日（俄历 3 月 2 日），尼古拉二世退位诏书的打字稿影像</figcaption></figure><p></p><p>首都驻军倒戈，政治精英与将领也不再保卫皇位，帝制失去了最后的支撑。</p><p>但不同参与者对革命的期待并不相同。将领希望军队恢复纪律，工人希望生活与权利得到改善，农民希望解决土地问题，士兵则希望不必再参加一场又一场看不到尽头的进攻。共同推倒沙皇之后，各方对新秩序的不同期待很快就会发生冲突。</p><hr /></section></section>
<section><h1>西线的新一轮消耗<a href="#西线的新一轮消耗"><span>#</span></a></h1><p>沙皇退位的消息传到英法时，协约国并未立即认为东线战场即将瓦解。</p><p>新成立的俄国临时政府仍承认对盟国的义务，许多人还希望，一个摆脱旧王朝的俄国能够更有效地作战。英国议会在 3 月讨论革命时，还寄望于俄国在获得自由的同时维持秩序，并通过革命增强作战能力。</p><p>不过，即使暂时不考虑俄国的不确定性，美国也无法在春天立刻接替英法的战斗任务。法国北部仍被德国占领，英法政府必须稳住前线，也要维系盟国之间的信心；德国则已经开始主动调整阵地，争取以更少兵力守住西线。</p><section><h2>尼韦尔攻势：法军对继续进攻丧失信心<a href="#尼韦尔攻势法军对继续进攻丧失信心"><span>#</span></a></h2><p>1917 年春，德军撤向兴登堡防线的部分阵地，缩短正面战线，并在撤退地区破坏道路、铁路和建筑。英法获得了土地，却也必须面对一套准备更充分的防御。</p><p>法国新任总司令 <strong>罗贝尔·尼韦尔（Robert Nivelle）</strong> 仍相信，集中炮兵、周密部署进攻，能够在埃纳河方向迅速撕开德军防线。</p><p>他在凡尔登反攻中取得的成绩，使这种信心具有一定基础。但要把局部战场上的成功变成足以改变整个西线战局的突破，就必须在更大范围内解决地形、情报与后勤带来的难题。</p><p>4 月，英军在阿拉斯进攻，加拿大军团夺取维米岭。随后，4 月 16 日，法军在埃纳河与“贵妇小径”高地一带发动攻势。德军已经有所准备，法军面对坚固阵地、恶劣天气与不利地形，未能获得承诺中的迅速突破。</p><p>对士兵而言，最令人难以承受的，不只是又一轮伤亡。他们原本被告知，这一次的牺牲会换来胜利，结果却仍是进攻受阻。</p><p>春末夏初，法军接连发生集体抗命和示威等事件，一些部队拒绝出发执行任务。许多士兵愿意保卫阵地，却拒绝再次参加他们认为难有胜算的进攻；也有人明确提出和平与更激进的政治诉求。</p><p>贝当接替尼韦尔后，一面整顿军纪、实施审判与惩处，一面改善伙食、落实休假和部队轮换，并缩小进攻目标。法军逐渐恢复秩序，仍能防守并实施准备充分的有限攻击，但短期内已经无法继续承担原来设想的突破任务。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Charles_Sensiers_Journal_during_the_Great_War_-_French_soldiers_in_captured_German_trenches,_Chemin_des_Da--ff-013aa69b512a.5trtjfug44.webp" alt="1917 年 7—9 月，贵妇小径一带已被法军占领的德军战壕，夏尔·桑西耶摄" /><figcaption>1917 年 7—9 月，贵妇小径一带已被法军占领的德军战壕，夏尔·桑西耶摄</figcaption></figure><p></p><p>英国因此承受了更大的压力。黑格需要考虑的，不只是自己能否打赢一场战役，还要考虑英国一旦停止大规模进攻，德国便可能腾出兵力，调往其他战场。</p></section><section><h2>帕斯尚尔战役：从进逼海岸到陷入泥地<a href="#帕斯尚尔战役从进逼海岸到陷入泥地"><span>#</span></a></h2><p>黑格把目光投向比利时佛兰德斯。</p><p>伊普尔突出部周围的高地，一直有利于德军观察和炮击。向东北推进，既可以改善英军阵地，也可能威胁德军交通，并为进一步逼近比利时海岸创造条件。比利时海岸港口也是德国潜艇的活动基地，这使陆上攻势又多了一层意义：减轻英国海运面临的威胁。</p><p>不过，夺取伊普尔附近高地、突破德军纵深、再抵达海岸，是难度逐级增加的任务。德国潜艇也不全依赖比利时基地。即使英军抵达海岸，也不能仅凭这一点结束潜艇战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/USMA_-_Third_Battle_of_Ypres--ff-fc04afafc585.9rk7045gso.webp" alt="1917 年第三次伊普尔战役的主要推进方向与战线变化，西点军校战后绘图" /><figcaption>1917 年第三次伊普尔战役的主要推进方向与战线变化，西点军校战后绘图</figcaption></figure><p></p><p>6 月 7 日，英军通过地下爆破与步炮协同夺取梅西讷岭，改善了伊普尔突出部南侧的态势。但这次准备充分、目标相对有限的胜利，并未就此打开通向海岸的道路。下一轮作战仍须重新集中兵力、部署炮兵、安排运输，还要应对德军增援。</p><p>7 月 31 日，第三次伊普尔战役开始。后世通常用 <strong>帕斯尚尔战役（Battle of Passchendaele）</strong> 指称这一轮夏秋攻势，名称来自后期争夺的村庄与高地。</p><p>炮火破坏了原有排水系统，降雨又让弹坑迅速积水。泥泞影响的不只是步兵行军：重炮难以向前移动，炮弹运不上去，火炮射击的平台也不稳定；观察与通信变差，前线便更难获得及时支援。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Chateau_Wood_Ypres_1917--ff-006644b4c817.362d931esi.webp" alt="1917 年 10 月 29 日，澳大利亚炮兵穿过伊普尔附近城堡林的木板路" /><figcaption>1917 年 10 月 29 日，澳大利亚炮兵穿过伊普尔附近城堡林的木板路</figcaption></figure><p></p><p>木板路虽然方便了通行，却也使人员和物资只能沿着狭窄通道移动。离开道路搬运弹药或后送伤员，可能需要比平时多得多的人力。部队占领一片阵地后，必须尽快恢复运输、组织防御，才能顶住德军反击。</p><p>战役并非从头到尾都采用同一种攻击方式。9 月，普卢默指挥的第二集团军在较好天气下，把进攻目标限定在较小范围内，依靠密集的炮火支援，并在得手后及时巩固阵地，取得了一系列进展。问题在于，这种战术成功容易被理解成敌军整体已经接近崩溃，进而推动下一次攻击。</p><p>到了 10 月，降雨和地面条件再度恶化。10 月 12 日，新西兰部队进攻时发现德军的铁丝网和混凝土火力点仍未被摧毁，己方炮兵支援又不足，结果遭受惨重损失。</p><p>奥塔哥团士兵 <strong>伦纳德·哈特（Leonard Hart）</strong> 一周后写信给父母，描述了自己所在连队的遭遇：接近山脊以后，他们发现铁丝网和混凝土机枪阵地仍在，少数缺口每次只容几个人通过，进攻者被火力困住。幸存者躲进弹坑，连队军官的伤亡又使他们难以得到新的命令。对步兵来说，任务远没有地图上画出一条推进线那么简单：有时连转移到下一个弹坑，都要冒生命危险。</p><p>加拿大军团随后接替任务，经过充分准备和分阶段推进，于 11 月 6 日夺取帕斯尚尔村，后续战斗持续到 11 月 10 日。</p><div><div><div></div><div>Note</div></div><div><p><strong>木板路与“鸭板”</strong></p><p>英语史料中的 duckboards，指铺在泥地、积水地面或战壕中的木质踏板。它们可以让人不必直接踩进泥浆，却无法像普通道路那样承载各种车辆与重炮。照片里一列人能够通过，并不代表整套补给系统已经恢复正常。</p></div></div><p>整场攻势推进有限，协约国没有抵达海岸，双方的总伤亡却达数十万人；具体估计因统计范围而有明显差异。</p><p>减轻法军压力、改善阵地和消耗德军，都是继续进攻的理由。但天气恶化、运输无法跟进以后，为原有目标继续投入兵力，已经越来越得不偿失。</p><p>英法希望坚持到美国的力量充分发挥作用，却在等待中继续损失兵员，消耗士兵与民众对政府的信任。</p><hr /></section></section>
<section><h1>从临时政府到十月革命：继续战争的选择耗尽了政府的信誉<a href="#从临时政府到十月革命继续战争的选择耗尽了政府的信誉"><span>#</span></a></h1><p>沙皇倒台以后，战争是否继续、又该由谁作出决定，成了新秩序中最棘手的问题。</p><p>最初的临时政府由 <strong>格奥尔基·李沃夫（Georgy Lvov）</strong> 领导，主要依靠自由派和杜马精英。它宣布保障公民自由、取消旧制度下的歧视性限制，并准备召开制宪会议，由选出的代表决定国家制度。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/MinistrosEnElFuneralDeLasV%C3%ADctimasDeLaRevoluci%C3%B3nKonovalovShingarevLvovnekrasovGodnevMiliukov--ff-e545fd8a957e.232ny75kxi.webp" alt="1917 年 4 月 5 日，李沃夫等临时政府成员参加二月革命死难者葬礼" /><figcaption>1917 年 4 月 5 日，李沃夫等临时政府成员参加二月革命死难者葬礼</figcaption></figure><p></p><p>这样的安排具有吸引力。对于担心革命失控的人来说，通过选举和制定宪法，似乎就能让街头运动逐渐平息，重新建立稳定的秩序。但在制宪会议召开以前，政府本身缺少全国选举的直接授权，旧行政与警察体系又已经受损。它要施政，必须获得其他革命组织的合作。</p><section><h2>政府与苏维埃之间的权力关系<a href="#政府与苏维埃之间的权力关系"><span>#</span></a></h2><p>彼得格勒苏维埃，由工人和士兵选出的代表组成，与工厂、驻军和铁路工人保持着联系。临时政府拥有正式政府的地位，苏维埃则能影响工人和士兵是否服从政府命令。</p><p>这种局面常被称为 <strong>“双重政权”</strong>，但两个机构并不是从第一天就各自宣布自己才是唯一政府。苏维埃早期的主要领导人愿意在一定条件下支持临时政府，希望通过监督、施压与合作保卫革命，同时避免内战和军事失败。</p><p>社会主义阵营内部也有分歧。孟什维克通常倾向于让俄国先经历民主政治的发展阶段，不愿立即由社会主义者独自执政；社会革命党在农民中拥有广泛影响，土地问题是其政治基础之一。两派中的温和领导人逐步参加联合政府，希望承担责任、维持团结，却也因此要与政府共同承担施政失败的责任。</p><div><div><div></div><div>Note</div></div><div><p><strong>“苏维埃”当时并不等于“布尔什维克”</strong></p><p>Soviet 是俄语中“委员会”或“代表会议”的音译，1905 年革命时已经出现这类组织。1917 年春，彼得格勒苏维埃主要由孟什维克和社会革命党等力量领导，布尔什维克尚未取得优势。苏联这个国家则到 1922 年才成立。</p></div></div><p>士兵委员会要求维护革命以后获得的权利，军官担心命令体系瓦解；工人要求提高工资与改善条件，企业则苦于燃料短缺、运输不畅和成本上涨；农民希望得到土地，政府又担心立即分地引发混乱、破坏生产和前线秩序，倾向于等待制宪会议作统一安排。</p><p>等待制宪会议有程序上的理由，乡村的土地纠纷、城市的物价上涨和前线的伤亡却不会因此暂停。问题拖得越久，临时政府就越难让人们相信，它将来能够兑现承诺。</p></section><section><h2>继续战争的临时政府<a href="#继续战争的临时政府"><span>#</span></a></h2><p>临时政府面对的外部条件，并没有随着沙皇退位而改变。</p><p>德国仍占领原俄国领土，英法仍是贷款和物资的重要来源。政府领导人担心，单独退出战争会让俄国陷入孤立，失去盟友支持，并被迫接受德国的苛刻条件。对部分自由派而言，俄国此前的战争目标与大国地位也仍值得维护。</p><p>温和社会主义者的理由又有所不同。许多人支持“革命护国主义”：可以反对侵略和吞并，推动各国达成和平，同时继续保卫已经摆脱沙皇的俄国。只要德国军队还在进攻，停止抵抗就可能危及革命本身。</p><p>两种立场暂时维持了合作，却没有结束战争的具体办法。盟国不愿马上和谈，德国也不接受没有领土收益的和平，继续防御很容易再次转向进攻。</p><p>春季，政府关于战争义务与目标的表态引发抗议，外交大臣米留科夫去职，联合政府重新组成。<strong>亚历山大·克伦斯基（Alexander Kerensky）</strong> 逐渐成为关键人物。他在革命中赢得了声望，也希望通过领导政府和军队，恢复国家的动员与作战能力。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Kerensky24971v--ff-a3e39da1e7af.7w7m7ht162.webp" alt="1917 年，克伦斯基向俄国部队讲话" /><figcaption>1917 年，克伦斯基向俄国部队讲话</figcaption></figure><p></p><p>他和其他支持进攻的人希望，一次军事成功能够恢复士气、证明新政府有能力统治，也向盟国表明俄国仍是可靠伙伴。在他们看来，军队若长期停在防线上被动等待，可能会更加涣散；发动进攻反而有机会重新整顿军队。</p><p>但对许多士兵而言，愿意防守家园，并不意味着愿意继续越过铁丝网。对他们来说，新一轮进攻首先意味着又一次有去无回的危险。</p></section><section><h2>列宁归国与《四月提纲》<a href="#列宁归国与四月提纲"><span>#</span></a></h2><p><strong>弗拉基米尔·列宁（Vladimir Lenin）</strong> 于 4 月回到俄国。在随后提出的《四月提纲》中，他反对以革命名义继续原有战争，主张不再支持临时政府，将政权交给苏维埃。他从瑞士经德国等地返回，德国允许过境，是希望俄国的反战力量削弱东线；允许过境并不意味着德国能够控制此后的俄国政治。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Lenin,_1916_%28Photo_of_V.Plier%29--ff-f775445c579c.icwyq8dgr.webp" alt="1916 年，流亡瑞士期间的列宁，威廉·普利尔摄" /><figcaption>1916 年，流亡瑞士期间的列宁，威廉·普利尔摄</figcaption></figure><p></p><p>这些主张最初并没有得到所有布尔什维克领导人的赞同。许多人仍认为同其他社会主义力量合作更现实，也担心立即谋求夺权会使党陷入孤立。列宁则坚持，参加联合政府只会让布尔什维克也为战争持续、物价上涨和土地问题迟迟未决承担责任。</p></section><section><h2>夏季攻势与七月危机<a href="#夏季攻势与七月危机"><span>#</span></a></h2><p>7 月 1 日，俄军在加利西亚发动新攻势。它在俄历中开始于 6 月，因此常被称为“六月攻势”，也称“克伦斯基攻势”。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/EasternFront1917--ff-69f24b7ed3e7.46h7v02lf.webp" alt="1917 年夏季俄军攻势与东线战局，西点军校战后绘图" /><figcaption>1917 年夏季俄军攻势与东线战局，西点军校战后绘图</figcaption></figure><p></p><p>进攻初期，一些部队取得了进展，但各部队的作战意愿和纪律状况差异很大。遭到德奥军队反击以后，俄军转为退却。这场原本用来恢复政府威信的进攻，反而使政府更加失信于人。</p><p>前线尚未全面溃退，首都的危机便已爆发。7 月中旬，部分驻军担心被调往前线，工人对生活状况不满，联合政府内部又存在分歧，再加上民众对继续战争的反感，最终引发了武装示威。喀琅施塔得水兵、工人和部分士兵要求苏维埃掌握政权。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/-Government_Troops_Firing_on_Demonstrators,_Corner_of_Nevsky_Prospect_and_Sadovaya_Street,_St._Petersburg,--ff-95cde576dfe1.99u5bj436w.webp" alt="1917 年七月危机期间，彼得格勒涅瓦大街与花园街交口遭到射击后的人群" /><figcaption>1917 年七月危机期间，彼得格勒涅瓦大街与花园街交口遭到射击后的人群</figcaption></figure><p></p><p>布尔什维克此时也没有一套能够控制局面的周密计划。党内领导层对是否发动起义犹豫不决，示威规模又超出了他们能够掌控的范围。政府最终恢复控制，查封布尔什维克报刊，逮捕活动者，列宁转入地下，后来逃往芬兰。</p><p>此时布尔什维克刚遭打击，温和社会主义者仍有广泛基础，政府也能调动部分武装力量。政府随后试图借助军方恢复秩序，却再次引发危机，改变了各派力量的处境。</p></section><section><h2>科尔尼洛夫事件：政府失去了两边的信任<a href="#科尔尼洛夫事件政府失去了两边的信任"><span>#</span></a></h2><p>克伦斯基在 7 月成为政府首脑。军队最高统帅 <strong>拉夫尔·科尔尼洛夫（Lavr Kornilov）</strong> 则主张恢复更严格的纪律，限制那些在他看来妨碍战争的政治活动与组织。</p><p>两人都认为秩序必须恢复，却对谁掌握权力、军队可以介入到什么程度有不同理解。经过中间人的传话和相互猜疑，原本包含合作意图的交涉演变成公开决裂。9 月，向彼得格勒推进的部队被克伦斯基视为军事夺权的威胁。</p><p>史学界对科尔尼洛夫最初意图、克伦斯基的责任以及误解如何形成仍有讨论。可以确认的是，政府为阻止部队推进，不得不依靠苏维埃、工人组织、铁路工人以及包括布尔什维克在内的左翼力量。</p><p>铁路工人阻碍部队运输，革命组织派人进入部队劝说士兵，最终使进军行动失败，科尔尼洛夫被捕。帮助阻止行动的革命组织得到新的活动空间，工人武装也获得发展。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Red_guard_unit_Petrograd_1917--ff-edb95fdc2d89.5c1ruut2ki.webp" alt="1917 年彼得格勒的一支赤卫队，维克托·布拉摄" /><figcaption>1917 年彼得格勒的一支赤卫队，维克托·布拉摄</figcaption></figure><p></p><p>危机过去以后，克伦斯基并未因此巩固自己的地位。保守派和部分军官认为他背叛了恢复秩序的努力，左翼则怀疑他此前同科尔尼洛夫的合作。能够暂时团结各方的政府首脑，开始变成各方都不愿完全信任的人。</p><p>布尔什维克却由此获得了新的政治机会：他们既坚持反战立场，又能以保卫革命、阻止军事反扑的姿态争取支持。</p></section><section><h2>布尔什维克的崛起<a href="#布尔什维克的崛起"><span>#</span></a></h2><p>到了秋天，临时政府仍未结束战争，生活条件继续恶化，乡村自行处置地主土地的行动不断扩大。政府对程序和秩序的坚持，在越来越多民众眼中成了拖延；“和平、土地、面包”以及“全部政权归苏维埃”等口号，则回应了不同群体最迫切的要求。</p><p>这些不满并非由布尔什维克制造，但与参加联合政府的党派相比，他们更容易借此号召人们反对政府。他们的支持者又集中在首都、重要工业城市、驻军和部分舰队中，能够直接影响政权的归属。9 月，布尔什维克在彼得格勒和莫斯科苏维埃中的地位明显上升。</p><p>这仍不意味着全国民众都支持他们执政。农民的政治倾向与首都工人并不相同，同样要求和平和土地的人，也可能支持社会革命党，或者只是希望建立广泛的社会主义联合政府。</p><p>列宁判断，首都局势提供了短暂的夺权机会，一旦拖延便可能错失时机。党内的季诺维也夫、加米涅夫等人则反对立即起义，担心失败与孤立。十月革命最终发生，既是社会危机不断加深的结果，也取决于布尔什维克领导层一次充满风险的政治决断。</p></section><section><h2>十月革命：首都的权力转移<a href="#十月革命首都的权力转移"><span>#</span></a></h2><p>托洛茨基担任彼得格勒苏维埃主席后，成为组织行动的重要人物。苏维埃军事革命委员会以保卫革命和首都为名，逐步争取到部分驻军的支持。这使夺权准备同士兵拒绝调离首都、反对政府干预的行动结合起来。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/The_entrance_to_the_Smolny_October_1917--ff-d2a9062695fc.6f1h5qowg4.webp" alt="1917 年秋，彼得格勒斯莫尔尼宫的入口，彼得·诺维茨基摄" /><figcaption>1917 年秋，彼得格勒斯莫尔尼宫的入口，彼得·诺维茨基摄</figcaption></figure><p></p><p>11 月 6 日至 7 日，军事革命委员会领导的部队与赤卫队控制桥梁、车站、通信等要点。克伦斯基政府能够可靠调动的兵力十分有限，许多驻军没有站出来保卫它。留在冬宫的临时政府部长们，于 7 日夜至 8 日凌晨被捕。</p><p>进攻冬宫后来成为这场革命最广为人知的一幕，但夺权的关键并不只在宫门前。政府已经失去对首都交通、通信和多数武装力量的控制，留在冬宫的政府成员因此陷入孤立。彼得格勒的夺权行动流血相对较少，却不代表此后全国各地的政权更替同样平静；莫斯科等地发生了更激烈的战斗。</p><p>第二次全俄苏维埃代表大会在夺权过程中召开。部分反对武装行动的孟什维克和社会革命党代表退场，大会通过了关于和平与土地的法令，并建立以列宁为首的人民委员会。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Milrevkom_proclamation--ff-30bef8198898.8vnpknvsc2.webp" alt="1917 年 11 月 7 日，军事革命委员会宣布临时政府被推翻的《告俄国公民书》" /><figcaption>1917 年 11 月 7 日，军事革命委员会宣布临时政府被推翻的《告俄国公民书》</figcaption></figure><p></p><p>新政权首先必须兑现的，是最能争取士兵支持、也最难单方面完成的承诺：结束战争。</p><p>法军同样经历过进攻失败与士兵抗命，却没有沿着俄国的道路走下去。贝当能够调整进攻目标、改善休假安排和给养，法国政府与军队指挥体系也仍保持运转。俄国的分歧则涉及整个权力秩序：临时政府、苏维埃和士兵委员会之间，连谁有权下达命令都难以达成共识；对许多农民出身的士兵来说，家乡的土地分配和家人的生计，也会影响他们继续服役的意愿。厌战情绪本身不足以决定军队是否瓦解。政府能否回应士兵的诉求、军队能否维持指挥权威，是两国走向不同结局的关键。</p>

<table><thead><tr><th>事件</th><th>公历时间</th><th>俄历时间</th></tr></thead><tbody><tr><td>妇女示威与二月革命开始</td><td>1917 年 3 月 8 日</td><td>2 月 23 日</td></tr><tr><td>尼古拉二世退位</td><td>3 月 15 日</td><td>3 月 2 日</td></tr><tr><td>俄军夏季攻势开始</td><td>7 月 1 日</td><td>6 月 18 日</td></tr><tr><td>七月危机主要示威与镇压</td><td>7 月 16—20 日</td><td>7 月 3—7 日</td></tr><tr><td>科尔尼洛夫事件</td><td>9 月</td><td>8 月末</td></tr><tr><td>彼得格勒主要夺权行动</td><td>11 月 6—8 日</td><td>10 月 24—26 日</td></tr></tbody></table><hr /></section></section>
<section><h1>康布雷战役：新的作战方法，仍然没有带来决定性胜利<a href="#康布雷战役新的作战方法仍然没有带来决定性胜利"><span>#</span></a></h1><p>俄国政权已经更替，西线军队却仍在寻找从战壕中打出去的办法。</p><p>1917 年 11 月 20 日，英军在法国北部康布雷附近发动进攻。这座城镇是德军重要的后方交通节点，周围又受到兴登堡防线保护。英军选择这里，是因为地面比佛兰德斯的泥地更适合坦克行动，而且尚未被长期炮击彻底破坏。</p><p>上一年在索姆河出现的坦克，此时已经有了新的型号和更成熟的使用经验。英军在康布雷集中了数百辆坦克，配合步兵、炮兵、航空兵和准备利用突破口的骑兵，试图让不同兵种在同一进攻中共同发挥作用。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/The_Battle_of_Cambrai,_November-december_1917_Q6284--ff-ab0e8c903f59.8dxnw2uern.webp" alt="1917 年 10 月，法国瓦伊附近的英军 Mark IV 坦克" /><figcaption>1917 年 10 月，法国瓦伊附近的英军 Mark IV 坦克</figcaption></figure><p></p><section><h2>步坦炮协同与突袭作战<a href="#步坦炮协同与突袭作战"><span>#</span></a></h2><p>此前的大规模进攻往往先进行长时间的炮击准备，既破坏地面，也给了守军预警。康布雷则更多依靠侦察、地图测量、火炮性能校准和气象资料计算射击数据，减少提前暴露进攻意图的试射，再突然开火，掩护部队进攻。</p><p>坦克负责压过或撕开铁丝网，压制部分火力点，帮助步兵接近阵地。为跨越德军宽壕，不少车辆携带大型柴捆，靠近后将其投入壕沟，给后续车辆创造通过条件。</p><div><div><div></div><div>Note</div></div><div><p><strong>填壕柴捆与坦克越壕</strong></p><p>这种装置称为 fascine，是由枝条捆扎成的大型柴束，沿用了工程兵填壕、铺路的传统办法。康布雷使用的柴捆不是增加装甲，也不是车辆自救用的“脱困木”；它主要用于填入壕沟，帮助坦克越过原本无法直接跨越的宽壕。</p></div></div><p>11 月 20 日，英军在若干地段迅速突破德军阵地，推进数公里，俘获大量守军。突然袭击、集中使用坦克和炮兵支援相互配合，使英军取得了此前多次长期炮击都未能换来的进展。</p><p>但首日成功以后，机械故障、战斗损伤，以及道路和跨河通道的限制，开始妨碍坦克继续推进。步兵需要清理残余据点、守住侧翼，炮兵与补给要重新前移，后备力量也不足以让进攻无限延伸。围绕布尔隆高地和树林的战斗，很快又变成艰难争夺。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/A_Mark_IV_%28Male%29_tank_of_H_Battalion,_Hyacinth,_ditched_in_a_German_trench_while_supporting_1st_Battalion,--ff-304ef8ca7660.9kgz4ojbdq.webp" alt="1917 年 11 月 20 日，康布雷战场上陷入德军壕沟的“风信子”号 Mark IV 坦克" /><figcaption>1917 年 11 月 20 日，康布雷战场上陷入德军壕沟的“风信子”号 Mark IV 坦克</figcaption></figure><p></p></section><section><h2>德军的反击与战术改进<a href="#德军的反击与战术改进"><span>#</span></a></h2><p>11 月 30 日，德军发动大规模反击，利用集中而较短促的炮火准备，以及强调小部队渗透、绕过部分坚固据点的攻击方式，在英军若干地段取得突破。</p><p>这些方法使德军能够寻找薄弱处推进，减少在每个坚固据点前停顿的时间。</p><p>英军最终放弃大部分初期所得，德军的反击也受到补给不畅、通信困难和后续兵力不足的限制，双方均未能把局部突破变成决定性胜利。</p><p>早期坦克的可靠性、速度与续航仍然有限，配套的机动后勤也尚未成熟。但康布雷战役表明，精确计算炮击参数，并让坦克与步兵协同作战，可以减少对漫长炮击准备的依赖。</p><hr /></section></section>
<section><h1>退出战争也要付出代价<a href="#退出战争也要付出代价"><span>#</span></a></h1><p>十月革命后的俄国政府希望结束战争，但宣布愿意议和，并不等于能让敌方接受自己提出的条件。</p><p>11 月 8 日通过的《和平法令》，向各交战国政府与人民呼吁达成不割地、不赔款的和平，并提出停战谈判。它既是一项外交倡议，也试图越过政府直接影响其他国家的士兵和工人。</p><p>列宁及其同志期待，俄国革命能够推动更广泛的欧洲革命。如果德国、奥匈等国的工人也迫使本国政府停止战争，俄国就可能不必单独面对军事强国提出的条件。</p><p>英法没有据此停止战争。同盟国愿意同俄国谈判，却首先希望结束东线作战、巩固既得优势。双方都愿意停火，并不代表对“和平”有相同理解。</p><section><h2>停战以后，领土问题才真正摆到桌面上<a href="#停战以后领土问题才真正摆到桌面上"><span>#</span></a></h2><p>1917 年 12 月 15 日，俄国与同盟国签订停战协定，随后在布列斯特—立托夫斯克展开和谈。对西线而言，俄国停止大规模作战的影响已经开始显现；对俄国而言，正式退出战争的条件却仍未谈定。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Bundesarchiv_Bild_183-R92623,_Brest-Litowsk,_Waffenstillstandsabkommen--ff-ff56aafe3cef.67y9ab2qz5.webp" alt="1917 年 12 月 15 日，俄国与同盟国代表在布列斯特签订停战协定" /><figcaption>1917 年 12 月 15 日，俄国与同盟国代表在布列斯特签订停战协定</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/At_the_Peace_Conference_in_Brest-Litovsk_%28arrival_of_the_Russian_delegation%29--ff-344f1918ace2.4n8iau5jl6.webp" alt="1918 年 1 月抵达布列斯特和会的俄国代表，中央侧身者为托洛茨基" /><figcaption>1918 年 1 月抵达布列斯特和会的俄国代表，中央侧身者为托洛茨基</figcaption></figure></div><p>德国希望保住对波兰、立陶宛与波罗的海部分地区的影响，并借民族自决等名义，为这些地区安排有利于德国的政治秩序。当地民族运动固然有自己的诉求，但在德军占领下，政治活动的空间有限，各方也无法自由选择未来的道路。</p><p>俄国代表则试图坚持不吞并的原则，并把谈判公开化，争取德国和其他国家的社会主义者支持。托洛茨基尤其希望拖延谈判，让欧洲革命与反战运动有更多时间发展。</p><p>布尔什维克内部也有激烈分歧。列宁认为，旧军队已经瓦解，新政权已无力继续进行有效抵抗，哪怕条件苛刻，也应接受和平以保住政权。主张“革命战争”的人则担心，签字等于承认帝国主义的强制条件，还会损害国际革命的前途。</p><p>分歧的关键，在于各方对新政权的军事能力、德国国内形势，以及欧洲革命何时可能爆发，有着不同判断。</p></section><section><h2>“不战不和”的破产<a href="#不战不和的破产"><span>#</span></a></h2><p>1918 年 2 月，托洛茨基宣布俄国退出战争、复员军队，却拒绝签署德国要求的和约，试图实行“不战不和”。他寄望于德国不愿为逼迫俄国签字而重新开战，或者德国工人会起来反对新的进攻。</p><p>德国选择了恢复进攻。2 月 18 日起，德军迅速向前推进，进一步暴露俄国军队的解体状态。革命后的政府能够发布命令，却已无法依靠瓦解中的旧军队阻止德军推进。</p><p>3 月 3 日，俄国政府签署 <strong>《布列斯特—立托夫斯克和约》</strong>，接受比此前更不利的条件。</p><p>俄国失去对波兰、立陶宛等西部地区的控制，还须撤出乌克兰、芬兰及其他指定地区，并在高加索领土问题上向奥斯曼帝国让步。这些地区的政治归属各不相同，德国对它们的控制方式也有差别，不能一概视为直接并入德意志帝国；但俄国确实失去了大片人口众多、农业和工业资源丰富的地区。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261016-WWI-5-1917-america-russian-revolution/Map_Treaty_of_Brest-Litovsk-en--ff-1a13fde1df82.46h7v02lp.webp" alt="1918 年布列斯特和约前后的东欧局势示意图，西点军校历史系制" /><figcaption>1918 年布列斯特和约前后的东欧局势示意图，西点军校历史系制</figcaption></figure><p></p><p>这幅战后绘制的地图也展示了罗马尼亚等地的战局，用来说明东欧整体变化。图上不同颜色与边界所表示的，是占领、脱离俄国及其他战争安排，并不都是同一种领土转让。</p><p>从军队瓦解到寻求和谈，从 12 月停战到次年 3 月签约，俄国退出战争经历了数月的过程。</p><p>和约使布尔什维克政府暂时摆脱了对德作战的压力，却没有给俄国带来普遍和平。国内权力冲突、民族独立运动和此后的内战仍在扩大。退出这场国际战争的过程，又同新一轮国家分裂与武装冲突交织在一起。</p><div><div><div></div><div>Note</div></div><div><p><strong>布列斯特和约并没有长期维持</strong></p><p>1918 年 11 月德国战败、帝国政权崩溃以后，苏俄宣布废除和约。但这没有让 1914 年的俄国疆界自动恢复。芬兰、波兰及波罗的海等地区此后的国家建立与边界争夺，还受到民族运动、内战和其他战争的影响。</p></div></div><hr /></section></section>
<section><h1>德国最后的进攻机会<a href="#德国最后的进攻机会"><span>#</span></a></h1><p>到 1917 年底，美国参战与俄国退出，使德国既看到了眼前的机会，也感到了长远的威胁。东线停战使德军可以向西调兵，美国持续动员却意味着这种兵力优势难以长久维持。英法在等待增援，德国则要赶在增援充分发挥作用以前，集中力量发动进攻。</p>

<table><thead><tr><th>战略变化</th><th>对德国的短期影响</th><th>仍然存在的限制</th></tr></thead><tbody><tr><td>俄国停止大规模作战</td><td>可逐步抽调部分东线部队</td><td>维持占领、保护铁路和应对地方冲突仍需驻军</td></tr><tr><td>美国参战</td><td>美军尚未大规模投入陆战</td><td>美国的信贷、海军支援和持续动员正增强协约国实力</td></tr><tr><td>英法经历新一轮消耗</td><td>短期恢复与补充更加困难</td><td>军队仍能守住战线，联盟没有瓦解</td></tr><tr><td>攻防战术继续改进</td><td>更有可能取得局部突破</td><td>补给不畅和后续兵力不足仍可能使攻势停顿</td></tr></tbody></table><p>东线部队也不能全部立即投入西线。占领区仍需驻军，铁路运输与部队整补需要时间，德国仍面临粮食和劳动力短缺，也仍需支援盟国。</p><p>继续等待，美国的力量会更强；立即进攻，德国又必须面对尚未解决的后勤困难。鲁登道夫最终把希望放在 1918 年春季，试图利用短暂的兵力优势，迫使英法在美军充分投入以前接受失败。英法能否顶住这轮进攻，将决定美国参战带来的长期优势能否真正转化为胜势。</p><p><strong>1917 年的转折没有直接结束战争，却让双方都必须在下一年争取决定性的结果。</strong></p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>美国海军历史与遗产司 — 霍尔岑多夫致兴登堡的备忘录，1916 年 12 月 22 日</strong><br />
一手文件，用于说明德国对击沉吨位、中立国航运反应与英国求和时间的预测；其中的预测作为当时判断使用。<br />
<a href="https://www.history.navy.mil/research/publications/documentary-histories/wwi/1916/chief-of-the-admiral.html" target="_blank">备忘录英译及编者说明</a></p>
</li>
<li>
<p><strong>美国国务院历史办公室 — U.S. Entry into World War I, 1917</strong><br />
支持苏塞克斯承诺、恢复无限制潜艇战，以及断交、武装商船与宣战之间的决策过程。<br />
<a href="https://history.state.gov/milestones/1914-1920/wwi" target="_blank">美国参战背景</a></p>
</li>
<li>
<p><strong>美国国家档案馆 — Zimmermann Telegram；The Zimmermann Telegram</strong><br />
用于核对电报内容、条件性结盟设想、英国破译与移交过程，以及公开后的政治影响。<br />
<a href="https://www.archives.gov/milestone-documents/zimmermann-telegram" target="_blank">电报与文字转录</a>、<a href="https://www.archives.gov/publications/prologue/2016/winter/zimmermann-telegram" target="_blank">档案研究文章</a></p>
</li>
<li>
<p><strong>美国国家档案馆 — Joint Address to Congress Leading to a Declaration of War Against Germany (1917)</strong><br />
支持美国中立时期的对协约国经济联系与参战背景，避免把贸易倾向直接等同于宣战决定。<br />
<a href="https://www.archives.gov/milestone-documents/address-to-congress-declaration-of-war-against-germany" target="_blank">宣战演说及历史说明</a></p>
</li>
<li>
<p><strong>伍德罗·威尔逊 / 加州大学圣巴巴拉分校 American Presidency Project — 1917 年 4 月 2 日国会演说</strong><br />
一手政治文本，用于分析总统公开提出的海上权利、动员措施与国际秩序目标，以及他对俄国革命的评价。<br />
<a href="https://www.presidency.ucsb.edu/documents/address-joint-session-congress-requesting-declaration-war-against-germany" target="_blank">演说全文</a></p>
</li>
<li>
<p><strong>美国海军历史与遗产司 — The First U.S. Overseas Troop Convoy</strong><br />
支持首批美国远征军抵法的时间、海军护航与运兵准备。<br />
<a href="https://www.history.navy.mil/content/history/nhhc/browse-by-topic/wars-conflicts-and-operations/world-war-i/history/first-troop-convoy.html" target="_blank">首批运兵船队</a></p>
</li>
<li>
<p><strong>美国陆军军事史中心 — United States Army in the World War, 1917–1919，第 12 卷</strong><br />
收录远征军统帅部及各部门报告，用于核对 1917 年底在法美军数量，并区分抵法人数与即时战斗能力。<br />
<a href="https://history.army.mil/Portals/143/Images/Publications/catalog/23-18.pdf" target="_blank">官方文件集 PDF</a></p>
</li>
<li>
<p><strong>美国国务院 — 1917 年战时财政往来文件</strong><br />
一手外交文件，说明美国政府信贷与此前商业贷款的区别，以及协约国融资如何转为政府间安排。<br />
<a href="https://history.state.gov/historicaldocuments/frus1917Supp02v01/d451" target="_blank">1917 年 5 月贷款往来</a></p>
</li>
<li>
<p><strong>帝国战争博物馆 — First World War Galleries: Large Print Guide</strong><br />
海战展陈部分说明 1917 年商船危机、护航制度与商船船员的损失。<br />
<a href="https://www.iwm.org.uk/sites/default/files/files/2023-10/first_world_war_large_print_guide.pdf" target="_blank">展陈说明 PDF</a></p>
</li>
<li>
<p><strong>Joshua A. Sanborn / 1914-1918 Online — Russian Empire</strong>
支持战争动员、社会压力、二月革命中的妇女与驻军作用，以及战争和国家危机的联系。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-russian_empire-2014-11-30.pdf" target="_blank">专业百科 PDF</a></p>
</li>
<li>
<p><strong>Iaroslav Golubinov / 1914-1918 Online — Food and Nutrition (Russian Empire)</strong><br />
用于理解粮食生产、征购、物价与城市供应之间的区别，以及后方短缺的形成。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-food_and_nutrition_russian_empire-2018-08-13.pdf" target="_blank">粮食与营养专题 PDF</a></p>
</li>
<li>
<p><strong>Christopher Read / 1914-1918 Online — Revolutions (Russian Empire)</strong><br />
提供从帝制崩溃到临时政府、社会运动与布尔什维克夺权的分析框架；其对精英政治与“双重政权”关系的解释，同具体事件分开使用。
<a href="https://encyclopedia.1914-1918-online.net/article/revolutions-russian-empire/" target="_blank">俄国革命专题</a></p>
</li>
<li>
<p><strong>英国议会 Hansard — Revolution in Russia，1917 年 3 月 19 日</strong><br />
一手议会记录，用于呈现英国政府当时对俄国革命和继续作战能力的期待。<br />
<a href="https://hansard.parliament.uk/Commons/1917-03-19/debates/1784e23d-a19e-4b5d-80ea-d49ea9db9710/RevolutionInRussia" target="_blank">议会记录</a></p>
</li>
<li>
<p><strong>弗拉基米尔·列宁 — The Tasks of the Proletariat in the Present Revolution（《四月提纲》）</strong><br />
一手党派政治文本，用于说明列宁对临时政府、战争与苏维埃的主张，不以其对其他党派的评价替代历史判断。<br />
<a href="https://www.marxists.org/archive/lenin/works/1917/apr/07.htm" target="_blank">英文译本</a></p>
</li>
<li>
<p><strong>Siobhan Peeling / 1914-1918 Online — July Crisis 1917 (Russian Empire)；Kornilov, Lavr Georgievich</strong><br />
分别支持七月示威与镇压、科尔尼洛夫事件中的政治关系和解释分歧。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/july-crisis-1917-russian-empire/" target="_blank">七月危机专题</a>、<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-kornilov_lavr_georgievich-2014-10-08.pdf" target="_blank">科尔尼洛夫专题 PDF</a></p>
</li>
<li>
<p><strong>美国驻俄使馆 / 美国国务院 — 1917 年科尔尼洛夫事件报告</strong><br />
一手外交观察，提供当时对中间人传话、误解与军政冲突的解释；报告本身也受到使馆信息范围的限制。<br />
<a href="https://history.state.gov/historicaldocuments/frus1918Russiav01/d157" target="_blank">外交报告</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Western Front；Between Acceptance and Refusal — Soldiers’ Attitudes Towards War</strong><br />
支持尼韦尔攻势与法军抗命的背景，并对照士兵诉求、政治表达和军纪恢复的不同层面。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-western_front-2015-11-11.pdf" target="_blank">西线专题 PDF</a>、<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-between_acceptance_and_refusal_-_soldiers_attitudes_towards_war-2014-10-08.pdf" target="_blank">士兵战争态度专题 PDF</a></p>
</li>
<li>
<p><strong>英国国家陆军博物馆 — 1917: Year of stalemate；Battle of Passchendaele</strong><br />
支持兴登堡防线、阿拉斯、梅西讷、佛兰德斯攻势和康布雷的总体过程，以及泥地对运输、炮兵和步兵的影响。<br />
<a href="https://www.nam.ac.uk/explore/1917-year-stalemate" target="_blank">1917 年战局</a>、<a href="https://www.nam.ac.uk/explore/battle-passchendaele" target="_blank">帕斯尚尔战役</a></p>
</li>
<li>
<p><strong>新西兰文化与遗产部 / NZ History — New Zealand’s ‘blackest day’ at Passchendaele；Battles of Broodseinde and Passchendaele map</strong><br />
用于核对 10 月攻势、炮兵支援不足与新西兰部队遭受重创的背景。<br />
<a href="https://nzhistory.govt.nz/new-zealands-blackest-day-at-passchendaele" target="_blank">10 月 12 日战斗</a>、<a href="https://nzhistory.govt.nz/media/photo/battles-broodseinde-and-passchendaele-map" target="_blank">战役阶段与地图说明</a></p>
</li>
<li>
<p><strong>加拿大退伍军人事务部 — Battle of Passchendaele；Passchendaele Canadian Memorial</strong><br />
支持加拿大军团接替进攻、11 月 6 日夺村及 11 月 10 日战役结束的日期区别。<br />
<a href="https://www.veterans.gc.ca/en/remembrance/military-history/first-world-war/battle-passchendaele?wbdisable=true" target="_blank">战役介绍</a>、<a href="https://veterans.gc.ca/en/remembrance/memorials/overseas/passchendaele-canadian-memorial" target="_blank">纪念资料</a></p>
</li>
<li>
<p><strong>英国坦克博物馆 — The Battle of Cambrai: The Fascine；康布雷地方历史解释中心 — La bataille de Cambrai</strong><br />
支持填壕柴捆的使用，以及康布雷将坦克同其他兵种配合的作战安排。<br />
<a href="https://tankmuseum.org/article/cambrai-fascine/" target="_blank">柴捆专题</a>、<a href="https://www.agglo-cambrai.fr/nos-structures/centre-dinterpretation-cambrai-tank-1917/la-bataille-de-cambrai-20-novembre-7-decembre-1917" target="_blank">康布雷战役介绍</a></p>
</li>
<li>
<p><strong>Siobhan Peeling / 1914-1918 Online — Decree on Peace</strong><br />
支持《和平法令》的内容、对外呼吁与革命外交背景。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-decree_on_peace-2014-12-19.pdf" target="_blank">和平法令专题 PDF</a></p>
</li>
<li>
<p><strong>Susanne Schattenberg / 1914-1918 Online — Brest-Litovsk, Treaty of</strong><br />
支持停战、和谈阶段、托洛茨基的“不战不和”、德国恢复进攻与最终签约。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-brest-litovsk_treaty_of-2014-11-30.pdf" target="_blank">布列斯特和约专题 PDF</a></p>
</li>
<li>
<p><strong>耶鲁大学 Avalon Project / 美国国务院外交文件 — The Conclusion of the Peace of Brest Litovsk</strong><br />
一手外交记录，用于对照俄国代表公开提出的谈判原则与协约国收到的和谈信息。<br />
<a href="https://avalon.law.yale.edu/20th_century/bl01.asp" target="_blank">1917 年 12 月谈判原则报告</a>、<a href="https://avalon.law.yale.edu/20th_century/bl03.asp" target="_blank">对协约国的谈判呼吁</a></p>
</li>
<li>
<p><strong>诺思·温希普 / 美国国务院外交文件 — The Consul at Petrograd to the Secretary of State，1917 年 3 月 20 日</strong><br />
一手领事报告，记录面包短缺、3 月 9 日喀山大教堂附近的妇女示威，以及此后口号和局势的变化。<br />
<a href="https://history.state.gov/historicaldocuments/frus1918Russiav01/d10" target="_blank">报告原文</a></p>
</li>
<li>
<p><strong>伦纳德·哈特 / Alexander Turnbull Library、NZ History — 1917 年 10 月 19 日家信</strong><br />
一手士兵书信，馆藏 MS-Papers-2157；用于呈现 10 月 12 日奥塔哥团进攻中铁丝网、火力与指挥损失的具体影响。<br />
<a href="https://nzhistory.govt.nz/sites/default/files/documents/passchendaele-letter-len-hart.pdf" target="_blank">书信全文 PDF</a>、<a href="https://nzhistory.govt.nz/media/video/leonard-hart-great-war-story" target="_blank">档案背景</a></p>
</li>
<li>
<p><strong>Aleksandr Borisovich Astashov / 1914-1918 Online — Between Acceptance and Refusal — Soldiers’ Attitudes Towards War (Russian Empire)，2021 年修订版</strong><br />
用于对照俄国士兵的家庭、乡村与军队经验，分析军队纪律危机及其同社会诉求的联系。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-between_acceptance_and_refusal_-_soldiers_attitudes_towards_war_russian_empire-2021-04-22-V1.1.pdf" target="_blank">俄国士兵专题 PDF</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>1917 年 4 月 7 日，遭德国 U-35 潜艇攻击的英国货船“梅普尔伍德”号</strong>：<a href="https://commons.wikimedia.org/wiki/File:Bundesarchiv_Bild_102-00159%2C_U-Bootkrieg%2C_britisches_Schiff_%22Maplewood%22.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详；德国联邦档案馆，Bild 102-00159，原影像与战时影片《Der magische Gürtel》有关。文件页注明原图注曾将沉船方式误写为舰炮，后更正为鱼雷；本文未作裁剪或改色。<a href="https://creativecommons.org/licenses/by-sa/3.0/de/deed.en" target="_blank">CC BY-SA 3.0 DE</a> 授权。</p>
</li>
<li>
<p><strong>美国总统伍德罗·威尔逊，哈里斯与尤因摄影工作室肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Woodrow_Wilson-H%26E.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Harris &amp; Ewing；美国国会图书馆 Harris &amp; Ewing Collection，LCCN 2016857917。馆藏标注无已知版权限制，Commons 标注为公有领域；具体拍摄年份在本图文件页未明确列出。</p>
</li>
<li>
<p><strong>1917 年齐默尔曼电报的密码文本，美国国家档案馆藏</strong>：<a href="https://commons.wikimedia.org/wiki/File:Zimmermann_Telegram.jpeg" target="_blank">Wikimedia Commons 原图页</a>。文书由德国外交部门发出；美国国家档案馆，Record Group 59，NAID 302025。使用该馆公开的密码电报影像，馆藏使用限制为 Unrestricted，发布版本标注无已知版权限制。</p>
</li>
<li>
<p><strong>齐默尔曼电报破译后的英文译文，美国国家档案馆藏</strong>：<a href="https://commons.wikimedia.org/wiki/File:Zimmermann-telegramm-offen.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国国家档案馆保存的破译英文译文影像，具体打字制作人员未署名；Commons 文件页标注为美国公有领域。</p>
</li>
<li>
<p><strong>1917 年 4 月 2 日，威尔逊向美国国会提出对德宣战请求</strong>：<a href="https://commons.wikimedia.org/wiki/File:President_Woodrow_Wilson_asking_Congress_to_declare_war_on_Germany%2C_2_April_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者不详；美国国会图书馆，LCCN 2002716887，数字编号 cph.3g10297。所用托管版本调整明暗、减轻网点并修补破损和污点；Commons 标注为美国公有领域，馆藏标注无已知出版限制。</p>
</li>
<li>
<p><strong>1917 年 6 月，美国首批跨大西洋运兵船队中的运输船</strong>：<a href="https://commons.wikimedia.org/wiki/File:America%27s_First_Convoy_1917.png" target="_blank">Wikimedia Commons 原图页</a>。原摄影者不详；刊于 Ralph Delahaye Paine《The Corsair in the War Zone》（1920），第 38 页对页插图，经 Internet Archive 扫描与 Commons 收录。图中为 Henderson、Antilles、Momus 和 Lenape 等运输船；托管版本转为灰阶，Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>1917—1918 年间，彼得格勒街头讨论面包配给的妇女，詹姆斯·麦克斯韦·普林格尔摄</strong>：<a href="https://commons.wikimedia.org/wiki/File:Petrograd._Women_discussing_the_bread_allowance_LCCN2011647887_(cropped).jpg" target="_blank">Wikimedia Commons 原图页</a>、<a href="https://www.loc.gov/pictures/item/2011647887/" target="_blank">美国国会图书馆馆藏</a>。摄影：James Maxwell Pringle；LCCN 2011647887，数字编号 ppmsca.31321。馆藏年代范围为 1917—1918 年，本文不将其认定为二月革命前的现场。采用 Verne Equinox 于 2019 年从馆藏相册扫描中裁切并校正倾斜的版本；Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1917 年 3 月 8 日，彼得格勒的妇女示威队伍</strong>：<a href="https://commons.wikimedia.org/wiki/File:1917_International_Women%27s_Day_-_Petrograd.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详；文件页列出的来源为俄罗斯政治历史博物馆（Museum of Political History of Russia），日期为 1917 年 3 月 8 日。Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1917 年 3 月 15 日（俄历 3 月 2 日），尼古拉二世退位诏书的打字稿影像</strong>：<a href="https://commons.wikimedia.org/wiki/File:%D0%9E%D1%82%D1%80%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BE%D1%82_%D0%BF%D1%80%D0%B5%D1%81%D1%82%D0%BE%D0%BB%D0%B0_%D0%B8%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D0%B0_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D1%8F_II._2_%D0%BC%D0%B0%D1%80%D1%82%D0%B0_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。文书：尼古拉二世退位诏书；Commons 文件页所列来源为俄罗斯档案署的国家历史专题展。文件日期为俄历 1917 年 3 月 2 日，对应公历 3 月 15 日；Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1917 年 7—9 月，贵妇小径一带已被法军占领的德军战壕，夏尔·桑西耶摄</strong>：<a href="https://commons.wikimedia.org/wiki/File:Charles_Sensier%27s_Journal_during_the_Great_War_-_French_soldiers_in_captured_German_trenches%2C_Chemin_des_Dames%2C_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Charles Sensier；来自其一战日记，经 Europeana 1914–1918 项目收录（贡献记录 10361）。所用版本由 Le Petit Chat 于 2024 年从日记扫描页中裁出，本文未另作裁剪或改色；拍摄时间为 1917 年 7—9 月。<a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>1917 年第三次伊普尔战役的主要推进方向与战线变化，西点军校战后绘图</strong>：<a href="https://commons.wikimedia.org/wiki/File:USMA_-_Third_Battle_of_Ypres.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国军事学院（西点军校）历史系制作，刊于《A Short Military History of World War I — Atlas》（1950）。此图为战后教学地图；美国联邦政府职务作品，Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>1917 年 10 月 29 日，澳大利亚炮兵穿过伊普尔附近城堡林的木板路</strong>：<a href="https://commons.wikimedia.org/wiki/File:Chateau_Wood_Ypres_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Frank Hurley；澳大利亚战争纪念馆，E01220；帝国战争博物馆亦收录为 E(AUS) 1220。Commons 标注为历史官方摄影的公有领域作品。</p>
</li>
<li>
<p><strong>1917 年 4 月 5 日，李沃夫等临时政府成员参加二月革命死难者葬礼</strong>：<a href="https://commons.wikimedia.org/wiki/File:MinistrosEnElFuneralDeLasV%C3%ADctimasDeLaRevoluci%C3%B3nKonovalovShingarevLvovnekrasovGodnevMiliukov.png" target="_blank">Wikimedia Commons 原图页</a>。原摄影者不详；文件页注明为 Ziva Galili y Garcia《The Menshevik Leaders in the Russian Revolution: Social Realities and Political Strategies》（Princeton University Press，1989，ISBN 069105567X）的插图扫描。图中包括李沃夫、米留科夫等临时政府成员；Commons 标注日期为 1917 年 4 月 5 日、版权状态为公有领域。</p>
</li>
<li>
<p><strong>1917 年，克伦斯基向俄国部队讲话</strong>：<a href="https://commons.wikimedia.org/wiki/File:Kerensky24971v.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者未署名；Bain News Service 历史照片，美国国会图书馆，馆藏页 ggb2006000385。所用 Commons 版本曾去除外框并替换为较高清扫描；馆藏标注无已知版权限制，Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1916 年，流亡瑞士期间的列宁，威廉·普利尔摄</strong>：<a href="https://commons.wikimedia.org/wiki/File:Lenin,_1916_(Photo_of_V.Plier).jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Wilhelm Plier；文件页注明拍摄于 1916 年 2—3 月间的苏黎世，原始馆藏编号未列出。Commons 标注为公有领域；此图为列宁归国前的肖像。</p>
</li>
<li>
<p><strong>1917 年夏季俄军攻势与东线战局，西点军校战后绘图</strong>：<a href="https://commons.wikimedia.org/wiki/File:EasternFront1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国军事学院（西点军校）军事艺术与工程系制作，原图来自该校一战地图集，第 41 图（WWOne41）。用于展示夏季攻势所在的东线区域及战线变化；美国联邦政府职务作品，Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1917 年七月危机期间，彼得格勒涅瓦大街与花园街交口遭到射击后的人群</strong>：<a href="https://commons.wikimedia.org/wiki/File:-Government_Troops_Firing_on_Demonstrators%2C_Corner_of_Nevsky_Prospect_and_Sadovaya_Street%2C_St._Petersburg%2C_Russia-_MET_DP237963.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国大都会艺术博物馆，藏品编号 2001.678，影像编号 DP237963；<a href="https://www.metmuseum.org/art/collection/search/284734" target="_blank">馆藏记录</a>将摄影归于 Karl Karlovich Bulla，标注日期为 1917 年 7 月 4 日。七月危机影像常沿用俄历日期，此处对应公历 7 月 17 日；为避免混淆，正文图注只标事件名称与年份。馆方开放获取影像，经 Commons 以 <a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank">CC0 1.0</a> 提供；本文未作裁剪或改色。</p>
</li>
<li>
<p><strong>1917 年彼得格勒的一支赤卫队，维克托·布拉摄</strong>：<a href="https://commons.wikimedia.org/wiki/File:Red_guard_unit_Petrograd_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Viktor Bulla；文件页经《Советская фотография 1917–1940 гг》影像资料收录，未明确单独馆藏编号。Commons 文件页标注为俄国公有领域。</p>
</li>
<li>
<p><strong>1917 年秋，彼得格勒斯莫尔尼宫的入口，彼得·诺维茨基摄</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_entrance_to_the_Smolny_October_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Pyotr Novitsky（1885—1942）；文件页所列来源为“俄罗斯照片史”项目（russiainphoto.ru）的摄影者资料。原图注标为 1917 年 10 月，未进一步说明历法，正文因此使用“1917 年秋”。Commons 标注为俄国及美国公有领域。</p>
</li>
<li>
<p><strong>1917 年 11 月 7 日，军事革命委员会宣布临时政府被推翻的《告俄国公民书》</strong>：<a href="https://commons.wikimedia.org/wiki/File:Milrevkom_proclamation.jpg" target="_blank">Wikimedia Commons 原图页</a>。彼得格勒苏维埃军事革命委员会发布，列宁起草；文件页所列原件来源为俄罗斯国家图书馆数字馆藏。文书署俄历 1917 年 10 月 25 日上午十时，即公历 11 月 7 日。此处展示的是宣布政权转移的文书，不是《和平法令》；Commons 标注为不受版权保护的官方文件。</p>
</li>
<li>
<p><strong>1917 年 10 月，法国瓦伊附近的英军 Mark IV 坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Battle_of_Cambrai%2C_November-december_1917_Q6284.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：John Warwick Brooke；帝国战争博物馆，Q 6284，Ministry of Information First World War Official Collection。文件归入康布雷战役影像，但明确标注拍摄时间为 1917 年 10 月 21 日、地点为 Wailly；本文按实际拍摄信息使用。Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1917 年 11 月 20 日，康布雷战场上陷入德军壕沟的“风信子”号 Mark IV 坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:A_Mark_IV_%28Male%29_tank_of_%27H%27_Battalion%2C_%27Hyacinth%27%2C_ditched_in_a_German_trench_while_supporting_1st_Battalion%2C_Leicestershire_Regiment_near_Ribecourt_during_the_Battle_of_Cambrai%2C_20_November_1917._Q6432.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：John Warwick Brooke；帝国战争博物馆，Q 6432，Ministry of Information First World War Official Collection。拍摄于 1917 年 11 月 20 日里贝库尔附近；Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1917 年 12 月 15 日，俄国与同盟国代表在布列斯特签订停战协定</strong>：<a href="https://commons.wikimedia.org/wiki/File:Bundesarchiv_Bild_183-R92623%2C_Brest-Litowsk%2C_Waffenstillstandsabkommen.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详；德国联邦档案馆，Bild 183-R92623。展示 1917 年 12 月停战协定的签署，正式和约另于 1918 年 3 月签订。采用 Commons 由 Quibik 于 2011 年裁切的馆藏影像版本，本文未另作裁剪或改色。署名：Bundesarchiv, Bild 183-R92623 / <a href="https://creativecommons.org/licenses/by-sa/3.0/de/deed.en" target="_blank">CC BY-SA 3.0 DE</a>。</p>
</li>
<li>
<p><strong>1918 年 1 月抵达布列斯特和会的俄国代表，中央侧身者为托洛茨基</strong>：<a href="https://commons.wikimedia.org/wiki/File:At_the_Peace_Conference_in_Brest-Litovsk_%28arrival_of_the_Russian_delegation%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者未明确署名；美国国家档案馆，NAID 17391270，文件页记录机构为美国陆军部。拍摄于 1918 年 1 月；Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>1918 年布列斯特和约前后的东欧局势示意图，西点军校历史系制</strong>：<a href="https://commons.wikimedia.org/wiki/File:Map_Treaty_of_Brest-Litovsk-en.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国军事学院（西点军校）历史系制作的战后教学地图，原系列由美国国防印刷机构印制。所用为文件页当前保留的英文地图版本，未作翻译或重绘；美国联邦政府职务作品，Commons 标注为美国公有领域。该图同时表现其他东欧战局，不能将全部色块视为直接并入德国的领土。</p>
</li>
</ol></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/tech/261015-picx-edgeone-image-migration/</id>
      <title type="text">使用 PicX 与 EdgeOne Pages 搭建图床实录</title>
      <published>2026-10-15T00:00:00.000Z</published>
      <updated>2026-10-15T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/tech/261015-picx-edgeone-image-migration/"/>
      <summary type="text">直连源站取图是很方便，但是太卡了！而现有的图床又有着或这或那的限制。于是我最终决定从自己搭建一个图床，并把现有的引用链接迁移过去。</summary>
      <content type="html"><![CDATA[<p>最近整理刚写完的系列文章时，我开始认真考虑图片的存放问题。</p>
<p>历史文章里有不少地图、人物肖像和战地照片。写作时直接引用原站图片很方便，但读文章时，有些图片加载缓慢，偶尔还会无法显示。随着图片越来越多，哪些图片属于哪篇文章、替换后旧图是否还需要保留，也逐渐变成了需要管理的事情。</p>
<p>于是，我决定把这批图片先备份到本地，再上传到自己的图床，通过统一的图片域名提供访问。这篇文章记录的，就是从搭建图床到完成图片引用替换的过程。</p>
<section><h1>最初考虑过的两条路<a href="#最初考虑过的两条路"><span>#</span></a></h1><section><h2>本地随站点构建，还是使用独立图床<a href="#本地随站点构建还是使用独立图床"><span>#</span></a></h2><p>最开始，我考虑过把图片全部下载下来，直接放进博客项目，和文章一起构建、发布。</p><p>这个办法很直观：文章和图片在同一个项目里，查找、备份也方便。这里说的“本地”，指的是把图片作为网站资源一同部署，读者最终访问的仍然是线上文件。</p><p>不过，历史文章里的图片还会继续增加，我也会不时补图、换图。我更希望这些图片有独立的管理目录和发布流程，让博客仓库主要保存文章、配置和组件。</p><p>而且我的源站带宽也不大，如果全部回源拉取图片，体验可能未必比直链好多少。</p>

<table><thead><tr><th>比较项</th><th>图片随博客构建</th><th>图片放在独立图床</th></tr></thead><tbody><tr><td>文件管理</td><td>文章和图片集中在同一个项目</td><td>博客与图片分开管理，需要维护对应关系</td></tr><tr><td>仓库体积</td><td>图片及其版本记录会增加博客仓库体积</td><td>图片体积转移到独立仓库，博客仓库更轻</td></tr><tr><td>构建与发布</td><td>图片作为站点产物一起处理、部署</td><td>图片项目单独部署；使用新链接时仍需更新并发布文章</td></tr><tr><td>图片处理</td><td>可接入 Astro 的图片优化流程</td><td>我在上传时通过 PicX 处理，再使用实际生成的链接</td></tr><tr><td>离线备份</td><td>一份完整项目可包含文章和图片</td><td>需要分别保留文章与图片副本</td></tr></tbody></table><p>本地图片的构建开销也取决于用法。以 Astro 为例，放在 <code>public/</code> 的文件直接复制到输出目录；放在 <code>src/</code> 并接入图片处理流程的资源，可以参与优化，处理结果也能利用缓存。不能把所有本地图片都理解成“每次构建都要重新压缩一遍”。具体行为见 <a href="https://docs.astro.build/en/guides/images/" target="_blank">Astro 图片指南</a>。</p></section><section><h2>GitHub Pages，还是 EdgeOne Pages<a href="#github-pages还是-edgeone-pages"><span>#</span></a></h2><p>决定使用独立图床后，另一个很自然的选择是 GitHub Pages：图片已经由 PicX 上传到 GitHub，PicX 又提供了一键部署入口，接起来很方便。我确实考虑过直接采用这条路线。</p><p>GitHub Pages 能从仓库发布静态资源，也支持<a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages" target="_blank">自定义域名</a>。两种方案的区别，主要在于由谁部署和提供访问：</p>

<table><thead><tr><th>比较项</th><th>GitHub Pages</th><th>EdgeOne Pages</th></tr></thead><tbody><tr><td>图片文件来源</td><td>使用 GitHub 图片仓库</td><td>同样可以使用这个 GitHub 图片仓库</td></tr><tr><td>接入方式</td><td>可沿用 PicX 的一键部署入口</td><td>授权导入仓库，再配置项目</td></tr><tr><td>更新发布</td><td>由 Pages 发布流程更新站点</td><td>仓库更新后由 EdgeOne 的部署流程发布</td></tr><tr><td>访问与区域</td><td>使用 GitHub Pages 的托管网络，效果需要实际测试</td><td>可按项目选择加速区域，再测试实际效果</td></tr><tr><td>图片域名</td><td>可以绑定自定义域名</td><td>可以绑定自定义域名，并关联生产环境</td></tr><tr><td>日常维护</td><td>主要在 GitHub 查看发布状态和相关设置</td><td>在 EdgeOne 控制台管理部署、域名与加速区域</td></tr></tbody></table><p>最终，我放弃了 GitHub Pages，选择把同一个图片仓库交给 EdgeOne 部署。我已经在使用腾讯云，网站也通过了备案，这样不仅管理方便，还可以选用中国大陆加速，提供相当不错的访问速度。<a href="https://pages.edgeone.ai/document/domain-overview" target="_blank">EdgeOne 的域名与区域说明</a>列出了不同区域及其配置要求。</p></section></section>
<section><h1>图片放在哪里，链接又从哪里来<a href="#图片放在哪里链接又从哪里来"><span>#</span></a></h1><p>最终采用的组合是 <strong>PicX + GitHub + EdgeOne Pages</strong>。</p><p>它们分别承担不同的工作：</p>

<table><thead><tr><th>组成</th><th>在这套方案中的作用</th></tr></thead><tbody><tr><td>PicX</td><td>上传、整理图片，并复制图片链接</td></tr><tr><td>GitHub 仓库</td><td>保存上传后的图片文件</td></tr><tr><td>EdgeOne Pages</td><td>部署仓库里的静态图片资源</td></tr><tr><td>自定义图片域名</td><td>为文章提供统一的访问地址</td></tr></tbody></table><p>访问关系可以画成下面这样：</p><div><div><div><div><span><p>PicX 上传</p></span></div><div><span><p>部署</p></span></div><div><span><p>请求图片域名</p></span></div><div><span><p>本地图片</p></span></div><div><span><p>GitHub 图片仓库</p></span></div><div><span><p>EdgeOne Pages</p></span></div><div><span><p>读者浏览文章</p></span></div>
</div><div><div><span><p>PicX 上传</p></span></div><div><span><p>部署</p></span></div><div><span><p>请求图片域名</p></span></div><div><span><p>本地图片</p></span></div><div><span><p>GitHub 图片仓库</p></span></div><div><span><p>EdgeOne Pages</p></span></div><div><span><p>读者浏览文章</p></span></div>
</div></div></div><p><a href="https://github.com/XPoet/picx#github-%E5%9B%BE%E5%BA%8A" target="_blank">PicX 的 GitHub 图床</a>支持目录管理、批量操作和图片链接复制。我需要的主要就是这部分功能。</p><p>图片有了独立域名后，博客文章和图片资源可以分别维护，以后调整托管方式时，也有机会通过域名保持访问入口稳定。</p><div><div><div></div><div>Note</div></div><div><p>下文沿用 EdgeOne Pages 这一称呼。目前部分<a href="https://pages.edgeone.ai/document/build-guide" target="_blank">官方文档</a>已使用 EdgeOne Makers，查看时可能会遇到不同名称。</p></div></div></section>
<section><h1>先传一张测试图片<a href="#先传一张测试图片"><span>#</span></a></h1><p>没错，就是这篇文章的封面图！</p><p>先用一张图片走完上传、部署、域名访问和文章引用的流程，确认每一步都能正常工作，再处理整批图片，会容易排查得多。</p><section><h2>准备账号和图片域名<a href="#准备账号和图片域名"><span>#</span></a></h2><p>开始之前，需要准备一个 GitHub 账号、一个腾讯云账号，以及一个可以管理 DNS 解析的域名。我给图片单独分配了子域名 <code>img.nightbugclub.cn</code>。</p><p>加速区域也需要提前想好。如果使用“中国大陆”或“全球可用区（含中国大陆）”，接入的自定义域名需要满足 ICP 备案要求；没有备案域名时，可以考虑“全球可用区（不含中国大陆）”。我已有备案，因此选择了包含中国大陆的区域，具体区别见 <a href="https://pages.edgeone.ai/document/domain-overview" target="_blank">EdgeOne 加速区域说明</a>。</p></section><section><h2>登录 PicX，上传封面<a href="#登录-picx上传封面"><span>#</span></a></h2><p>进入 <a href="https://picx.xpoet.cn/" target="_blank">PicX</a> 的 GitHub 图床功能。这里以 PicX v3 的 GitHub OAuth 登录流程为例：</p><ol>
<li>在登录页点击“安装 PicX GitHub APP”，跳转到 GitHub 完成安装。</li>
<li>返回 PicX，点击“GitHub OAuth 授权登录”，完成授权。</li>
<li>登录成功后，检查自动完成的图床配置。</li>
</ol><p>PicX v3 默认使用仓库 <code>picx-images-hosting</code> 和分支 <code>master</code>，配置时会自动创建所需仓库。已有其他版本的配置时，应以自己界面中的实际信息为准。登录与初始化过程可对照 <a href="https://picx-docs.xpoet.cn/usage-guide/get-start.html" target="_blank">PicX 快速开始</a>。</p><p>接下来，在“图床配置”中设置保存目录。我把这张封面放在 <code>posts/test</code>，可以通过新建目录输入这个路径；多级目录用 <code>/</code> 分隔。目录的几种设置方式见 <a href="https://picx-docs.xpoet.cn/usage-guide/config.html" target="_blank">PicX 图床配置</a>。</p><p>切换到“上传图片”，选择封面，检查上传目录和图片处理选项，再点击上传。测试阶段先传这一张就够了。</p><p>上传成功后，在“图床管理”中找到图片，再打开 GitHub 上的 <code>picx-images-hosting</code> 仓库，切换到 <code>master</code> 分支，进入 <code>posts/test</code>，确认文件已经出现。到这里，先记下三件事：<strong>仓库名称、分支名称、文件在仓库中的完整路径</strong>。</p><p>这篇文章封面上传后的实际文件名是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>f3c08ec596129347ef057118aeabf4a61334156f.jpg@672w_378h_1c_!web-search-common-cover.46h65a4rl.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>名字虽然很长，但后面不需要手动重写它。PicX 的哈希化、压缩设置可能改变最终名称和格式，访问时认准仓库中真正存在的文件即可。相关选项见 <a href="https://picx-docs.xpoet.cn/usage-guide/upload.html" target="_blank">PicX 上传说明</a>。</p><p>PicX 中还提供了“一键部署 GitHub Pages”的入口。看到它时不用急着点击：这次图片由 EdgeOne Pages 部署，上传到 GitHub 后，就可以接着配置 EdgeOne。</p></section><section><h2>在 EdgeOne 中导入图片仓库<a href="#在-edgeone-中导入图片仓库"><span>#</span></a></h2><p>登录腾讯云，进入 EdgeOne Pages 的项目列表，新建项目，选择通过 Git 仓库导入。首次使用时，需要关联 GitHub，并授权 EdgeOne 读取准备部署的仓库。</p><p>在仓库列表中选择 <code>picx-images-hosting</code>。如果列表中看不到它，先检查 GitHub 授权是否包含这个仓库，再刷新列表。连接与授权流程见 <a href="https://pages.edgeone.ai/document/importing-a-git-repository" target="_blank">EdgeOne 导入 Git 仓库指南</a>。</p><p>项目名称可以沿用 <code>picx-images-hosting</code>，生产分支选择 PicX 实际上传的 <code>master</code>，加速区域选择前面确定的区域。这里部署的是图片仓库，博客项目有自己的部署流程。</p></section><section><h2>展开构建设置，发布仓库里的静态文件<a href="#展开构建设置发布仓库里的静态文件"><span>#</span></a></h2><p>图片已经是可以直接访问的文件，这个项目不需要执行 Astro 构建。下面给出适用于纯图片仓库的一组配置方式：</p>

<table><thead><tr><th>配置项</th><th>填写内容</th><th>原因</th></tr></thead><tbody><tr><td>框架预设</td><td><code>Other</code></td><td>按普通静态资源处理</td></tr><tr><td>根目录</td><td>仓库根目录，使用默认的 <code>./</code></td><td>图片目录位于仓库中</td></tr><tr><td>安装命令</td><td><code>echo "Skip install"</code></td><td>只输出提示，跳过无关的依赖安装</td></tr><tr><td>构建命令</td><td><code>echo "Static images only"</code></td><td>只输出提示，无需编译图片</td></tr><tr><td>输出目录</td><td><code>./</code>，即仓库根目录；若界面使用目录选择器，则选择根目录</td><td>发布仓库中的图片目录</td></tr><tr><td>环境变量</td><td>无需添加</td><td>这套静态图片访问流程不需要额外变量</td></tr></tbody></table><p>两条 <code>echo</code> 命令只是明确地让对应步骤输出一行提示。如果界面提供直接跳过安装或构建的选项，也可以使用该选项。平台支持 Bash 构建命令，根目录与输出目录的含义见 <a href="https://pages.edgeone.ai/document/build-guide" target="_blank">EdgeOne 构建指南</a>。</p><p>输出目录最容易填错。假如仓库的结构是这样：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>picx-images-hosting/</span></div></div><div><div><div>2</div></div><div><span>└── posts/</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>├── test/</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>│   └── 封面图片.webp</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>└── history/</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>└── 某篇文章/</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>└── 图片.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>选择仓库根目录作为输出目录，发布后的图片路径就保留 <code>posts/test/</code> 和 <code>posts/history/</code>。如果只输出 <code>posts/test</code>，发布范围和访问路径都会随之改变；如果填写 <code>dist</code>，仓库中又没有生成这个目录，就无法按预期发布图片。</p><p>检查完配置，点击“开始部署”。在部署详情中确认状态成功、分支为 <code>master</code>，并且所用提交已经包含测试图片。若部署失败，先查看日志中失败的步骤，再检查对应配置。</p><p><strong>PicX 上传成功，只表示文件已经进入 GitHub；EdgeOne 部署成功，才表示这一版文件已经发布。</strong> 后续每次上传新图，都要留意这两个阶段。</p></section><section><h2>绑定图片域名，配置 DNS 和 HTTPS<a href="#绑定图片域名配置-dns-和-https"><span>#</span></a></h2><p>部署完成后，进入这个项目的“域名管理”，添加自己的图片子域名。我这里填写 <code>img.nightbugclub.cn</code>，并将它关联到<strong>生产环境</strong>。</p><p>随后按照控制台引导完成域名归属验证。若要求添加验证记录，就到域名当前使用的 DNS 服务商处添加对应记录，再回到 EdgeOne 验证。域名归属验证和图片访问所需的解析各有用途，分别照控制台给出的内容操作。</p><p>访问解析使用 EdgeOne 提供的 CNAME 目标。以在 <code>nightbugclub.cn</code> 的 DNS 区域内添加图片子域名为例：</p>

<table><thead><tr><th>DNS 配置项</th><th>填写内容</th></tr></thead><tbody><tr><td>主机记录</td><td><code>img</code></td></tr><tr><td>记录类型</td><td><code>CNAME</code></td></tr><tr><td>记录值</td><td>EdgeOne 为这个自定义域名提供的 CNAME 目标，原样复制</td></tr><tr><td>线路与 TTL</td><td>首次配置可使用 DNS 控制台默认值</td></tr></tbody></table><p>记录值填写目标域名，不添加 <code>https://</code> 或图片目录。同一个 <code>img</code> 主机名若已有冲突的解析记录，需要先确认旧用途，再调整。完成后等待 DNS 生效，返回 EdgeOne 检查解析状态。添加域名、归属验证和环境关联可对照 <a href="https://pages.edgeone.ai/document/custom-domain" target="_blank">EdgeOne 自定义域名说明</a>。</p><p>接着打开该域名的 HTTPS 配置。没有现成证书时，可以使用平台的免费证书申请功能；已有证书时，也可以按支持的方式部署托管证书。确认申请和部署完成后，再使用 <code>https://</code> 测试图片，具体操作见 <a href="https://pages.edgeone.ai/zh/document/configuring-an-https-certificate" target="_blank">EdgeOne HTTPS 配置说明</a>。</p><p>到这里，应该分别确认三件事：DNS 已指向平台、自定义域名关联生产环境、HTTPS 证书已经生效。后续生产分支的新版本部署成功后，这个图片域名就可以继续作为固定入口。</p></section><section><h2>找到真实文件路径，打开封面链接<a href="#找到真实文件路径打开封面链接"><span>#</span></a></h2><p>图片地址由两部分组成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>图片域名 + 文件相对于部署输出目录的路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>回到 GitHub，在 <code>master</code> 分支找到封面文件。复制文件路径时，取的是仓库内从 <code>posts/</code> 开始的路径；GitHub 文件页面地址中的账号、仓库名和 <code>blob/master</code> 都不属于部署后的图片路径。</p><p>由于这里把仓库根目录作为输出目录，封面的完整路径为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>posts/test/f3c08ec596129347ef057118aeabf4a61334156f.jpg@672w_378h_1c_!web-search-common-cover.46h65a4rl.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在前面加上图片域名，就得到这篇文章实际使用的封面地址：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>https://img.nightbugclub.cn/posts/test/f3c08ec596129347ef057118aeabf4a61334156f.jpg@672w_378h_1c_!web-search-common-cover.46h65a4rl.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把完整地址粘贴到浏览器新标签页，能够直接显示封面，就说明这条访问路径已经跑通。仓库名 <code>picx-images-hosting</code> 不需要再加到域名后面。</p><p>测试时打开的是<strong>具体图片地址</strong>。纯图片仓库没有准备首页时，直接访问 <code>https://img.nightbugclub.cn/</code> 可能返回 404，这本身不能说明图片部署失败。</p><p>如果具体图片仍然 404，先核对文件名和目录是否一致，再看最新生产部署是否已经包含该文件，以及输出目录是否改变了路径。后文那八张含单引号的图片，是在这些基础检查都完成后才进一步定位到的问题。</p></section><section><h2>回到 PicX，让复制的链接使用自己的域名<a href="#回到-picx让复制的链接使用自己的域名"><span>#</span></a></h2><p>图片可以访问后，还需要补上日常使用的一步：让 PicX 直接生成自己的域名链接。</p><p>在 PicX 的“我的设置”中找到“图片链接规则配置”，展开“配置自定义图片链接规则”，添加一条规则。例如，类型填写 <code>EdgeOne</code>，规则填写：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>https://img.nightbugclub.cn/{{path}}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>{{path}}</code> 是 PicX 的文件路径占位符，保留双层大括号，PicX 会把它替换成包含目录和文件名的实际路径。读者配置时，把域名换成自己的即可。规则入口和占位符示例见 <a href="https://picx-docs.xpoet.cn/usage-guide/settings.html#%E9%85%8D%E7%BD%AE%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%BE%E7%89%87%E9%93%BE%E6%8E%A5%E8%A7%84%E5%88%99" target="_blank">PicX 自定义图片链接规则</a>。</p><p>添加以后，还要在“选择图片链接规则”的下拉框中选中刚才的 <code>EdgeOne</code>。回到图床管理，复制封面的链接，检查它是否与前面手动拼出的地址一致。</p><p>链接格式可以根据用途选择：平时写 Markdown 正文，可以开启自动转换为 Markdown；需要填写文章封面字段，或者导出链接清单用于迁移时，则使用纯 URL。</p><p>例如，这张图放进 Markdown 正文时写成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>![</span><span>图床搭建测试封面</span><span>]</span><span><span>(</span><span>https://img.nightbugclub.cn/posts/test/f3c08ec596129347ef057118aeabf4a61334156f.jpg@672w_378h_1c_!web-search-common-cover.46h65a4rl.webp</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改 PicX 链接规则影响的是后续复制出来的地址，已经写进文章的旧链接仍需要另外替换。</p></section><section><h2>再上传一张图，确认后续能自动更新<a href="#再上传一张图确认后续能自动更新"><span>#</span></a></h2><p>最后，可以换一张不同文件名的图片，再走一遍日常操作：在 PicX 上传，确认 GitHub 出现新提交，然后到 EdgeOne 查看是否自动触发了新的生产部署。</p><p>Git 集成的自动部署开启后，生产分支的提交会触发更新；若没有新部署记录，检查仓库、生产分支与自动部署设置，也可以从控制台手动重新部署。更新机制见 <a href="https://pages.edgeone.ai/document/importing-a-git-repository" target="_blank">EdgeOne Git 部署指南</a>。</p><p>等部署成功，再复制自己的域名链接，打开验证，最后写入文章并发布博客。首次配置完成后，后续就沿着这条路线操作：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>PicX 上传 → GitHub 仓库更新 → EdgeOne 部署成功 → 验证图片链接 → 在文章中使用</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我也是确认单张图片能够正常访问以后，才开始处理整批文章。</p></section></section>
<section><h1>把文章中的图片搬过去<a href="#把文章中的图片搬过去"><span>#</span></a></h1><p>这批图片按文章整理，本地目录与文章目录保持对应：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>文章：history/WWI/某篇文章.md</span></div></div><div><div><div>2</div></div><div><span>原图：images/history/WWI/某篇文章/图片文件.jpg</span></div></div><div><div><div>3</div></div><div><span>图床：posts/history/WWI/某篇文章/上传后的图片.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样，在 PicX 里查找图片、给某篇文章补图，都会比较直观。</p><p>实际迁移流程是：先下载并备份原图，按文章整理，再通过 PicX 上传；等 EdgeOne 部署完成，取得新链接，最后备份文章并替换图片地址。原有的图注、作者、来源页面和许可说明继续保留。</p><p>为了减少重复操作，我另外准备了一个小工具，辅助扫描、下载、匹配和替换。</p><p>匹配时，每个原图地址在清单中对应一个编号，下载后的文件名保留这个编号。因此，即使上传后添加了后缀、转换了格式，仍能从图床文件名找到对应图片。文章里的图注不需要改成新文件名，替换的是图片 URL。</p></section>
<section><h1>批量下载先遇到了 429<a href="#批量下载先遇到了-429"><span>#</span></a></h1><p>文章中的不少图片来自 Wikimedia Commons。开始批量下载后，很快遇到了 <code>HTTP 429 Too Many Requests</code>，部分响应还要求等待 600 秒。</p><p>排查时，我找到了 <a href="https://foundation.wikimedia.org/wiki/Policy:Wikimedia_Foundation_User-Agent_Policy/zh" target="_blank">Wikimedia 的 User-Agent 政策</a>。它要求自动化客户端明确表明身份，提供可识别的程序信息和联系渠道。</p><p>于是，下载请求开始使用包含程序名、版本和联系地址的 User-Agent，例如下面这种形式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ExampleImageMigratorBot/1.0 (https://example.com/contact)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时，我把下载控制为单线程、请求间隔约 5 秒，总下载限速约 1 MB/s，并继续复用已经下载好的文件。</p><p>调整后，429 明显减少了。但这不能只归功于 User-Agent：请求频率、带宽和缓存复用也一起发生了变化。表明身份更不等于登录认证或获得限流豁免。收到 429 时，仍应遵守源站的 <code>Retry-After</code> 等要求，相关规则见 <a href="https://wikitech.wikimedia.org/wiki/Robot_policy" target="_blank">Wikimedia Robot policy</a>。</p></section>
<section><h1>最后一张 SVG，问题出在识别逻辑<a href="#最后一张-svg问题出在识别逻辑"><span>#</span></a></h1><p>下载接近完成时，只剩一张 SVG 图片始终下载失败。</p><p>检查后发现，文件本身能够读取，只是在 XML 声明后还带有 <code>DOCTYPE</code>。原来的识别逻辑没有完整处理这类文件头，导致合法的 SVG 没有通过检查。</p><p>SVG 文件不一定直接以 <code>&lt;svg&gt;</code> 开头，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;?</span><span>xml</span><span> version</span><span>=</span><span>"1.0"</span><span>?&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;!</span><span>DOCTYPE</span><span> </span><span>svg</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span>&lt;</span><span>svg</span><span> </span><span>xmlns</span><span>=</span><span>"http://www.w3.org/2000/svg"</span><span>&gt;...&lt;/</span><span>svg</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>补全识别逻辑后，这张图成功保存，原始文件内容也保持不变。这个问题提醒我，下载失败有时发生在客户端自己的检查步骤里，需要把响应状态、实际内容和识别结果分开看。</p></section>
<section><h1>固定八张图片为什么总是 404<a href="#固定八张图片为什么总是-404"><span>#</span></a></h1><p>上传完成后，最费解的问题出现了：大部分图片都正常，偏偏固定几张始终无法通过 EdgeOne 访问。重新上传以后，问题仍然存在。</p><p>把失败的文件名放在一起看，终于发现共同点：它们都包含英文单引号 <code>'</code>，例如 <code>l'Armée</code>、<code>d'assalto</code> 和 <code>'75'</code>。</p><p>随后，对同一批文件做了对照：</p>

<table><thead><tr><th>测试方式</th><th>结果</th></tr></thead><tbody><tr><td>直接读取 GitHub 上的图片文件</td><td>正常返回图片</td></tr><tr><td>访问 EdgeOne 对应路径</td><td>返回 404</td></tr><tr><td>把单引号编码成 <code>%27</code> 后访问</td><td>仍返回 404</td></tr><tr><td>去掉单引号，重新上传并部署</td><td>正常返回图片</td></tr></tbody></table><p>这个结果很明确地把问题缩小到了含单引号的资源路径上。</p><p>不过，<strong>单引号本身是合法的 URL 路径字符</strong>，<a href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.3" target="_blank">RFC 3986</a>允许它出现在路径中。因此，不能把这次经历直接写成“所有图床都不支持单引号”。</p><p>根据实测，更可能是当前 EdgeOne 部署链路在处理这类路径时存在兼容性问题。具体发生在文件索引、转义解码还是其他环节，仅凭这些响应还无法确定。</p><p>对我的博客而言，先采用已经验证有效的处理方式就足够了：上传前去掉文件名中的英文单引号，保留其余名称和匹配编号。</p></section>
<section><h1>后续维护<a href="#后续维护"><span>#</span></a></h1><p>以后新增图片时，我会先保存本地副本，放进对应文章的目录，处理文件名，再上传 PicX，等 EdgeOne 部署完成后使用实际链接。</p><p>更换图片时，使用新文件名和新链接，确认文章显示正常后再整理旧图。如果本地同步提示某张图已不再被文章引用，也会先看清待删除清单；图床上的删除则另外确认，不把两件事混在一起。</p><p>这次整理以后，图片有了本地备份，文章里的资源也有了统一的访问入口。下一篇文章再需要配图时，就可以沿着这套已经跑通的流程继续添加了。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/261004-wwi-st2-industrial-war-weapons/</id>
      <title type="text">WWI-专题2 工业化战争：一战中的武器技术革命</title>
      <published>2026-10-04T00:00:00.000Z</published>
      <updated>2026-10-04T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/261004-wwi-st2-industrial-war-weapons/"/>
      <summary type="text">从栓动步枪、狙击镜与枪榴弹，到轻机枪、冲锋枪、速射炮、迫击炮、战斗机与坦克：一战并没有凭空发明所有现代武器，却让它们以前所未有的速度被改进、量产并投入实战。沿着几条清晰的技术发展脉络，看看工业时代的武器装备如何在四年战争中迅速演变。</summary>
      <content type="html"><![CDATA[
<p>1914 年夏天，欧洲各国把数以百万计的士兵送上战场。</p>
<p>他们穿着不同的军服，说着不同的语言，但绝大多数步兵手里拿着的，仍然是一支长长的栓动步枪。</p>
<p>乍看之下，战场上仍保留着许多十九世纪的景象。骑兵仍在，马匹仍然拖着火炮，军官仍然佩剑，步兵仍然训练刺刀冲锋。可是，如果把视线从军装和队列移到武器内部，就会发现一个完全不同的世界已经形成。</p>
<p>无烟火药让步枪射击不再升起大片白烟；弹仓和桥夹让士兵能够连续射击；马克沁式机枪让持续的自动射击成为现实；新式后坐复进装置让炮手不必每打一发就把整门野战炮推回原位；飞机已经飞上天空；内燃机、履带、装甲钢板和大规模机械制造，则让一种此前只存在于设想中的陆地战舰逐渐成为可能。</p>
<p>这些技术许多在 1914 年以前就已经存在。</p>
<p>战争把这些技术投入了一个持续四年的巨大试验场。数百万士兵在前线使用武器，数以万计的火炮不断开火，军工厂持续扩张，前线的需求几乎从未停歇。这一切迅速让设计者明白——<strong>什么好用，什么不好用，什么必须立刻改。</strong></p>
<p>于是，一种武器往往会在短短几年里经历一轮完整的改进过程：</p>
<blockquote><p>战前设计<br />
→ 战场暴露问题<br />
→ 简化或改进<br />
→ 更适合大规模生产<br />
→ 新型号投入前线<br />
→ 对手发展反制措施</p></blockquote>
<hr />
<section><h1>步枪：步兵的主要武器<a href="#步枪步兵的主要武器"><span>#</span></a></h1><p>第一次世界大战中最常见的单兵武器是步枪。1914 年的欧洲主要陆军已经普遍完成了从单发步枪到<strong>使用无烟火药、采用小口径弹药和弹仓供弹的栓动步枪</strong>的转型。它们的基本操作方式非常相似：射手抬起枪机柄、向后拉出弹壳，再向前推入下一发子弹，锁紧枪机后重新击发。</p><p>各国的设计差异体现在容量、枪机与供弹结构上。英国的 Lee-Enfield（李-恩菲尔德）强调较大的弹仓容量和快速操作；德国的 Gewehr 98（毛瑟98式步枪）以坚固的 Mauser 98 式机匣闻名；法国的 Lebel（勒贝尔步枪）则保留着新旧技术交替的鲜明痕迹——它在 1886 年问世时极其先进，到 1914 年，旧有结构的局限却已经逐渐显现。</p>

<table><thead><tr><th>代表步枪</th><th>国家</th><th>弹仓 / 供弹特点</th><th>最值得注意的地方</th></tr></thead><tbody><tr><td>李-恩菲尔德 Mk III</td><td>英国</td><td>10 发盒式弹仓，5 发桥夹装填</td><td>容量大、枪机操作快，长度兼顾步兵与骑兵的使用需求</td></tr><tr><td>毛瑟98式步枪</td><td>德国</td><td>5 发内置弹仓，桥夹装填</td><td>Mauser 98 枪机坚固可靠，成为后来大量军用步枪的技术基础</td></tr><tr><td>勒贝尔M1886/93 步枪</td><td>法国</td><td>8 发管状弹仓，逐发装填</td><td>无烟火药时代的先驱，但管状弹仓在一战时已显老旧</td></tr></tbody></table><section><h2>SMLE Mk III（短弹匣李-恩菲尔德步枪 Mk III）：英国人的“短步枪”<a href="#smle-mk-iii短弹匣李-恩菲尔德步枪-mk-iii英国人的短步枪"><span>#</span></a></h2><p><strong>短弹匣李-恩菲尔德步枪</strong>这个译名很容易让人误解。</p><p>“Short”并不是说它的弹匣很短，而是说<strong>整支枪比此前英军传统的长步枪更短</strong>。20 世纪初的英国陆军希望用一种统一长度的步枪同时满足步兵和骑兵需求，于是 1903 年出现了 SMLE；1907 年的 Mk III 又进一步完善了设计。</p><p>它最醒目的特点之一，是 <strong>10 发盒式弹仓</strong>。相比德军毛瑟98式步枪常见的 5 发弹仓，英国步枪兵每次装满弹仓后，都能连续打出更多发子弹。实际装填时，士兵通常把 5 发子弹装在一个桥夹上，从机匣上方一次压入弹仓；两个桥夹即可装满。</p><p>另一个特点在于 Lee 系列枪机的操作方式。李-恩菲尔德属于**闭锁时待击（cock-on-closing）**的结构。简单来说，射手向后拉枪机时主要完成开锁和抽壳，向前推回并闭锁时才压缩击针簧。配合位置靠后的枪机柄，熟练射手可以迅速完成退壳、装填和闭锁。</p><p>英国陆军十分重视快速、准确的步枪射击训练。战争初期，在蒙斯等战斗中，训练有素的英国正规军能够打出很高的射击密度。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Lee-Enfield_Mk_III_%28No_1_Mk_3%29_-_AM.032056_%28LHS%29--ff-5a162e040f74.2a5vsf20jz.webp" alt="瑞典陆军博物馆收藏的 李-恩菲尔德 Mk III 步枪" /><figcaption>瑞典陆军博物馆收藏的 李-恩菲尔德 Mk III 步枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/SMLE_rifle_with_bayonet_fixed--ff-1f374e3d38a1.60v1dnqvz5.webp" alt="一战时期安装 Pattern 1907（1907式）刺刀的李-恩菲尔德步枪" /><figcaption>一战时期安装 Pattern 1907（1907式）刺刀的李-恩菲尔德步枪</figcaption></figure></div><p>一战的实战需求又推动了李-恩菲尔德的改进。</p><p>原版 Mk III 还保留着远距离齐射瞄具和弹匣隔断器等战前装置。为加快生产，英国开始取消部分不再必要的功能。</p><p>于是 1915—1916 年间，简化生产的 <strong>Mk III*</strong> 逐渐普及。部分不再被认为必要的结构被取消，从而减少加工工序和制造时间。</p></section><section><h2>Gewehr 98（毛瑟98式步枪）：毛瑟枪机的成熟形态<a href="#gewehr-98毛瑟98式步枪毛瑟枪机的成熟形态"><span>#</span></a></h2><p>德军标准步枪 <strong>毛瑟98式步枪</strong> 在 1898 年定型。</p><p>它采用 Mauser 98 系列旋转后拉式枪机，使用 7.92 mm 弹药，5 发内置弹仓由桥夹一次装填。与 Lee-Enfield 相比，它的弹仓容量较小，枪身也更长，但其机匣和闭锁结构非常坚固。</p><div><div><div></div><div>Note</div></div><div><p><strong>“7.92×57”怎样读？</strong></p><p>这类公制弹药名称通常依次列出名义弹径与弹壳长度，单位均为毫米。7.92×57 mm 中的 57 mm 表示弹壳长度，全弹长度还包括伸出弹壳的弹头。</p></div></div><p>Mauser 98 的影响远不止于一战。其双前锁耳、控制进弹、抽壳和保险结构，后来对许多国家的军用与民用栓动步枪都产生了深远影响。从西班牙、南美到美国和后来的德国 Karabiner 98k，都能看到 Mauser 技术路线留下的痕迹。</p><p>但“设计优秀”不意味着它能够完美适应所有战场环境。</p><p>毛瑟98式步枪是一支典型的战前长步枪。装上刺刀以后，在开阔地和传统步兵队形中并没有问题；可一旦进入曲折、狭窄的壕沟，过长的枪身就会妨碍行动和射击。</p><p>战争期间，更短的 <strong>毛瑟 98AZ 卡宾枪</strong> 被突击部队、骑兵和部分专业兵种广泛使用。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Gewehr_98--ff-4487d78bc876.1ow8647k81.webp" alt="德国毛瑟98式步枪" /><figcaption>德国毛瑟98式步枪</figcaption></figure><p></p></section><section><h2>Lebel M1886/93（勒贝尔 M1886/93 步枪）：背着历史包袱的武器<a href="#lebel-m188693勒贝尔-m188693-步枪背着历史包袱的武器"><span>#</span></a></h2><p>如果只从 1914 年看，法国 <strong>勒贝尔 M1886/93 步枪</strong> 的结构会显得有些奇怪。</p><p>它的子弹不是装在机匣下方的盒式弹仓，而是首尾相接地排在枪管下面的一根<strong>管状弹仓</strong>里。射手必须一发一发地把子弹塞进去，补弹速度明显慢于桥夹或后来的漏夹系统。</p><p>可如果把时间拨回到 1886 年，就会发现它的问世是一场真正的技术革命。</p><p>勒贝尔是第一批围绕<strong>无烟火药</strong>设计并大规模装备的军用步枪。</p><p>过去的黑火药每开一枪都会产生大量烟雾。成排士兵连续射击后，阵地很快被白烟覆盖，不仅暴露位置，还会遮挡视野；火药残渣也更容易污染枪膛。</p><p>法国化学家 Paul Vieille 发明的 Poudre B（B型无烟火药）大幅改变了这一点。</p><p>无烟火药燃烧更充分，能在较小口径下获得更高弹速，烟雾和残渣也少得多。于是，步兵武器从大口径黑火药时代真正进入了现代小口径步枪时代。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Lebel_Mle._1886--ff-1c483a29d62b.51ey0ho4lp.webp" alt="法国勒贝尔 Mle 1886 步枪" /><figcaption>法国勒贝尔 Mle 1886 步枪</figcaption></figure><p></p><p>问题在于，法国当时为了尽快发挥新火药的优势，在新枪设计中沿用了许多成熟零件和旧有设计。</p><p>结果就是：**弹药非常先进，弹仓却很快过时。**管状弹仓不仅装填慢，还会限制弹头形状。尖头弹如果直接顶着前一发子弹的底火，可能造成危险，因此法国后来还必须围绕弹壳和弹头进一步改进。</p><p>一战期间，更现代的 <strong>贝蒂埃</strong> 系列步枪和卡宾枪越来越重要。它使用漏夹供弹，装填速度更快；1918 年出现的 5 发弹仓版本又进一步改善了早期 3 发容量过小的问题。</p><p>但法国已经拥有大量勒贝尔步枪，战争也没有给军队留下“全部换枪”的余裕。</p><p>所以，直到停战前，大量法国士兵仍然在使用这支诞生于 1886 年的步枪。</p><p>数百万支旧枪及其配套弹药、零件和训练体系，使彻底换装代价高昂。新型号投入以后，军队通常仍要长期使用原有装备。</p></section><section><h2>其他主要参战国的制式步枪<a href="#其他主要参战国的制式步枪"><span>#</span></a></h2><p>俄国、奥匈、意大利与美国军队也使用着各自的制式步枪。</p><p>它们同样属于“无烟火药 + 弹仓供弹 + 栓动枪机”的大框架，但在枪机结构、装填方式和工业生产上，各国采取了不同方案。</p>

<table><thead><tr><th>步枪</th><th>主要使用国</th><th>供弹 / 枪机特点</th><th>最值得注意的地方</th></tr></thead><tbody><tr><td>Mosin–Nagant M1891（莫辛-纳甘 M1891）</td><td>俄罗斯帝国</td><td>5 发内置弹仓，桥夹装填</td><td>俄军一战最主要制式步枪之一，战争中还依靠国外合同补充产量</td></tr><tr><td>Mannlicher M1895（曼利夏 M1895）</td><td>奥匈帝国</td><td>5 发漏夹，直拉式枪机</td><td>不需要像毛瑟那样旋转枪机柄，直接前后拉动即可完成循环</td></tr><tr><td>Carcano M91（卡尔卡诺 M91）</td><td>意大利</td><td>6 发漏夹</td><td>6.5 mm 小口径制式步枪，构成意军一战单兵武器主体</td></tr><tr><td>M1903 Springfield（M1903 斯普林菲尔德）</td><td>美国</td><td>5 发内置弹仓，桥夹装填</td><td>美国战前标准步枪，明显受到毛瑟技术路线影响</td></tr><tr><td>U.S. M1917 Enfield（美国 M1917 恩菲尔德）</td><td>美国</td><td>.30-06，内置弹仓</td><td>由英国 P14 生产体系改造而来，实际成为美国远征军更常见的步枪</td></tr></tbody></table><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mosin-Nagant_M1891_-_Ryssland_-_AM.032971--ff-63db537adf10.8okho0jx85.webp" alt="瑞典陆军博物馆收藏的俄国莫辛-纳甘 M1891 步兵步枪" /><figcaption>瑞典陆军博物馆收藏的俄国莫辛-纳甘 M1891 步兵步枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mannlicher_M1895_from_the_Swedish_Army_Museum--ff-3302be799f48.mv8xha5m.webp" alt="瑞典陆军博物馆收藏的奥匈曼利夏 M1895 直拉式步枪" /><figcaption>瑞典陆军博物馆收藏的奥匈曼利夏 M1895 直拉式步枪</figcaption></figure></div><p>莫辛-纳甘仍然属于比较传统的旋转后拉式枪机，射手需要抬起枪机柄解锁，再向后拉动；曼利夏 M1895 的直拉式枪机则把旋转闭锁动作交给枪机内部机构完成，射手只需要前后直拉枪机柄。</p><p>卡尔卡诺 M91 的不同之处则在于装填方式。它使用 6 发漏夹。士兵不是把子弹从桥夹压进弹仓后抽走桥夹，而是连同金属漏夹一起压入弹仓；最后一发送入枪膛后，空漏夹会从弹仓下方掉出。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Carcano_M1891--ff-b130dd2251d5.70b4qttmtp.webp" alt="瑞典陆军博物馆收藏的意大利 Carcano M1891 步枪" /><figcaption>瑞典陆军博物馆收藏的意大利 Carcano M1891 步枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/M1917_Enfield_right_side--ff-4bc908ab2bca.232nwzfv6w.webp" alt="瑞典陆军博物馆收藏的美国 M1917 Enfield 步枪" /><figcaption>瑞典陆军博物馆收藏的美国 M1917 Enfield 步枪</figcaption></figure></div><section><h3>M1917 Enfield（M1917 恩菲尔德步枪）：美国远征军最常见的步枪并不是 M1903<a href="#m1917-enfieldm1917-恩菲尔德步枪美国远征军最常见的步枪并不是-m1903"><span>#</span></a></h3><p>美国已经拥有自己的 <strong>M1903 Springfield（M1903 斯普林菲尔德步枪）</strong>，这支枪性能优秀，也已经是美国陆军制式武器。</p><p>美国参战后要为大量士兵配发步枪，原有 M1903 产能无法及时满足需求。</p><p>好在美国军工企业此前已经替英国生产 <strong>Pattern 1914 Enfield（P14 恩菲尔德步枪）</strong>。</p><p>Winchester、Remington 和其 Eddystone 工厂已经拥有机器、工装、熟练工人和成熟生产线。</p><p>美国于是保留 P14 的基本设计，改用美军 .30-06 弹药，以便利用现有设备扩大产量。</p><p>由此产生了 <strong>U.S. Rifle, Caliber .30, Model of 1917</strong>，通常简称 <strong>M1917 Enfield（M1917 恩菲尔德）</strong>。</p><p>它的内部弹仓实际上可以容纳 6 发 .30-06 弹，但标准桥夹仍然一次装入 5 发。</p><p>澳大利亚战争纪念馆的馆藏资料特别指出：与后来的大众印象不同，第一次世界大战期间美国远征军使用最广泛的步枪并不是 M1903，而是 M1917。</p><p>M1903 仍然大量装备美军，也用于精确射击和狙击任务。</p></section></section><section><h2>半自动步枪的大量使用<a href="#半自动步枪的大量使用"><span>#</span></a></h2><p>如果只看 1914 年的主力制式武器，一战似乎仍是栓动步枪的时代。</p><p>但战争结束以前，<strong>半自动步枪，甚至能切换单发和连发模式的步枪，已经进入部队。</strong></p><p>扣一次扳机打一发之后，士兵并不需要再手动拉一次枪机，这可以大大提升射击速度。</p><section><h3>RSC M1917：法国为步兵配备的半自动步枪<a href="#rsc-m1917法国为步兵配备的半自动步枪"><span>#</span></a></h3><p>法国的 <strong>Fusil Automatique Modèle 1917</strong>，通常被称为 <strong>RSC M1917 半自动步枪</strong>。</p><p>它使用法军现有的 8×50 mmR Lebel 弹药，通过导气机构让射击产生的火药燃气推动机械循环：</p><blockquote><p>开火<br />
→ 导气机构获得能量<br />
→ 枪机自动后退、抽壳<br />
→ 复进并装入下一发<br />
→ 射手再次扣扳机即可继续射击</p></blockquote><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/FSA-1917-detoured--ff-e5ef1aab100b.2ksplkh8nr.webp" alt="保存至今的法国 RSC M1917 半自动步枪" /><figcaption>保存至今的法国 RSC M1917 半自动步枪</figcaption></figure><p></p><p>沿用现役 8 mm Lebel 弹药，使法国不必为新枪重建弹药供应体系。澳大利亚战争纪念馆将这一点列为 RSC 被选中投入有限生产的原因之一。</p><p>1917—1919 年间，RSC M1917 与后续型号的产量达到数万支。</p></section><section><h3>Fedorov Avtomat（费德洛夫自动步枪）：俄国较早探索了后坐力较小的自动步枪<a href="#fedorov-avtomat费德洛夫自动步枪俄国较早探索了后坐力较小的自动步枪"><span>#</span></a></h3><p>俄国设计师 Vladimir Fedorov（弗拉基米尔·费德洛夫）的思路更加激进。</p><p>他的 **Fedorov Avtomat（费德洛夫自动步枪）**不只能够自动装填，还可以在单发和连发之间切换，使用 25 发可拆卸弹匣。</p><p>更特别的是弹药。费德洛夫最终没有让它继续使用俄军 7.62×54 mmR 制式步枪弹，而是采用日本 <strong>6.5×50 mmSR Arisaka（6.5毫米有坂弹）</strong>。</p><p>俄国在战争中已经进口大量日本步枪和弹药，因此这种弹药并不是完全脱离现实补给体系的实验品。</p><p>6.5 mm 有坂弹产生的后坐力也明显小于许多欧洲大口径全威力步枪弹，更适合一支相对轻便的自动武器。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Fedorov_avtomat--ff-785abb02be1d.4g5ae6to80.webp" alt="芬兰军事博物馆收藏的 Fedorov Avtomat M1916 自动步枪" /><figcaption>芬兰军事博物馆收藏的 Fedorov Avtomat M1916 自动步枪</figcaption></figure><p></p><p>图拉国家武器博物馆的资料记载，装备这种武器的一个专门连队隶属于第 189 伊兹梅尔团，并在 <strong>1916 年 12 月被送往前线</strong>；俄军飞行员也曾使用这种武器。</p><p>它的数量很少，结构也较复杂，但它确实引出了一种枪械改良思路：</p><blockquote><p>如果一种单兵自动武器使用后坐力更小的弹药，它是不是会比“把全威力步枪弹硬塞进轻型自动武器”更容易控制？</p></blockquote><p>因此，后人经常把费德洛夫自动步枪拿来与后来“突击步枪”的概念比较。</p>

<table><thead><tr><th>武器</th><th>弹药</th><th>射击方式</th><th>一战中的状态</th><th>技术方向</th></tr></thead><tbody><tr><td>RSC M1917</td><td>8×50 mmR Lebel</td><td>半自动</td><td>数万支投入法军</td><td>让制式步枪自动完成装填循环</td></tr><tr><td>BAR M1918</td><td>.30-06</td><td>单发 / 全自动</td><td>战争末期投入</td><td>全威力自动步枪</td></tr><tr><td>Fedorov Avtomat</td><td>6.5×50 mmSR Arisaka</td><td>单发 / 全自动</td><td>少量前线使用</td><td>后坐力较小的弹药 + 单兵自动火力</td></tr><tr><td>MP 18</td><td>9×19 mm</td><td>全自动</td><td>1918 年投入</td><td>放弃远射程，专攻近战</td></tr></tbody></table><div><div><div></div><div>Note</div></div><div><p>一战前后还有法国 **Meunier A6（默尼耶 A6）**与由墨西哥设计、后来被德国采购的 **Mondragón（蒙德拉贡）**等半自动步枪路线。</p></div></div></section></section><section><h2>步枪开始“长出附件”<a href="#步枪开始长出附件"><span>#</span></a></h2><p>堑壕战推动各国通过附件扩展步枪的用途。它可以装上光学瞄准镜，成为狙击手的精确射击武器；也可以在枪口加装发射装置，把手榴弹送到人手无法投掷的距离；刺刀等近战武器则可以让步兵不得不近身作战时，占据一定的优势。</p><section><h3>狙击步枪：把几秒钟的暴露变成致命错误<a href="#狙击步枪把几秒钟的暴露变成致命错误"><span>#</span></a></h3><p>固定战线形成以后，士兵即使只从堑壕中暴露几秒钟，也可能遭到致命的射击。探头观察无人区、修理铁丝网，或穿过壕沟缺口，都可能让自己成为目标。</p><p>因此，能够在远距离抓住这短暂机会、准确命中目标的射手，越来越受到重视。</p><p>第一次世界大战并不是历史上第一次出现优秀射手，但它推动各国<strong>把狙击手的选拔、训练和使用纳入正式的军事制度</strong>。</p><p>德军早期占有一个重要优势：德国拥有成熟的光学工业，也较早把商用和军用光学瞄准镜安装到毛瑟98式步枪等步枪上。英军则在战争中逐渐给李-恩菲尔德配用 PPCo、Aldis、Winchester A5 等不同型号的瞄准镜，并建立专门的狙击学校。</p><p>光学瞄准镜便于识别目标细节、观察弹着，也能配合狭窄射孔与伪装阵地使用。</p><p>但早期军用瞄准镜的性能和可靠性远不如今天。安装座可能松动，镜片质量差异很大，倍率也不高；有些瞄准镜还不得不偏置在机匣一侧，以免妨碍装填。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/American_First_World_War_Official_Exchange_Collection_Q103350--ff-1083c273a06a.60v1dnqvkr.webp" alt="1918 年 5 月，法国 Badonviller，一支带光学瞄准镜并经过伪装的美军 Springfield 狙击步枪" /><figcaption>1918 年 5 月，法国 Badonviller，一支带光学瞄准镜并经过伪装的美军 Springfield 狙击步枪</figcaption></figure><p></p><p>英军还建立专门训练，要求射手学习：</p><ul>
<li>距离判断；</li>
<li>风偏修正；</li>
<li>伪装；</li>
<li>观察；</li>
<li>射击阵地选择；</li>
<li>如何在开火以后不立刻暴露自己。</li>
</ul></section><section><h3>枪榴弹：既然“扔不远”，那就“射出去”<a href="#枪榴弹既然扔不远那就射出去"><span>#</span></a></h3><p>如果敌军躲在更远一段战壕、交通壕或反斜面后面，普通步枪虽然射程足够，但子弹无法产生手榴弹那样的爆炸效果。</p><p>手榴弹受投掷距离限制，枪榴弹则借助步枪发射，把爆炸火力送到更远的地方。</p><p>战争早期最简单的办法，是给手榴弹或专用榴弹加上一根金属杆，把杆插进步枪枪管，使用空包弹发射。</p><p>英国的 No. 3、No. 23 等杆式枪榴弹就属于这条路线。</p><p>但问题也很明显：</p><ul>
<li>发射时后坐力巨大；</li>
<li>金属杆会磨损甚至损坏膛线；</li>
<li>枪托容易开裂；</li>
<li>射手往往必须把枪托抵在地面，而不能像普通步枪那样抵肩射击。</li>
</ul><p>英国后来发展出<strong>杯式发射器（cup discharger）</strong>，把榴弹放在枪口前的金属杯中，用空包弹推动，从而避免把长杆直接插进枪管。</p><p>法国则广泛使用 <strong>Viven-Bessières（VB）枪榴弹</strong>。</p><p>它的发射筒安装在勒贝尔或贝蒂埃步枪枪口上，榴弹中央有一条通道。普通步枪弹从通道穿过时产生的火药燃气推动榴弹飞出，同时启动引信。</p><p>这意味着法国 VB 系统在特定条件下甚至可以使用实弹，而不必每次换用空包弹。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/111-SC-2123_-_French_Soldiers_demonstrating_use_of_rifle_grenades_-_NARA_-_55165599_%28cropped%29--ff-a74471837b6d.46h6nacjj.webp" alt="一战期间法国士兵向美军海军陆战队演示 VB 枪榴弹发射器" /><figcaption>一战期间法国士兵向美军海军陆战队演示 VB 枪榴弹发射器</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Gewehr-Granate_VB--ff-30ddb0c1c696.8l0vqaqucj.webp" alt="1916 年 Viven-Bessières 枪榴弹结构图" /><figcaption>1916 年 Viven-Bessières 枪榴弹结构图</figcaption></figure></div><p>于是，步兵开始获得一种处于手榴弹与迫击炮之间的火力：</p>

<table><thead><tr><th>武器</th><th>典型距离</th><th>优势</th><th>局限</th></tr></thead><tbody><tr><td>手榴弹</td><td>很近</td><td>简单、灵活</td><td>完全受投掷距离限制</td></tr><tr><td>枪榴弹</td><td>近—中距离</td><td>普通步枪即可提供爆炸火力</td><td>后坐力大，装填与瞄准较慢</td></tr><tr><td>轻型迫击炮</td><td>更远</td><td>弹道弧度大、射速较高、弹头较重</td><td>需要专门武器与弹药</td></tr></tbody></table></section><section><h3>刺刀与近战武器<a href="#刺刀与近战武器"><span>#</span></a></h3><p>一战主要制式步枪大多仍然配有很长的刺刀。这和战前军队对刺刀冲锋、白刃战以及“步枪必须兼作长矛”的理解有关。长步枪再加长刺刀，理论上可以让步兵在近战中拥有更大的攻击范围。</p><p>可进入狭窄、曲折的壕沟后，这种长度反而可能成为负担。</p><p>因此，在一战的近距离战斗中，士兵也越来越多地使用其他武器：</p><ul>
<li>更短的卡宾枪；</li>
<li>壕沟刀；</li>
<li>工兵铲；</li>
<li>手枪，</li>
<li>甚至是临时使用钉子等制作的土制狼牙棒。</li>
</ul><hr /></section></section></section>
<section><h1>机枪与自动步枪：持续倾泻大量火力<a href="#机枪与自动步枪持续倾泻大量火力"><span>#</span></a></h1><p>如果说步枪是每一名普通士兵的基本武器，那么机枪就是最能体现工业时代战争变化的武器之一。</p><p>机枪的革命性意义，在于<strong>机械机构能够连续完成装填、闭锁、击发和退壳，不再需要射手逐发手动操作。</strong></p><section><h2>Maxim（马克沁机枪）：自动武器的共同祖先<a href="#maxim马克沁机枪自动武器的共同祖先"><span>#</span></a></h2><p>1880 年代，Hiram Maxim 制造出了第一批真正成熟、能够持续自动射击的机枪。</p><p>其核心思想非常重要：不再依靠射手手摇曲柄，也不再需要外部动力，而是直接利用<strong>每次射击本身产生的后坐能量</strong>完成下一次循环。</p><p>一发子弹击发后：</p><ol>
<li>枪管与枪机在后坐作用下移动；</li>
<li>机构完成开锁；</li>
<li>抽出并抛掉弹壳；</li>
<li>弹链送入下一发子弹；</li>
<li>枪机重新闭锁；</li>
<li>只要射手继续压住扳机，下一发便会继续击发。</li>
</ol><p>但持续射击也带来了一个新问题：<strong>枪管过热。</strong></p><p>因此，马克沁系列通常用水冷套筒包住枪管。只要冷却水、弹药和枪机状态允许，它就能维持远超过普通步枪的持续火力。</p><p>到一战爆发时，英国、德国、俄国等多个国家都已经装备了马克沁系列或其改进型。</p><p>其中最典型的，就是英国 <strong>维克斯</strong> 与德国 <strong>MG 08</strong>。</p></section><section><h2>Vickers（维克斯机枪）与 MG 08：同一祖先的两条路线<a href="#vickers维克斯机枪与-mg-08同一祖先的两条路线"><span>#</span></a></h2><p>英国维克斯机枪是在马克沁设计基础上进一步改进的型号。维克斯工程师重新布置了部分机构，让机匣更紧凑、零件更少、重量有所下降。1912 年，它成为英国陆军正式采用的重机枪。</p><p>德国的 <strong>MG 08</strong> 则保留了更多马克沁原始结构。它同样可靠，同样可以长时间射击，但整套系统十分沉重。枪本身、水冷套筒、沉重的滑橇式枪架、弹药箱、备件和冷却水共同组成了一个完整的重机枪阵地。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Vickers_machine_gun_crew_with_gas_masks--ff-e834b8463a34.3yf8plsazj.webp" alt="1916 年索姆河战役期间的英军 维克斯重机枪组" /><figcaption>1916 年索姆河战役期间的英军 维克斯重机枪组</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Maschinengewehr_08_%28MG_08%29_on_sled_mount_-_Braunschweigisches_Landesmuseum_-_DSC04729--ff-aef5dddf0893.1ow8647kdf.webp" alt="德国 MG 08 重机枪与滑橇式枪架" /><figcaption>德国 MG 08 重机枪与滑橇式枪架</figcaption></figure></div><p>重机枪需要多人操作：搬运枪架、水和弹药，观察目标，排除故障并维持供弹。</p></section><section><h2>不只是马克沁<a href="#不只是马克沁"><span>#</span></a></h2><p>法国刻意绕开马克沁专利，奥匈采用 Schwarzlose（施瓦茨洛泽），意大利则发展 FIAT–Revelli（菲亚特-雷维利），形成了其他技术路线。</p><p>它们最后都能完成“持续自动射击”这个任务，可内部机械、冷却和供弹方式差别很大。</p><section><h3>St. Étienne Mle 1907 → Hotchkiss M1914：复杂设计输给了可靠设计<a href="#st-étienne-mle-1907--hotchkiss-m1914复杂设计输给了可靠设计"><span>#</span></a></h3><p>法国在战争初期大量装备 <strong>St. Étienne Mle 1907（圣艾蒂安 M1907 机枪）</strong>。</p><p>它并不是马克沁式后坐自动武器，而是导气式机枪。</p><p>问题是，为了让机构工作，圣艾蒂安使用了一套相当复杂的活塞、齿条和齿轮传动系统。Royal Armouries 的馆藏研究指出，这套设计零件多、外露结构容易受到泥水影响，在一战堑壕环境中尤其容易出现卡壳等故障。</p><p>它甚至具有可调射速等颇有“工程巧思”的功能。</p><p>但到了前线，这些复杂功能带来的好处，并不足以弥补可靠性上的不足。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mitrailleuse_Saint-Etienne_mod%C3%A8le_1907%2C_Mus%C3%A9e_de_lArm%C3%A9e--ff-d94c54ae8b25.70b4qwqi8p.webp" alt="巴黎陆军博物馆收藏的 St. Étienne Mle 1907 机枪" /><figcaption>巴黎陆军博物馆收藏的 St. Étienne Mle 1907 机枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Hotchkiss%2C_1914--ff-c36eec169780.54yjy7h7ar.webp" alt="博物馆收藏的 Hotchkiss M1914 重机枪" /><figcaption>博物馆收藏的 Hotchkiss M1914 重机枪</figcaption></figure></div><p>法国最终越来越依赖 <strong>Hotchkiss M1914（哈奇开斯 M1914 重机枪）</strong>。</p><p>哈奇开斯同样采用导气原理，但结构更简单，也更可靠：</p><ul>
<li>风冷而不是水冷；</li>
<li>枪管后部有醒目的散热片；</li>
<li>主要使用金属弹板供弹；</li>
<li>结构比圣艾蒂安简单、可靠。</li>
</ul><p>它与马克沁采用了截然不同的技术组合：</p>

<table><thead><tr><th>特点</th><th>Maxim / Vickers</th><th>Hotchkiss M1914</th></tr></thead><tbody><tr><td>自动原理</td><td>后坐式</td><td>导气式</td></tr><tr><td>冷却</td><td>水冷</td><td>风冷</td></tr><tr><td>供弹</td><td>布质弹链</td><td>金属弹板</td></tr><tr><td>优势重点</td><td>长时间持续射击</td><td>坚固、结构相对简单、无需冷却水</td></tr></tbody></table><p>在殖民地和缺水环境中，风冷本身就是优势；在西线，它省去了水套和冷凝管，也不必供应冷却水；但弹板供弹和风冷又限制了长时间连续射击的能力。</p></section><section><h3>Schwarzlose M07/12：“优化版”马克沁？<a href="#schwarzlose-m0712优化版马克沁"><span>#</span></a></h3><p>奥匈帝国的主力重机枪之一是 <strong>Schwarzlose M07/12（施瓦茨洛泽 M07/12 重机枪）</strong>。</p><p>外表看，它同样有一个巨大的水冷套筒，同样从弹带持续供弹，很容易被误认成另一种“马克沁亲戚”。</p><p>但它的内部工作方式不同。施瓦茨洛泽采用<strong>延迟开锁 / 延迟自由枪机思路</strong>，依靠机械结构和强力复进簧防止枪机在膛压仍很高时过早打开，而不是让枪管与枪机像马克沁那样共同后坐完成循环。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Schwarzlose_MG_M07_12--ff-dddc8093d469.32ira5imhq.webp" alt="罗马步兵博物馆收藏的 Schwarzlose M07/12 重机枪、弹箱与布质弹链" /><figcaption>罗马步兵博物馆收藏的 Schwarzlose M07/12 重机枪、弹箱与布质弹链</figcaption></figure><p></p><p>这种方案的吸引力之一，是可以减少一部分复杂运动部件。</p><p>但它仍然需要解决高压弹壳抽出的困难，一战时期型号还围绕弹壳润滑采取了相应措施。</p></section><section><h3>FIAT–Revelli M1914：多列式供弹盒<a href="#fiatrevelli-m1914多列式供弹盒"><span>#</span></a></h3><p>意大利的 **FIAT–Revelli Modello 1914（菲亚特-雷维利 M1914 重机枪）**同样是一种水冷重机枪，却没有采用我们熟悉的长弹链。</p><p>最醒目的地方，是机匣侧面的<strong>多列式弹药供弹盒</strong>。这种装置把大量子弹分成数列排列，整盒从侧面送入机枪。射击机构逐列取弹，看上去很像一只被不断横向推进的金属抽屉。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Italian_Army_-_World_War_I_-_Italian_troops_carry_a_Fiat%E2%80%93Revelli_Modello_1914_machine_gun_on_the_Piave_front%2C_191--ff-17d05056a11c.73uqojmpmh.webp" alt="1918 年皮亚韦河战线，意大利士兵搬运 FIAT–Revelli Modello 1914 重机枪" /><figcaption>1918 年皮亚韦河战线，意大利士兵搬运 FIAT–Revelli Modello 1914 重机枪</figcaption></figure><p></p><p>它也需要通过润滑弹药来保证机构顺畅运转。</p><p>从后世眼光看，这套系统显得繁琐，但它在战争中确实构成了意大利机枪火力的重要组成部分。</p><p>把这些技术路线放在一起比较，就会发现一战重机枪的差异，远不止外观上的水冷套筒：</p>

<table><thead><tr><th>武器</th><th>国家</th><th>冷却</th><th>供弹</th><th>最值得注意的差异</th></tr></thead><tbody><tr><td>MG 08 / Vickers</td><td>德 / 英</td><td>水冷</td><td>弹链</td><td>马克沁后坐式技术谱系</td></tr><tr><td>St. Étienne M1907</td><td>法</td><td>风冷</td><td>弹板 / 后期部分弹带</td><td>机构复杂，堑壕条件下可靠性不足</td></tr><tr><td>Hotchkiss M1914</td><td>法</td><td>风冷</td><td>金属弹板</td><td>导气式、坚固可靠</td></tr><tr><td>Schwarzlose M07/12</td><td>奥匈</td><td>水冷</td><td>弹链</td><td>非马克沁式延迟开锁路线</td></tr><tr><td>FIAT–Revelli M1914</td><td>意</td><td>水冷</td><td>多列供弹盒</td><td>极具特色的供弹与润滑系统</td></tr></tbody></table></section></section><section><h2>Browning M1917（勃朗宁 M1917）：姗姗来迟的重机枪<a href="#browning-m1917勃朗宁-m1917姗姗来迟的重机枪"><span>#</span></a></h2><p>美国参战之初，本国重机枪的生产与供应尚不足以满足庞大远征军的需求。</p><p>John Moses Browning（约翰·摩西·勃朗宁）设计的新机枪在 <strong>1917 年 5 月</strong>通过美国陆军试验，随后被定型为 <strong>Browning M1917（勃朗宁 M1917 重机枪）</strong>。</p><p>它采用：</p><ul>
<li>水冷枪管；</li>
<li>弹链供弹；</li>
<li>.30-06 弹药；</li>
<li>多人枪组操作。</li>
</ul><p>看上去，它在任务定位上与 Vickers 很接近。</p><p>但内部并不是马克沁复制品，而是勃朗宁自己的短后坐自动机构。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Val_Browning_M1917--ff-dd2c07ea338e.2dphq4v3iq.webp" alt="美国陆军中尉 Val Browning 与 Browning M1917 重机枪" /><figcaption>美国陆军中尉 Val Browning 与 Browning M1917 重机枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Browning_Heavy_Machine_Gun%2C_water_cooled%2C_M1917%2C_1918%2C_WWI_%2832916860696%29--ff-62321f291b8a.szqqnxvpd.webp" alt="美国陆军通信兵资料中的 1918 年 Browning M1917 水冷重机枪" /><figcaption>美国陆军通信兵资料中的 1918 年 Browning M1917 水冷重机枪</figcaption></figure></div><p>它在测试中的持续射击能力给美国军方留下了深刻印象，随后迅速进入大规模生产。</p><p>美国 1917 年参战后，还要扩建工厂、生产武器、训练部队并完成跨洋运输。M1917 虽然技术已经成熟，直到战争最后阶段才真正进入前线。</p></section><section><h2>Madsen（麦德森轻机枪）：可以由士兵携带的轻机枪<a href="#madsen麦德森轻机枪可以由士兵携带的轻机枪"><span>#</span></a></h2><p>在刘易斯和绍沙之前，丹麦已经发展出一种真正可以由士兵携带的自动武器：</p><p><strong>Madsen（麦德森轻机枪）。</strong></p><p>它的设计可以追溯到 20 世纪初，早于第一次世界大战。</p><p>典型麦德森拥有：</p><ul>
<li>风冷枪管；</li>
<li>顶部弧形弹匣；</li>
<li>两脚架；</li>
<li>枪托；</li>
<li>重量相对较轻，可以由少数士兵组成的枪组携行。</li>
</ul><p>也就是说，当欧洲大国还把“机枪”首先理解为架在三脚架或滑橇上的重型装备时，轻机枪的基本形态其实已经开始出现。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/The_Battle_of_the_Somme%2C_July-november_1916_Q132--ff-1c0990a9f522.3nsewgd2sr.webp" alt="1916 年索姆河战役中英军展示缴获的德军武器，其中包括一挺麦德森轻机枪" /><figcaption>1916 年索姆河战役中英军展示缴获的德军武器，其中包括一挺麦德森轻机枪</figcaption></figure><p></p><p>澳大利亚战争纪念馆保存着一挺 <strong>Madsen M1902/04</strong>，其记录显示，它在 1916 年 7 月 23 日波济耶尔被澳军第 4 营从德军手中缴获。</p><p>随着各国军队对轻机枪的需求逐渐扩大，它开始从少量配备的特殊装备，成为普遍需要的步兵武器。</p></section><section><h2>Lewis（刘易斯轻机枪）：移动的机枪阵地<a href="#lewis刘易斯轻机枪移动的机枪阵地"><span>#</span></a></h2><p>刘易斯轻机枪是由美国军官 Isaac Newton Lewis 在 Samuel McClean 早期设计基础上改进，美国军方最初并没有积极采用，反而是英国首先大规模接受了这种武器。</p><p>刘易斯在重量与使用方式上，与维克斯有明显区别。</p><p>它取消了沉重的水冷系统，枪管外套着带散热片的铝制散热器和筒形护罩，通过射击产生的气流帮助散热；供弹也从弹链变成了枪顶上的圆盘形弹盘。</p><p>它还带有：</p><ul>
<li>枪托；</li>
<li>手枪握把；</li>
<li>两脚架；</li>
<li>可以由步兵直接携带的整体结构。</li>
</ul><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/British_Lewis_gun_team_Battle_of_Hazebrouck_1918_IWM_Q_10902--ff-5c30dfcdb7c6.7so08ka8ji.webp" alt="1918 年阿兹布鲁克战役期间的英军 刘易斯轻机枪组" /><figcaption>1918 年阿兹布鲁克战役期间的英军 刘易斯轻机枪组</figcaption></figure><p></p><p>战争中后期，英国步兵的火力组织因此发生了重要变化。</p><p>维克斯重机枪逐渐集中到 Machine Gun Corps（机枪军团）等专业单位，承担更持久、更远距离的火力任务；刘易斯则越来越多地进入步兵营、连和排，跟着进攻部队向前推进。</p><p>从“每个营只有少量机枪”，到“小单位也能掌握自动火力”，这是第一次世界大战步兵武器最重要的变化之一。</p></section><section><h2>Chauchat（绍沙轻机枪）：声名狼藉<a href="#chauchat绍沙轻机枪声名狼藉"><span>#</span></a></h2><p>法国的 <strong>Mle 1915 CSRG 绍沙轻机枪</strong>，通常被称为 <strong>绍沙</strong>。</p><p>绍沙的设计目标是轻便、能够随攻击队形移动，并适合大规模制造。它的持续射击能力无法与维克斯或哈奇开斯重机枪相比。</p><p>它使用长后坐自动原理和弯曲的半月形弹匣，整枪远轻于重机枪。一战期间法国制造了数量极大的绍沙，它成为法国步兵小单位自动火力的重要组成部分。</p><p>它确实有明显缺点：</p><ul>
<li>弹匣开有大面积观察口，泥沙容易进入；</li>
<li>射击节奏和握持、操作的舒适性并不理想；</li>
<li>长时间连续射击能力有限；</li>
<li>美国后来使用的 .30-06 口径版本还出现了更严重的可靠性问题。</li>
</ul><p>但那些最严厉的负面评价，往往把<strong>美军 .30-06 改型的问题</strong>与法国原版 8 mm 勒贝尔型混为一谈。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/French_soldier_with_Chauchat_taken_by_Jacques_Ridel_in_Le_Monchel_May_1918--ff-28be1fb5ceba.1lcm8eehhw.webp" alt="1918 年索姆地区法军士兵操作 绍沙轻机枪" /><figcaption>1918 年索姆地区法军士兵操作 绍沙轻机枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Chauchat_1_%2850643241222%29_%28cropped%29--ff-8cde9ce39fdd.4922ir7is4.webp" alt="博物馆收藏的绍沙Mle 1915 轻机枪" /><figcaption>博物馆收藏的绍沙Mle 1915 轻机枪</figcaption></figure></div>

<table><thead><tr><th>机枪</th><th>类型</th><th>冷却 / 供弹</th><th>战场角色</th></tr></thead><tbody><tr><td>维克斯</td><td>重机枪</td><td>水冷、弹链</td><td>长时间持续射击、阵地防御与火力支援</td></tr><tr><td>MG 08</td><td>重机枪</td><td>水冷、弹链</td><td>德军固定阵地火力核心</td></tr><tr><td>刘易斯</td><td>轻机枪</td><td>风冷、弹盘</td><td>跟随步兵前进，为班、排提供自动火力</td></tr><tr><td>绍沙</td><td>自动步枪 / 轻机枪</td><td>风冷、弹匣</td><td>为法军进攻中的小分队提供机动自动火力</td></tr></tbody></table></section><section><h2>MG 08/15：把一挺重机枪硬生生“变成步兵武器”<a href="#mg-0815把一挺重机枪硬生生变成步兵武器"><span>#</span></a></h2><p>刘易斯和绍沙代表了从一开始就追求较轻重量的自动武器路线。</p><p>德国则以现有 MG 08 为基础减重，研制了 <strong>MG 08/15</strong>。</p><p>它仍然采用马克沁式短后坐机构，仍然使用水冷套筒，也仍然由弹链供弹。也就是说，从机械原理上看，它与阵地里的 MG 08 是有着非常密切的技术关系。</p><p>但为了让它能够跟随步兵行动，德国设计者加入了：</p><ul>
<li>木制枪托；</li>
<li>手枪式握把；</li>
<li>两脚架；</li>
<li>更轻的水冷套筒和机匣部件。</li>
</ul><p>整套武器依然很重，远谈不上后来真正意义上的轻机枪。</p><p>可和需要沉重滑橇枪架的 MG 08 相比，它已经能够由更小的枪组搬运和使用。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Plotone_dassalto_tedesco_con_MG0815--ff-bde4ae426be1.92qxeyp39d.webp" alt="1917 年卡波雷托战役中的德军突击分队，携带 MG 08/15" /><figcaption>1917 年卡波雷托战役中的德军突击分队，携带 MG 08/15</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/MG_08-15--ff-583b529b580a.6writ40kbv.webp" alt="博物馆保存的 MG 08/15 轻量化马克沁机枪" /><figcaption>博物馆保存的 MG 08/15 轻量化马克沁机枪</figcaption></figure></div><p>沿用成熟的马克沁机构，便于利用现有生产体系，但也限制了减重幅度。</p><p>到战争后期，MG 08/15 大量进入德军步兵部队，也成为德军班排火力的重要组成部分。</p></section><section><h2>BAR M1918（M1918 勃朗宁自动步枪）：“机枪”也可以像是“自动步枪”<a href="#bar-m1918m1918-勃朗宁自动步枪机枪也可以像是自动步枪"><span>#</span></a></h2><p>John Moses Browning 为美军设计的 <strong>M1918 勃朗宁自动步枪（BAR）</strong>，同样用于向进攻步兵提供机动自动火力。</p><p>它不像维克斯那样有三脚架和水冷套筒，也不像 MG 08/15 那样保留着明显的马克沁式结构。</p><p>BAR 更像一支被放大、强化并能够全自动射击的步枪：</p><ul>
<li>使用 .30-06 全威力步枪弹；</li>
<li>20 发可拆卸盒式弹匣；</li>
<li>导气式自动原理；</li>
<li>肩射枪托；</li>
<li>由单兵携带。</li>
</ul><p>这使它属于一个后来长期存在、但边界始终有些模糊的类别：</p><p><strong>自动步枪（automatic rifle）。</strong></p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Val_Browning_M1918_BAR--ff-8f53f77bc2e0.39lz5l4ryv.webp" alt="1918 年 2 月，John Browning 之子 Val Browning 中尉手持 M1918 BAR" /><figcaption>1918 年 2 月，John Browning 之子 Val Browning 中尉手持 M1918 BAR</figcaption></figure><p></p><p>BAR 在一战中投入时间较晚，因此它对 1918 年战局的实际影响不能和维克斯、MG 08 或刘易斯相提并论。</p><p>到这里，一战步兵自动火力已经分化出几条很不一样的路线：</p>

<table><thead><tr><th>武器</th><th>弹药</th><th>主要思路</th><th>更接近后来的哪类武器</th></tr></thead><tbody><tr><td>MG 08/15</td><td>全威力步枪弹</td><td>把重机枪尽可能轻量化</td><td>通用 / 轻机枪的早期过渡</td></tr><tr><td>Lewis（刘易斯）</td><td>全威力步枪弹</td><td>从设计上追求可携行机枪</td><td>轻机枪</td></tr><tr><td>Chauchat（绍沙）</td><td>全威力步枪弹</td><td>让小枪组甚至单兵携带自动火力</td><td>自动步枪 / 轻机枪</td></tr><tr><td>BAR M1918</td><td>全威力步枪弹</td><td>让一名步兵本身成为自动火力点</td><td>自动步枪</td></tr></tbody></table><p>它们有一个非常重要的共同点：</p><p><strong>仍然围绕步枪弹建立自动火力。</strong></p><p>这意味着远距离性能和穿透力仍然很好，但武器和弹药的重量，以及射击时的后坐力，都很难大幅降低。</p><div><div><div></div><div>Note</div></div><div><p>战场上还可以见到较早采用导气式机构的 Colt-Browning M1895（柯尔特-勃朗宁 M1895）机枪，以及 Hotchkiss M1909 / Benét–Mercié（哈奇开斯 M1909 / 贝内-梅西耶）等轻型型号。</p></div></div><hr /></section></section>
<section><h1>手枪与左轮：更好的防身选择<a href="#手枪与左轮更好的防身选择"><span>#</span></a></h1><p>步枪与机枪是一战普通步兵最重要的武器，但并非所有军人都方便随身携带一支一米多长的步枪或者较为笨重的机枪。</p><p>军官、机枪组、炮兵、坦克乘员、飞行员和部分突击队员，都需要一种更紧凑的自卫武器。</p><p>堑壕战又进一步放大了这个需求。</p><p>在狭窄的交通壕、地下掩体中，或执行夜间巡逻和突击任务时，交战距离可能从几百米突然缩短到十几米甚至几步远。</p><p>于是，一战的手枪发展出现了一个很有意思的局面：</p><p><strong>左轮手枪并没有因为半自动手枪出现而立即消失；半自动手枪也开始不断扩大弹容量、增加枪托，尝试承担“军官副武器”之外的更多任务。</strong></p><section><h2>左轮与半自动的共存<a href="#左轮与半自动的共存"><span>#</span></a></h2><p>十九世纪末，左轮手枪已经是一种非常成熟的军用武器。</p><p>它的优点很明确：</p><ul>
<li>结构成熟；</li>
<li>操作直观；</li>
<li>对弹匣、供弹坡和自动循环的依赖较少；</li>
<li>发生哑火时，双动左轮往往只需继续扣动扳机即可转到下一发。</li>
</ul><p>但它的缺点也很明显：</p><ul>
<li>弹容量通常不高；</li>
<li>重新装填较慢；</li>
<li>转轮使枪身横向尺寸较大。</li>
</ul><p>半自动手枪则试图让火药燃气或后坐能量自动完成退壳、复进与下一发装填。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Luger_P08_%286971793777%29--ff-1dc827318e47.1ow8647kbj.webp" alt="博物馆收藏的德国 Luger P08（鲁格 P08）半自动手枪" /><figcaption>博物馆收藏的德国 Luger P08（鲁格 P08）半自动手枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Colt_M1911_Pistol_from_1912_Army_manual--ff-b688feed9031.pg4sy4t0a.webp" alt="美国陆军 1912 年手册中的 Colt M1911 半自动手枪" /><figcaption>美国陆军 1912 年手册中的 Colt M1911 半自动手枪</figcaption></figure></div><p>德国的 **Pistole 08（P08，鲁格手枪）**和美国的 <strong>M1911</strong>，正好代表一战前夕两种已经成熟的军用半自动手枪。</p><p>但它们并没有立刻把所有左轮淘汰。</p><p>英国仍然大量使用 <strong>Webley Mk VI（韦伯利 Mk VI）</strong>；法国军官仍使用 <strong>Modèle 1892</strong>；俄军继续使用 <strong>Nagant M1895（纳甘 M1895）</strong>。</p></section><section><h2>德国：P08 一步步变成“高容量近战武器”<a href="#德国p08-一步步变成高容量近战武器"><span>#</span></a></h2><p>标准 P08 使用 9×19 mm Parabellum 手枪弹和 8 发弹匣。</p><p>作为军官和专业兵种的副武器，这已经足够。</p><p>但德国有另一个需求：</p><p><strong>炮兵等人员需要一种比普通手枪射击更稳定、射程更远的武器，同时又希望减轻携带步枪的负担。</strong></p><p>由此出现了 <strong>Lange Pistole 08（LP 08，“炮兵鲁格”）</strong>。</p><p>它把标准 P08 向“手枪卡宾枪”方向推进了一步：</p><ul>
<li>枪管明显加长；</li>
<li>增加可调表尺；</li>
<li>可以安装木制枪托；</li>
<li>原本仍可使用普通 8 发弹匣；</li>
<li>后来还能使用 32 发 <strong>Trommelmagazin 08（TM 08）蜗形弹鼓</strong>。</li>
</ul><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Arty08--ff-54be4e4ceea4.3uvmrvz7ux.webp" alt="安装木制枪托与 32 发 Trommelmagazin 08 弹鼓的 Lange Pistole 08（LP 08，“炮兵鲁格”）" /><figcaption>安装木制枪托与 32 发 Trommelmagazin 08 弹鼓的 Lange Pistole 08（LP 08，“炮兵鲁格”）</figcaption></figure><p></p><p>最初，这套设计主要是为了让炮兵等专业人员获得一种比步枪更紧凑、比普通手枪更稳定的自卫武器。</p><p>堑壕战却很快给它找到了另一种用途。</p><p>枪托让射手可以抵肩射击；9 mm 手枪弹产生的后坐力较小；32 发弹鼓则让一支原本只能装 8 发子弹的手枪突然获得远高于普通副武器的持续火力。</p><p>于是，LP 08 开始出现在突击部队手中。</p><p>LP 08 结合了手枪弹、肩托与大容量供弹，但仍是<strong>半自动</strong>武器，每扣一次扳机只发射一发。</p></section><section><h2>美国：M1911 与 M1917——产能决定两种武器并用<a href="#美国m1911-与-m1917产能决定两种武器并用"><span>#</span></a></h2><p><strong>M1911</strong> 已经是一支成熟的制式半自动手枪：</p><ul>
<li>使用 .45 ACP 弹；</li>
<li>7 发可拆卸弹匣；</li>
<li>短后坐自动原理；</li>
<li>结构坚固；</li>
<li>可以快速更换弹匣。</li>
</ul><p>但美国在 1917 年参战以后，军队规模迅速扩大。</p><p>M1911 的现有产能无法及时满足需求。于是，美国没有等待新的半自动手枪生产线慢慢追上需求，而是同时向 Colt（柯尔特）和 Smith &amp; Wesson（史密斯-韦森）采购大批左轮手枪。</p><p>这就是 <strong>M1917 revolver（M1917 左轮手枪）</strong>。</p><p>真正有意思的是弹药。M1911 使用的是<strong>无底缘 .45 ACP</strong>，而传统左轮手枪通常依靠弹壳底缘在转轮中定位并帮助退壳。</p><p>美国的解决办法是加入一种薄金属夹：</p><p><strong>half-moon clip（半月形弹夹）</strong>。</p><blockquote><p>把几发 .45 ACP 固定在金属夹上<br />
→ 整组装入转轮<br />
→ 既能正确定位<br />
→ 又能在射击后一起退壳</p></blockquote><p>于是，同一种 .45 ACP 弹药可以同时供应：</p><ul>
<li>M1911 半自动手枪；</li>
<li>Colt M1917 左轮；</li>
<li>Smith &amp; Wesson M1917 左轮。</li>
</ul></section><section><h2>其他参战国<a href="#其他参战国"><span>#</span></a></h2><p>各国并没有沿着同一条路线前进，他们都有着自己独特的手枪体系。</p>

<table><thead><tr><th>武器</th><th>主要使用国</th><th>类型 / 弹药</th><th>一战中值得注意的地方</th></tr></thead><tbody><tr><td>Webley Mk VI（韦伯利 Mk VI）</td><td>英国及英联邦</td><td>.455，中折式左轮</td><td>1915 年采用；开枪后折开枪身即可同时顶出弹壳，广泛配发给机枪组、坦克乘员、空勤与突击人员</td></tr><tr><td>Modèle 1892</td><td>法国</td><td>8 mm，双动左轮</td><td>法军军官的重要制式副武器之一；结构成熟，继续与后来的半自动手枪并用</td></tr><tr><td>Ruby pistol（鲁比手枪）</td><td>法国等协约国</td><td>7.65 mm / .32 ACP，半自动</td><td>法国战争需求巨大，从西班牙大量采购；说明产能不足时，小型民用式自动手枪也会迅速被军队采用</td></tr><tr><td>Nagant M1895（纳甘 M1895）</td><td>俄罗斯帝国</td><td>7.62 mm，7 发左轮</td><td>俄军典型军用左轮，以特殊的转轮前移气密结构著称</td></tr><tr><td>Steyr M1912（斯泰尔 M1912）</td><td>奥匈帝国</td><td>9×23 mm Steyr，半自动</td><td>使用 8 发内置弹仓并以桥夹装填，和常见可拆卸弹匣半自动手枪走了不同路线</td></tr><tr><td>Glisenti M1910（格利森蒂 M1910）</td><td>意大利</td><td>9 mm Glisenti，半自动</td><td>意军战前制式自动手枪之一；其较弱的 9 mm Glisenti 弹后来也被 Villar Perosa 使用</td></tr><tr><td>Mauser C96 “Red 9”（毛瑟 C96“红9”）</td><td>德国</td><td>9×19 mm，半自动</td><td>德国为补充 P08 供应采购 9 mm 型；握把上的醒目红色“9”用于防止与 7.63 mm C96 弹药混淆</td></tr><tr><td>M1911</td><td>美国</td><td>.45 ACP，半自动</td><td>美国制式手枪；坚固、可快速换匣，成为另一条长期延续的军用半自动路线</td></tr><tr><td>Colt / S&amp;W M1917</td><td>美国</td><td>.45 ACP，左轮</td><td>以半月形弹夹兼容无底缘 .45 ACP，用成熟左轮产能补充 M1911 数量</td></tr></tbody></table><hr /></section></section>
<section><h1>冲锋枪：当手枪弹获得全自动火力<a href="#冲锋枪当手枪弹获得全自动火力"><span>#</span></a></h1><p>“冲锋枪”今天看起来是一个非常清楚的武器类别：</p><blockquote><p>使用手枪弹</p><ul>
<li>可以全自动射击</li>
<li>单兵携行</li>
<li>可以抵肩或以双手稳定射击</li>
<li>主要服务于近距离战斗</li>
</ul></blockquote><p>但在第一次世界大战期间，这套分类还没有完全成形。</p><p>德国当时称这类武器为 <strong>Maschinenpistole（机器手枪）</strong>；意大利也使用 <strong>pistola mitragliatrice</strong> 一类称呼。英语里的 <strong>submachine gun</strong> 这个词本身，是战后才逐渐固定下来的。</p><section><h2>为什么轻机枪和自动步枪仍然不足以满足需求<a href="#为什么轻机枪和自动步枪仍然不足以满足需求"><span>#</span></a></h2><p>步枪弹自动武器的射程与穿透力较强，却也带来较重的枪械、弹药和较大后坐力。壕沟突击与建筑争夺的距离可能只有几十米，远射性能的收益有限。使用较弱的手枪弹，可以减轻负担，提供更容易控制的近距离自动火力。</p></section><section><h2>Villar Perosa M1915（维拉尔-佩罗萨 M1915）：仍然像是一挺机枪<a href="#villar-perosa-m1915维拉尔-佩罗萨-m1915仍然像是一挺机枪"><span>#</span></a></h2><p>意大利 **Villar Perosa M1915（维拉尔-佩罗萨 M1915）**是这段发展史中最难归类、也最有意思的武器之一。</p><p>从机械条件看，它已经具备冲锋枪最核心的两项特征：</p><ul>
<li>使用 9 mm Glisenti 手枪弹；</li>
<li>可以进行全自动射击。</li>
</ul><p>但只要看一眼外形，就会发现它和后来 MP 18、汤普森或者 MP 40 完全不像同一类武器。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Villar-Perosa_M15--ff-9532c73489d7.mv8xhaci.webp" alt="Villar Perosa M1915 双联手枪弹自动武器" /><figcaption>Villar Perosa M1915 双联手枪弹自动武器</figcaption></figure><p></p><p>Villar Perosa 实际上把<strong>两套独立自动机构并排装在一起</strong>：</p><ul>
<li>两根枪管；</li>
<li>两个枪机；</li>
<li>两个顶部 25 发弹匣；</li>
<li>后方使用铲形握把；</li>
<li>可以安装护盾、支架或两脚架。</li>
</ul><p>Royal Armouries 对它的描述很准确：</p><p>从技术定义上，它可以被视为最早进入军用的手枪弹全自动武器之一；但从意军的实际使用方式看，它更像一挺<strong>超轻型、超短射程机枪</strong>，而不是一支真正意义上由单兵抵肩使用的冲锋枪。</p></section><section><h2>OVP Modello 1918：把 Villar Perosa“拆成一半”<a href="#ovp-modello-1918把-villar-perosa拆成一半"><span>#</span></a></h2><p><strong>OVP Modello 1918</strong> 将 Villar Perosa 的双联机构改为单管肩射武器。</p><p>意大利国家文化遗产目录对一支 1918 年生产的 OVP 实物，给出了清楚的说明：</p><p>它由 <strong>Villar Perosa M1915 的一套枪管—机匣组件</strong>改造而来，并增加新的击发机构与单发 / 连发选择。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Ovp_1918--ff-5b104f97d2d6.54yjy7h7g8.webp" alt="由 Villar Perosa 单侧机构发展而来的 OVP Modello 1918" /><figcaption>由 Villar Perosa 单侧机构发展而来的 OVP Modello 1918</figcaption></figure><p></p>

<table><thead><tr><th>Villar Perosa M1915</th><th>OVP Modello 1918</th></tr></thead><tbody><tr><td>两套自动机构并列</td><td>保留一套自动机构</td></tr><tr><td>双枪管</td><td>单枪管</td></tr><tr><td>两个顶部弹匣</td><td>一个顶部弹匣</td></tr><tr><td>铲形握把、支架式使用</td><td>木制枪托、单兵抵肩射击</td></tr><tr><td>更像超轻型机枪</td><td>更接近自动卡宾枪 / 冲锋枪</td></tr><tr><td>9 mm Glisenti</td><td>9 mm Glisenti</td></tr></tbody></table><p>意大利官方文物目录可以确认：现存 OVP 实物<strong>生产于 1918 年，用于军方航空任务</strong>；Royal Armouries 也明确记录了把 Villar Perosa 拆解、以单侧机构制造 OVP 的技术关系。</p><p>但关于 OVP 在 1918 年陆上前线的配发规模，可靠资料远不如 MP 18 那样明确。</p></section><section><h2>从 LP 08 “高容量手枪”，到 MP 18<a href="#从-lp-08-高容量手枪到-mp-18"><span>#</span></a></h2><p>德国研发冲锋枪的起点和其他国家并不完全相同。</p><p>1915 年，德国陆军进一步提出一种更加明确的需求：</p><p><strong>给突击部队制造一件手持、短射程、能够连续射击的自动武器。</strong></p><p>到 1918 年，这条路线产生了 <strong>Bergmann MP 18,I</strong>。</p></section><section><h2>MP 18：近乎完美的步兵近战武器<a href="#mp-18近乎完美的步兵近战武器"><span>#</span></a></h2><p>MP 18 使用 <strong>9×19 mm Parabellum</strong> 手枪弹。</p><p>最初的供弹装置，正是与 LP 08 同一体系的 <strong>32 发 Trommelmagazin 08 蜗形弹鼓</strong>。与 LP 08 相比，MP 18 可以全自动射击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Bergmann_MP18.1_submachine_gun_with_drum_magazine%2C_Theodor_Bergmann%2C_Suhl%2C_Germany%2C_1918-1920_AD_-_Braunschweigis--ff-16a0070b9420.1apsf8z998.webp" alt="Braunschweigisches Landesmuseum 收藏的 Bergmann MP 18,I，配 32 发蜗牛形弹鼓" /><figcaption>Braunschweigisches Landesmuseum 收藏的 Bergmann MP 18,I，配 32 发蜗牛形弹鼓</figcaption></figure><p></p><p>MP 18 的整体形态已经非常接近后来几十年的冲锋枪：</p><ul>
<li>单枪管；</li>
<li>木制肩托；</li>
<li>单兵携行；</li>
<li>手枪弹；</li>
<li>大容量供弹；</li>
<li>全自动；</li>
<li>专门强调近距离突击。</li>
</ul><p>不过，Royal Armouries 的记录显示，它在 <strong>1918 年 8 月</strong>才真正抵达前线，并主要配发给 Stosstruppen（突击部队）。</p><p>较晚的投入时间限制了 MP 18 对一战战局的影响。</p><section><h3>MP 18 相比其它“自动步枪”的优势<a href="#mp-18-相比其它自动步枪的优势"><span>#</span></a></h3><p>其中一个重要优势在于弹药。</p><p>全威力步枪弹膛压高、后坐力大，要想安全地进行自动射击，枪机通常不能在膛压很高时随意打开。</p><p>这往往意味着需要：</p><ul>
<li>闭锁机构；</li>
<li>导气系统；</li>
<li>后坐机构；</li>
<li>更坚固的机匣。</li>
</ul><p>手枪弹的能量较低，MP 18 因而可以采用相对简单的**自由枪机（blowback）**原理。</p><p>工作过程可以简单理解为：</p><blockquote><p>枪机在后方待击<br />
→ 扣动扳机<br />
→ 复进簧推动沉重枪机向前<br />
→ 枪机推弹入膛并击发<br />
→ 弹壳后坐力把枪机重新推回<br />
→ 抽壳、抛壳<br />
→ 只要扳机仍被扣住，就继续下一循环</p></blockquote><p>这种机构省去了旋转闭锁与导气系统，后来成为许多冲锋枪的机械基础。</p></section></section><section><h2>冲锋枪为什么不用步枪弹？<a href="#冲锋枪为什么不用步枪弹"><span>#</span></a></h2><p>BAR、Fedorov 与 MP 18 对弹药威力和自动火力作出了不同取舍：</p>

<table><thead><tr><th>路线</th><th>代表武器</th><th>弹药</th><th>得到什么</th><th>付出什么</th></tr></thead><tbody><tr><td>全威力自动步枪</td><td>BAR M1918</td><td>.30-06</td><td>射程、穿透和威力</td><td>枪重、弹重、全自动射击时后坐力较大</td></tr><tr><td>后坐力较小的自动步枪</td><td>Fedorov Avtomat</td><td>6.5×50 mmSR Arisaka</td><td>射程与自动射击可控性的折中</td><td>仍然比手枪弹武器更重、更复杂</td></tr><tr><td>冲锋枪</td><td>MP 18</td><td>9×19 mm</td><td>弹药轻、后坐力小、近距离连续火力</td><td>明显放弃中远距离性能</td></tr></tbody></table></section><section><h2>全自动“手枪”<a href="#全自动手枪"><span>#</span></a></h2><p>各国并非都选择重新设计一支肩射冲锋枪。</p><p>奥匈帝国还尝试过更加直接的办法：</p><p><strong>把现有军用半自动手枪改造成能够连发的武器。</strong></p><p>其中最典型的是 <strong>Repetierpistole M1912/P16</strong>——斯泰尔 M1912 连发手枪。</p><p>它使用 9×23 mm Steyr 弹，把标准 M1912 的固定弹仓加长，并配合枪托来帮助控制自动射击。</p><p>这种设计从外观和握持、操作方式上仍然更接近“装了枪托的大型手枪”，而不是后来标准冲锋枪。</p><p>因此，它没有成为后来冲锋枪最主流的形态。</p><blockquote><p><strong>1915—1918 年间，各国并不知道未来的冲锋枪一定要长成 MP 18 那样。</strong></p></blockquote>

<table><thead><tr><th>武器</th><th>国家</th><th>形态</th><th>弹药</th><th>一战中的意义</th></tr></thead><tbody><tr><td>LP 08</td><td>德国</td><td>带枪托高容量半自动手枪</td><td>9×19 mm</td><td>把手枪弹、枪托和 32 发弹鼓组合起来，但仍不能全自动</td></tr><tr><td>Repetierpistole M1912/P16</td><td>奥匈</td><td>全自动手枪</td><td>9×23 mm Steyr</td><td>直接把现役手枪推向全自动，是“机器手枪”路线的代表</td></tr><tr><td>Villar Perosa M1915</td><td>意大利</td><td>双联短程自动武器</td><td>9 mm Glisenti</td><td>手枪弹已经获得全自动，但仍按超轻型机枪的方式使用</td></tr><tr><td>OVP Modello 1918</td><td>意大利</td><td>单管肩射自动武器</td><td>9 mm Glisenti</td><td>把 Villar Perosa 的一半装进木制枪托，冲锋枪形态更加清楚</td></tr><tr><td>MP 18,I</td><td>德国</td><td>单兵肩射冲锋枪</td><td>9×19 mm</td><td>1918 年明确投入突击部队，形成后来最熟悉的冲锋枪基本组合</td></tr></tbody></table><hr /></section></section>
<section><h1>火炮：改变堑壕战格局的武器<a href="#火炮改变堑壕战格局的武器"><span>#</span></a></h1><p>早期野战炮每次射击都会因为后坐力整门向后跳动。炮手必须重新把炮推回位置，再重新校正方向与高低，然后才能继续射击。即使装填速度已经很快，真正的射速仍然被“复位和重新瞄准”限制。</p><p><strong>法国75炮</strong>的历史地位，正是从解决这个问题开始。</p><section><h2>Canon de 75 modèle 1897（M1897式75毫米野战炮，俗称“法国75炮”）：炮架不再受后坐影响<a href="#canon-de-75-modèle-1897m1897式75毫米野战炮俗称法国75炮炮架不再受后坐影响"><span>#</span></a></h2><p>法国 75 炮最著名的技术，是<strong>液气式后坐复进系统</strong>。射击时，真正向后运动的是炮管，而不是整套炮架。</p><p>炮管的后坐推动内部活塞，使液体通过狭窄通道流动并压缩气体，从而逐步吸收后坐能量；随后压缩气体又把炮管推回原位。</p><p>这意味着炮手不需要每打一发都重新把整门炮推回原位，再重新瞄准。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/French_75_mm_field_gun_and_limber_-_Imperial_War_Museum_1--ff-a51bb2150a1f.szqqnxvrl.webp" alt="法国 M1897式75毫米野战炮（法国75炮）" /><figcaption>法国 M1897式75毫米野战炮（法国75炮）</figcaption></figure><p></p><p>再配合快速炮闩、定装弹药、炮盾和瞄准装置，法国 75 炮可以达到当时非常惊人的射速。</p><p>1897 年定型时，这套组合是革命性的。到 1914 年，法国陆军已经高度依赖 75 炮，并一度相信机动、快速、准确的 75 mm 炮火足以满足大部分野战需要。</p><p>75 mm 炮弹相对较轻，弹道也偏平直。在对付暴露步兵、机枪、道路和浅层阵地时非常有效，可面对深掩体、混凝土工事和反斜面目标，就需要更大口径、弹道弧度更大的榴弹炮。</p></section><section><h2>Ordnance QF 18-pounder（QF 18磅野战炮）：可靠的数量与性能<a href="#ordnance-qf-18-pounderqf-18磅野战炮可靠的数量与性能"><span>#</span></a></h2><p>英国陆军的对应主力是 <strong>QF 18磅野战炮</strong>。</p><div><div><div></div><div>Note</div></div><div><p><strong>火炮名称中的重量与口径</strong></p><p>“18 磅”表示弹丸的标称重量，约合 8.2 千克。法国“75 毫米炮”则以炮管口径命名。</p></div></div><p>18 磅炮是一战英军最主要的野战炮之一。</p><p>1916 年索姆河战役期间，英军发射的炮弹数量以千万计，其中很大一部分就是由 18 磅炮打出的。它既可以发射榴霰弹，也逐渐更多使用高爆弹和烟幕弹。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/QF_18-pounder_Valcartier_Quebec_1914--ff-b78d4852e2e4.1ht0aoleyv.webp" alt="1914 年加拿大瓦尔卡捷营地的 QF 18磅炮 野战炮" /><figcaption>1914 年加拿大瓦尔卡捷营地的 QF 18磅炮 野战炮</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/QF_18_Pounder_at_Collins_Barracks--ff-e23d76d67caa.73uqojmps6.webp" alt="都柏林 Collins Barracks 馆藏的 QF 18磅炮 野战炮" /><figcaption>都柏林 Collins Barracks 馆藏的 QF 18磅炮 野战炮</figcaption></figure></div></section><section><h2>7.7 cm FK 96 n.A.（7.7厘米 FK 96 n.A. 野战炮）：同一代野战炮的另一条路线<a href="#77-cm-fk-96-na77厘米-fk-96-na-野战炮同一代野战炮的另一条路线"><span>#</span></a></h2><p>德军主要野战炮之一是 <strong>7.7厘米 FK 96 n.A. 野战炮</strong>，通常简称 <strong>FK 96 n.A.</strong></p><p>它同样属于 20 世纪初新一代速射野战炮，采用后坐制退装置和炮盾。</p><p>德军将这种野战炮与榴弹炮、中重型火炮配合使用，分别承担野战支援与工事破坏等任务。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/7.7_cm_FK_96_n.A._With_Ammunition--ff-d059ebd5958e.3gp710qwto.webp" alt="1918 年摄影资料中的德国 7.7 cm FK 96 n.A. 野战炮与弹药" /><figcaption>1918 年摄影资料中的德国 7.7 cm FK 96 n.A. 野战炮与弹药</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/7.7_cm_FK_96_n.A._Battlefields_Park_01--ff-84ee8ded6d4a.pg4sy4tee.webp" alt="Battlefields Park 展出的 7.7 cm FK 96 n.A. 野战炮" /><figcaption>Battlefields Park 展出的 7.7 cm FK 96 n.A. 野战炮</figcaption></figure></div></section><section><h2>当 75 mm 不再够用：榴弹炮与重炮<a href="#当-75-mm-不再够用榴弹炮与重炮"><span>#</span></a></h2><p>僵持的战壕战越来越需要使用更大口径的火炮和弧度更大的弹道来打开局面。</p><p>榴弹炮能够以更高的射角，把炮弹送到胸墙、建筑或地形遮蔽物后面；大口径高爆弹则更有可能摧毁深掩体、混凝土结构和铁丝网。</p><p>英国的 <strong>9.2 英寸榴弹炮</strong>就是典型代表之一。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/9.2inchHowitzer91stBatteryRGAArras1April1917--ff-88b7cc29d9f0.6poaxoeegr.webp" alt="1917 年阿拉斯战役前，英军 9.2 英寸榴弹炮隐藏在伪装网下" /><figcaption>1917 年阿拉斯战役前，英军 9.2 英寸榴弹炮隐藏在伪装网下</figcaption></figure><p></p><p>德国也拥有强大的重炮基础。</p><p>15 cm sFH 系列重型榴弹炮、21 cm Mörser 等武器，比普通野战炮更重、转移更慢，对运输和弹药供应要求更高，却能把更重的炮弹砸向坚固阵地。</p><p>重炮还需要铁路、马匹或牵引车运输，配套弹药仓储、测距、观察、通信与维修。口径增加通常也会扩大后勤负担。</p></section><section><h2>炮弹技术的变革：榴霰弹、高爆弹与引信<a href="#炮弹技术的变革榴霰弹高爆弹与引信"><span>#</span></a></h2><p>火炮对目标的毁伤还取决于弹种与起爆时机。</p><p>一战野战炮最值得区分的两种弹药，就是<strong>榴霰弹（shrapnel shell）<strong>与</strong>高爆弹（high-explosive shell）</strong>。</p><p>今天，“shrapnel”在中文里常被笼统地译为爆炸产生的破片，但历史上的榴霰弹是一种结构非常具体的炮弹。</p><section><h3>榴霰弹：大炮也可以发射霰弹<a href="#榴霰弹大炮也可以发射霰弹"><span>#</span></a></h3><p>一枚典型榴霰弹内部装有大量铅制或硬质金属弹丸。</p><p>炮弹接近目标上空时，时间引信引爆少量装药，把内部弹丸从弹体前方高速推出。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/18pdrShrapnelDiagram1--ff-ca74ab8f8f13.9ddr817fsz.webp" alt="1915 年英军 18磅炮 Mk VI 榴霰弹结构图，可见内部大量弹丸" /><figcaption>1915 年英军 18磅炮 Mk VI 榴霰弹结构图，可见内部大量弹丸</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/18pdrHEShellDiagram--ff-caaf8d2a40c0.7p4eauh5mq.webp" alt="1916 年英军 18磅炮 高爆弹结构图" /><figcaption>1916 年英军 18磅炮 高爆弹结构图</figcaption></figure></div><p>在战前机动作战设想中，榴霰弹非常理想：</p><p>敌军步兵、骑兵和炮手如果暴露在地面上，一枚在正确位置和高度起爆的榴霰弹能够覆盖相当大的区域。</p><p>可堑壕战改变了目标的防护条件。</p><p>士兵开始躲进深壕和地下掩蔽部，或藏在沙袋胸墙、混凝土工事后面。从上方散落的弹丸，已经不容易杀伤他们。</p></section><section><h3>高爆弹：利用爆炸摧毁目标<a href="#高爆弹利用爆炸摧毁目标"><span>#</span></a></h3><p>高爆弹内部装有更多炸药。爆炸时，炸药的冲击和弹体破裂共同产生毁伤。</p><p>它更适合：</p><ul>
<li>炸断铁丝网；</li>
<li>摧毁胸墙；</li>
<li>破坏建筑；</li>
<li>攻击炮位；</li>
<li>震塌掩体；</li>
<li>把地面炸成弹坑。</li>
</ul><p>随着西线工事越来越坚固，高爆弹的重要性不断上升。</p><p>炮兵必须根据任务目的选择弹种，正确的弹种使用可以大大增加大炮的效率。</p></section><section><h3>引信：决定炸弹的爆炸时机<a href="#引信决定炸弹的爆炸时机"><span>#</span></a></h3><p>炮弹内部装什么是一回事，<strong>什么时候爆炸</strong>又是另一回事，并不是所有炮弹都必须要在接触目标的一瞬间引爆。</p><p>一战中常见的引信类型包括：</p><ul>
<li><strong>时间引信</strong>：在预定飞行时间后起爆，特别适合榴霰弹空爆；</li>
<li><strong>碰炸引信</strong>：撞击目标后起爆；</li>
<li><strong>延迟引信</strong>：让炮弹先钻入土层、建筑或工事再爆炸；</li>
<li><strong>擦发 / 惯性引信</strong>：利用突然减速启动起爆机构。</li>
</ul></section></section><section><h2>超重炮：极端的射程未必实用<a href="#超重炮极端的射程未必实用"><span>#</span></a></h2><p>一战还催生了许多令人印象极深的超重型火炮。</p><p>德国 420 mm M-Gerät 因为攻城战而获得“大贝尔莎”的名声；更极端的“巴黎炮”则在 1918 年从一百多公里外炮击巴黎。</p><p>超重炮通常意味着：</p><ul>
<li>极其复杂的运输；</li>
<li>很低的射速；</li>
<li>昂贵的炮弹；</li>
<li>炮管快速磨损；</li>
<li>繁重的部署准备工作；</li>
<li>很难快速转移。</li>
</ul><p>这类武器数量有限，适合特殊攻城或远程炮击任务，无法取代更常见的野战炮与榴弹炮。</p><hr /></section></section>
<section><h1>手榴弹、霰弹枪、迫击炮：为堑壕战而生<a href="#手榴弹霰弹枪迫击炮为堑壕战而生"><span>#</span></a></h1><p>一支长步枪在开阔地很好用。</p><p>可当敌人就在下一道壕沟拐角后面时，“射程几百米”不再是最重要的性能。</p><p>你需要的是：</p><ul>
<li>能扔过胸墙的爆炸物；</li>
<li>能把炮弹以高弧线送进沟里的短程火炮；</li>
<li>能在狭窄空间快速使用的武器；</li>
<li>能跟着突击小组一起移动的火力。</li>
</ul><p>第一次世界大战因此让许多原本边缘化的武器重新成为主角。</p><section><h2>Mills Bomb（米尔斯手榴弹）：现代手榴弹的经典造型<a href="#mills-bomb米尔斯手榴弹现代手榴弹的经典造型"><span>#</span></a></h2><p>英国 <strong>No. 5 米尔斯手榴弹</strong>于 1915 年开始装备部队。</p><p>今天看到它的外形，几乎立刻就会联想到“经典手榴弹”：</p><ul>
<li>卵形铸铁弹体；</li>
<li>外壳上有明显分格；</li>
<li>一根压在弹体侧面的保险杆；</li>
<li>顶部保险销和拉环。</li>
</ul><p>它的操作方式，也成了后来常见的手榴弹操作方式。</p><p>拔出保险销以后，只要射手仍然用手握住保险杆，引信就不会启动；投出后，保险杆弹开，击针点燃延时引信，数秒后起爆。</p><p>与早期那些需要点火、撞击或复杂操作的手榴弹相比，这种结构更适合紧张的战壕环境。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/11th_Royal_Scots_raiding_party_12-07-1918--ff-512118dae147.icwxiinct.webp" alt="1918 年 7 月，皇家苏格兰团突击队领取米尔斯手榴弹" /><figcaption>1918 年 7 月，皇家苏格兰团突击队领取米尔斯手榴弹</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mills_bomb_Mk_I-997.493-IMG_7525-white--ff-8c4e056964a2.7ehkhp1xwx.webp" alt="巴黎陆军博物馆收藏的英国 Mills Bomb Mk I（米尔斯手榴弹）" /><figcaption>巴黎陆军博物馆收藏的英国 Mills Bomb Mk I（米尔斯手榴弹）</figcaption></figure></div></section><section><h2>Stielhandgranate（木柄手榴弹）：手榴弹的另一种形象<a href="#stielhandgranate木柄手榴弹手榴弹的另一种形象"><span>#</span></a></h2><p>德国最具辨识度的手榴弹，则是 <strong>木柄手榴弹</strong>。</p><p>长木柄让它拥有完全不同的外形，因此后来获得“土豆捣碎器”的绰号。</p><p>它的思路与米尔斯手榴弹并不相同。</p><p>木柄不仅方便握持，还增加了投掷时的杠杆长度，使士兵能够把手榴弹扔得更远。早期型号和一战中常见的型号，主要依靠爆炸冲击杀伤，而不像米尔斯手榴弹那样强调破片杀伤。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Stielhandgranate17--ff-e1dc0e0ab77e.5j4zp2pied.webp" alt="原装1917型德国木柄手榴弹" /><figcaption>原装1917型德国木柄手榴弹</figcaption></figure><p></p><p>两种武器都很成功，却反映出不同的设计取向：</p>

<table><thead><tr><th>武器</th><th>设计重点</th><th>典型特点</th></tr></thead><tbody><tr><td>米尔斯手榴弹</td><td>紧凑、破片杀伤、单手操作</td><td>体积小，便于携带较多枚</td></tr><tr><td>木柄手榴弹</td><td>投掷距离与爆炸效应</td><td>木柄便于远投，外形醒目</td></tr></tbody></table></section><section><h2>Winchester Model 1897（温彻斯特 M1897）：高效扫荡战壕的武器<a href="#winchester-model-1897温彻斯特-m1897高效扫荡战壕的武器"><span>#</span></a></h2><p>美军进入西线以后，还把一种非常具有美国特色的武器带进了战壕：</p><p><strong>12 号口径泵动霰弹枪。</strong></p><p>其中最著名的是 <strong>Winchester Model 1897 Trench Gun（温彻斯特 M1897 堑壕霰弹枪）</strong>。</p><div><div><div></div><div>Note</div></div><div><p><strong>“12 号”的编号方式</strong></p><p>霰弹枪的“号”（gauge）采用传统的铅球计量法：12 颗直径与枪膛相同的铅球合重一磅，便对应 12 号口径。按这套编号，号数越小，枪膛直径越大。</p></div></div><p>霰弹枪用于夜间巡逻、战壕突袭和地下掩体争夺等近距离任务。</p><p>军用堑壕型 M1897 通常具有较短枪管，并增加了带刺刀座的护罩结构，可以安装刺刀。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/M1897_with_bayonet--ff-be2fc34c819b.99u5abedgm.webp" alt="美国国家枪械博物馆收藏、安装刺刀的 Winchester Model 1897 堑壕霰弹枪" /><figcaption>美国国家枪械博物馆收藏、安装刺刀的 Winchester Model 1897 堑壕霰弹枪</figcaption></figure><p></p><p>它使用鹿弹时，一次射击会射出多枚较大的弹丸。</p><p>霰弹在近距离的散布仍然有限。</p><p>真正的优势是：</p><ul>
<li>极近距离命中概率高；</li>
<li>泵动结构便于快速退壳和装填，接着进行下一次射击；</li>
<li>多枚鹿弹能在狭窄空间形成很强的瞬间杀伤；</li>
<li>武器比长步枪更适合部分近距离任务。</li>
</ul><p>美国还使用过 **Winchester Model 1912（温彻斯特 M1912）**和 **Remington Model 10（雷明顿 M10）**等霰弹枪。</p><div><div><div></div><div>Note</div></div><div><p><strong>一支霰弹枪甚至引发了正式外交抗议。</strong></p><p>1918 年 9 月，德国政府通过瑞士向美国正式抗议美军使用霰弹枪，声称这种武器违反战争法，并威胁对携带霰弹枪或相关弹药的被俘美军人员采取报复措施。</p><p>美国政府在 9 月 28 日的正式答复中拒绝这一主张，认为霰弹枪并未被海牙公约禁止，并明确表示不会停止使用；如果德国真的执行针对战俘的威胁，美国也将采取报复措施。</p></div></div></section><section><h2>Stokes mortar（斯托克斯迫击炮）：一根“管子”也可以发射炮弹<a href="#stokes-mortar斯托克斯迫击炮一根管子也可以发射炮弹"><span>#</span></a></h2><p>堑壕战还让迫击炮重新变得重要。</p><p>传统火炮太重，也未必能在最前沿找到合适射界；手榴弹又扔不了太远。</p><p>英国工程师 <strong>Wilfred Stokes</strong> 设计的 3 英寸迫击炮，最大的优点之一就是结构极其简单。</p><p>它主要由三部分组成：</p><ul>
<li>一根滑膛炮管；</li>
<li>底板；</li>
<li>两脚架。</li>
</ul><p>炮管底部固定着击针。炮手把迫击炮弹从炮口放进去，炮弹沿炮管滑到底部，底火撞上击针后自动点燃发射药，炮弹随即飞出。</p><p>也就是说，炮手不需要像普通火炮那样反复开闩、装填、闭闩。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/The_Portuguese_Expeditionary_Corps_on_the_Western_Front%2C_1917-1918_Q6440--ff-ce12dd187f3e.5molmsil4c.webp" alt="一战时期的3英寸斯托克斯迫击炮" /><figcaption>一战时期的3英寸斯托克斯迫击炮</figcaption></figure><p></p><p>这种结构后来成为现代迫击炮最经典的技术路线之一。</p><p>它的优势非常适合堑壕战：</p><ul>
<li>可以打出高弧弹道；</li>
<li>能越过己方胸墙；</li>
<li>能攻击敌方沟壕和反斜面；</li>
<li>比重炮轻得多；</li>
<li>可以相对靠近前线部署；</li>
<li>射速高。</li>
</ul></section><section><h2>7.6 cm leichter Minenwerfer（7.6厘米轻型掷雷器）：德国人的“壕沟小炮”<a href="#76-cm-leichter-minenwerfer76厘米轻型掷雷器德国人的壕沟小炮"><span>#</span></a></h2><p>如果说斯托克斯迫击炮代表的是“越简单越好”，德国的 <strong>7.6厘米轻型掷雷器</strong> 则展示了另一种路线。</p><p>德语 Minenwerfer 可直译为“投雷器”或“掷雷器”。德国在一战前就受到日俄战争攻城经验影响，发展出一系列用于近距离攻击工事的曲射武器。</p><p>最轻的 7.6 cm 型并不像 Stokes 那样只用一根滑膛炮管构成主体。</p><p>它更像一门缩小的、专门为战壕准备的火炮：</p><ul>
<li>带膛线的炮管；</li>
<li>后膛装填；</li>
<li>后坐缓冲机构；</li>
<li>金属底座；</li>
<li>可调方向和高低角。</li>
</ul><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/German_7.58_cm_minenwerfer--ff-db3a5cfb812a.58i5vxa9zy.webp" alt="布鲁塞尔军事博物馆收藏的德国 7.58 cm leichter 掷雷器" /><figcaption>布鲁塞尔军事博物馆收藏的德国 7.58 cm leichter 掷雷器</figcaption></figure><p></p><p>1916 年的新型 **n.A.（neuer Art，新式）**进一步简化了结构并减轻了重量，并使用能够旋转的底座。</p><p>战争后期，一些掷雷器甚至可以采用平射弹道射击，可以直接瞄准目标——包括坦克。</p><p>这和 Stokes 形成了鲜明的技术对照：</p>

<table><thead><tr><th>特点</th><th>Stokes 3-inch mortar</th><th>7.6厘米轻型掷雷器</th></tr></thead><tbody><tr><td>炮管</td><td>滑膛</td><td>膛线</td></tr><tr><td>装填</td><td>炮口装填</td><td>后膛装填</td></tr><tr><td>击发</td><td>炮弹落底撞击击针</td><td>更接近传统火炮机构</td></tr><tr><td>结构</td><td>极简</td><td>相对复杂</td></tr><tr><td>优势</td><td>重量轻、操作快、射速高</td><td>瞄准与弹道控制更精细</td></tr><tr><td>长期影响</td><td>成为现代迫击炮主流结构</td><td>代表一战德军专业堑壕炮路线</td></tr></tbody></table></section><section><h2>火焰喷射器：极端近距离的武器<a href="#火焰喷射器极端近距离的武器"><span>#</span></a></h2><p>德国在一战中率先较系统地使用现代军用火焰喷射器。</p><p>它通常由储存燃料和压缩气体的容器、软管和喷枪组成，把燃烧液体喷向敌方阵地。</p><p>火焰喷射器很适合攻击：</p><ul>
<li>掩体入口；</li>
<li>壕沟转角；</li>
<li>建筑物；</li>
<li>极近距离坚固据点。</li>
</ul><p>但它也有明显局限：</p><p>射程有限，装置沉重，燃料危险，操作人员又很容易成为优先射击目标。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/A_squad_with_Double_Kleif_M.1915_Flammenwerfer--ff-c025108365c5.1e9ecysbwt.webp" alt="1916 年德军 Double Kleif M.1915 火焰喷射器小组" /><figcaption>1916 年德军 Double Kleif M.1915 火焰喷射器小组</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/111-SC-2076_-_Chemical_Warfare_Service_-_German_flamethrower_or_flame_projector_-_NARA_-_55165505--ff-dabaa5e04e2b.6m4ozylbse.webp" alt="美国档案中的德国火焰喷射器实物照片" /><figcaption>美国档案中的德国火焰喷射器实物照片</figcaption></figure></div></section><section><h2>堑壕刀与棍棒：工业化战争并没有让近身格斗消失<a href="#堑壕刀与棍棒工业化战争并没有让近身格斗消失"><span>#</span></a></h2><p>如果战场只沿着“射得更远、火力更大”的方向发展，那么刀具和棍棒似乎应该越来越没有用武之地。</p><p>堑壕战却制造了相反的需求。</p><p>夜间巡逻、战壕突袭、掩体争夺和狭窄交通壕里的战斗，经常让士兵在相隔几步的距离内交战。步枪太长，刺刀未必来得及派上用场；开枪还可能暴露位置。</p><p>于是，一战前线重新出现了大量极其直接的近战工具。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/U.S._M1917_trench_knife--ff-dae116f02971.4qs47c8wp9.webp" alt="美国 Fort Lewis 军事博物馆收藏的 M1917 堑壕刀" /><figcaption>美国 Fort Lewis 军事博物馆收藏的 M1917 堑壕刀</figcaption></figure><p></p><p>美军 **M1917 trench knife（M1917 堑壕刀）**几乎就是一件纯粹为了格斗而设计的武器，带有明显的护拳结构。</p><p>各国前线还出现过：</p><ul>
<li>私购匕首；</li>
<li>改造刺刀；</li>
<li>工兵铲；</li>
<li>自制堑壕棍棒。</li>
</ul><hr /></section></section>
<section><h1>化学武器：效果拔群，但又极具争议<a href="#化学武器效果拔群但又极具争议"><span>#</span></a></h1><p>在人们对一战的记忆中，毒气占有突出的位置。</p><p>但如果从武器技术角度看，真正值得注意的不只是氯气、光气和芥子气本身，还有<strong>投放方式的变化</strong>。</p><section><h2>1915：风本身就是武器系统的一部分<a href="#1915风本身就是武器系统的一部分"><span>#</span></a></h2><p>1915 年第二次伊普尔战役中，德军大规模使用氯气时，主要方法是把装有液化氯气的钢瓶搬到前线，打开阀门，让气体随风飘向敌军阵地。</p><p>这显然存在巨大限制。</p><p>攻击方必须等待：</p><ul>
<li>有利的风向；</li>
<li>合适的风速；</li>
<li>稳定的天气。</li>
</ul><p>一旦风向变化，毒气甚至可能吹回自己阵地。</p></section><section><h2>化学炮弹：让毒气不再只能“随风放出去”<a href="#化学炮弹让毒气不再只能随风放出去"><span>#</span></a></h2><p>随着战争发展，各国越来越多地使用火炮、迫击炮等方式投放化学战剂。</p><p>这使毒气从“大面积云团”逐渐成为炮兵可以按作战计划投放的一类特殊弹药。</p><p>不同毒剂用途也有所不同。</p>

<table><thead><tr><th>化学战剂</th><th>大致特点</th><th>战术意义</th></tr></thead><tbody><tr><td>氯气</td><td>早期大规模使用，刺激性明显</td><td>制造突然恐慌，但较容易被察觉</td></tr><tr><td>光气</td><td>毒性更高，症状可能延迟</td><td>防护失误时危险更大</td></tr><tr><td>芥子气</td><td>伤害皮肤、眼睛与呼吸道，污染持续时间长</td><td>适合污染阵地、道路和集结区</td></tr></tbody></table><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/12th_Royal_Scots_in_gas_masks_25-06-1918--ff-5d403477f86d.7i16fev074.webp" alt="1918 年梅特伦前线佩戴呼吸器的皇家苏格兰团士兵" /><figcaption>1918 年梅特伦前线佩戴呼吸器的皇家苏格兰团士兵</figcaption></figure><p></p><p>防护也在同步进化。</p><p>从早期临时浸湿的布料和简易头罩，到更成熟的滤毒罐、防毒面具和警报制度，防护手段不断完善。毒气攻击越来越难以重现初次大规模使用时，守军毫无防备的局面。</p></section><section><h2>Livens Projector（利文斯投射器）：“一排铁管”埋进地里，也能成为武器<a href="#livens-projector利文斯投射器一排铁管埋进地里也能成为武器"><span>#</span></a></h2><p>气瓶释放毒气最大的问题，是太依赖风。</p><p>普通化学炮弹虽然更加灵活，但单发炮弹能够携带的毒剂有限。</p><p>英国工程师 William Livens 发展出的 <strong>利文斯投射器</strong>，提供了一种介于两者之间的投放方案。</p><p>它本质上不是一门精密火炮。</p><p>标准投射器就是一根口径约 8 英寸的金属管，以约 45° 的倾角埋入地面，底部放置发射药，上方装入一枚圆筒形毒剂弹。</p><p>大量投射器会被成排埋设，再用电路同时点火。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Livens_gas_projector_loading--ff-c6ec466a508b.7p4eauh5zl.webp" alt="一战期间英军士兵为一组 利文斯投射器 装填毒气弹" /><figcaption>一战期间英军士兵为一组 利文斯投射器 装填毒气弹</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/CWM_-_Livens_Projector--ff-762b809082f5.9o0l16mo65.webp" alt="1917 年加拿大部队使用的 利文斯投射器 阵地" /><figcaption>1917 年加拿大部队使用的 利文斯投射器 阵地</figcaption></figure></div><p>它的设计思路非常直接：</p><blockquote><p>不追求一根炮管反复精确射击，而是一次埋下几十甚至上百根廉价发射管，在同一瞬间把大量毒剂容器抛进一个区域。</p></blockquote><p>这样可以在很短时间内制造高浓度毒气云，同时减少传统气瓶对风向的依赖。</p><p>缺点同样明显：</p><ul>
<li>投射器需要事先搬运和埋设；</li>
<li>阵地准备工作繁重；</li>
<li>发射后难以迅速装填并再次射击；</li>
<li>一旦准备工作被发现，就可能遭到炮击。</li>
</ul><hr /></section></section>
<section><h1>飞机：从“会飞的观察台”，到真正意义上的战斗机<a href="#飞机从会飞的观察台到真正意义上的战斗机"><span>#</span></a></h1><p>1903 年莱特兄弟完成动力飞行时，距离一战爆发只有十一年。</p><p>因此，1914 年的飞机仍是一种刚刚起步的新技术。</p><p>战争初期，飞机主要用于侦察：观察敌军行军路线、寻找炮兵位置、拍摄壕沟并校正炮弹落点。</p><p>侦察机一旦开始影响地面战斗，一个新问题便随之出现：</p><blockquote><p>如果敌人的飞机正在观察我，怎样把它赶走？</p></blockquote><p>于是，一战只用了几年时间，就推动了专用战斗机及其配套技术的发展。</p><section><h2>B.E.2：稳定是优点，也是缺点<a href="#be2稳定是优点也是缺点"><span>#</span></a></h2><p>英国 Royal Aircraft Factory（皇家飞机制造厂）的 <strong>B.E.2</strong> 系列是一战早期典型侦察机。</p><p>它的设计强调稳定。</p><p>对于航空摄影、观察地形和炮兵校射来说，稳定非常重要——观察员不希望飞机每时每刻都在剧烈摆动。</p><p>可空战兴起以后，同样的特点却变成缺陷。</p><p>飞机越稳定，通常也意味着越不灵活。面对机动性越来越强的专用战斗机，B.E.2 这类侦察机开始显得缓慢而脆弱。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Operational_B.E.2c--ff-cd1bd6cc9452.232nwzfv93.webp" alt="1916—1917 年英国皇家飞行队正在服役的 B.E.2c 侦察机" /><figcaption>1916—1917 年英国皇家飞行队正在服役的 B.E.2c 侦察机</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Royal_aircraft_factory_BE2c_at_the_Imperial_War_Museum--ff-a5a828c5f03a.4922ir7j1n.webp" alt="伦敦帝国战争博物馆收藏的 皇家飞机制造厂 B.E.2c" /><figcaption>伦敦帝国战争博物馆收藏的 皇家飞机制造厂 B.E.2c</figcaption></figure></div></section><section><h2>Fokker Eindecker（福克 E 单翼战斗机）：让子弹穿过螺旋桨<a href="#fokker-eindecker福克-e-单翼战斗机让子弹穿过螺旋桨"><span>#</span></a></h2><p>早期飞行员很快发现，最自然的射击方式就是：</p><p><strong>机头指向哪里，子弹就打向哪里。</strong></p><p>但单发牵引式飞机的机头前面有一个高速旋转的螺旋桨。</p><p>如果直接把机枪装在机头，子弹很可能先把自己的桨叶打断。</p><p>各国尝试过很多办法：</p><ul>
<li>把机枪安装在上翼，让弹道越过螺旋桨；</li>
<li>使用后置螺旋桨的“推式”飞机；</li>
<li>给桨叶安装金属偏导片；</li>
<li>让射手手动避开螺旋桨位置。</li>
</ul><p>真正关键的突破，是<strong>同步射击装置</strong>。它通过机械机构把发动机转速与机枪击发联系起来，让机枪只在螺旋桨叶片没有挡在枪口前方的瞬间开火。</p><p>1915 年，福克 E 单翼战斗机把这一技术真正投入实战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Fokker_E.III_Eindecker_%28210-16%29_%2819036385131%29--ff-d57fa8fa2963.1zj1z9mscr.webp" alt="保存至今的 Fokker E.III Eindecker（福克 E.III 单翼战斗机）" /><figcaption>保存至今的 Fokker E.III Eindecker（福克 E.III 单翼战斗机）</figcaption></figure><p></p><p>同步射击此前已有探索，福克则把实用同步机枪与飞机结合，形成了可供前线使用的系统。</p><p>1915 年下半年，装备同步机枪的福克战斗机给协约国侦察机造成很大压力，这一阶段后来常被称为“Fokker Scourge”（“福克灾难”）。</p><p>但这种优势并没有维持太久。</p></section><section><h2>Albatros（“信天翁”）、Sopwith Camel（索普威思“骆驼”）与 SPAD XIII（斯帕德 XIII）：一挺枪不够，那就再来一挺<a href="#albatros信天翁sopwith-camel索普威思骆驼与-spad-xiii斯帕德-xiii一挺枪不够那就再来一挺"><span>#</span></a></h2><p>战斗机发展速度极快。</p><p>1916 年以后，双方飞机开始：</p><ul>
<li>使用功率更大的发动机；</li>
<li>改进机翼结构；</li>
<li>增加同步机枪数量；</li>
<li>提高爬升率和速度；</li>
<li>强化机身。</li>
</ul><p>德国 <strong>“信天翁”D系列</strong>的重要特点之一，就是把性能较强的水冷直列发动机与双同步机枪结合起来。</p><p>英国 <strong>索普威思“骆驼”</strong> 则代表另一种路线。它使用旋转式发动机，机身短，重心集中，拥有极强的转弯能力。技术熟练的飞行员可以利用这种灵敏性迅速改变方向；可对于经验不足者，同样的操纵特性会非常危险。</p><div><div><div></div><div>Note</div></div><div><p><strong>旋转式发动机究竟哪里在转？</strong></p><p>这类发动机的曲轴固定在机体上，汽缸组与螺旋桨一起旋转，转动有助于汽缸散热。旋转的发动机也会产生陀螺效应，增加飞行员改变飞机姿态时的操纵负担。</p></div></div><p>“骆驼”的机头上并列安装两挺维克斯同步机枪，外部整流罩形成一个明显隆起，因此得名“骆驼”。</p><p>法国 <strong>斯帕德 XIII</strong> 则更强调速度、坚固结构和俯冲性能。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/1917_Sopwith_F.1_Camel--ff-6b446ce668ff.6m4ozylbug.webp" alt="1917 年投入使用的 Sopwith F.1 Camel（索普威思“骆驼”战斗机）" /><figcaption>1917 年投入使用的 Sopwith F.1 Camel（索普威思“骆驼”战斗机）</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/SPAD_XIII_USAF--ff-2a03a940abdc.szqqnxw0y.webp" alt="法国 斯帕德 XIII 战斗机" /><figcaption>法国 斯帕德 XIII 战斗机</figcaption></figure></div><p>到 1917—1918 年，一战战斗机已经从“给侦察机随便装一挺枪”发展为专门围绕空战设计的武器平台。</p>

<table><thead><tr><th>飞机</th><th>典型阶段</th><th>武器技术上的意义</th></tr></thead><tbody><tr><td>B.E.2</td><td>战争早期</td><td>稳定的侦察与炮兵观察平台</td></tr><tr><td>福克 E 单翼战斗机</td><td>1915</td><td>实用同步前射机枪推动专用战斗机出现</td></tr><tr><td>“信天翁”D系列</td><td>1916—1917</td><td>双同步机枪与更强发动机成为新标准</td></tr><tr><td>索普威思“骆驼”</td><td>1917—1918</td><td>双维克斯机枪、极强机动性</td></tr><tr><td>斯帕德 XIII</td><td>1917—1918</td><td>高速、坚固、双机枪，协约国后期重要战斗机</td></tr></tbody></table></section><section><h2>Lewis（刘易斯机枪）+ Scarff ring（斯卡夫环形枪架）：让后座观察员也能进行火力输出<a href="#lewis刘易斯机枪-scarff-ring斯卡夫环形枪架让后座观察员也能进行火力输出"><span>#</span></a></h2><p>双座飞机的后座观察员无法随时要求飞行员转向配合射击，需要一挺能独立改变朝向的机枪。</p><p>英国很常见的解决方案，是把 <strong>刘易斯机枪</strong>安装在 <strong>斯卡夫环形枪架</strong>上。环形枪架允许机枪沿机舱周围转动，并通过平衡结构帮助射手抬高或压低枪口。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/The_Royal_Flying_Corps_on_the_Western_Front%2C_1914-1918_Q9973--ff-f5821eb203a0.96ajcllawd.webp" alt="1918 年 9 月 Handley Page（汉德利·佩季）轰炸机前舱观察员使用安装在 斯卡夫环形枪架上的 刘易斯机枪" /><figcaption>1918 年 9 月 Handley Page（汉德利·佩季）轰炸机前舱观察员使用安装在 斯卡夫环形枪架上的 刘易斯机枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Scarff_ring_sc--ff-712f7dbe8a8f.64enbdjynj.webp" alt="保存于 Bristol F.2B 上的 斯卡夫环形枪架与 刘易斯机枪安装结构" /><figcaption>保存于 Bristol F.2B 上的 斯卡夫环形枪架与 刘易斯机枪安装结构</figcaption></figure></div><p>于是，同一架双座飞机上可以同时存在两种不同的航空机枪使用方式：</p><ul>
<li>飞行员：固定前射机枪，用整架飞机瞄准；</li>
<li>观察员：活动机枪，用枪架直接跟踪目标。</li>
</ul></section><section><h2>从手扔炸弹到专用轰炸机<a href="#从手扔炸弹到专用轰炸机"><span>#</span></a></h2><p>战争早期，航空轰炸曾经非常原始。</p><p>小型炸弹甚至可以由机组成员直接用手从机舱中投下。</p><p>但随着飞机变大、发动机功率提升，轰炸开始形成真正的专用武器系统：</p><ul>
<li>炸弹架；</li>
<li>机械投弹装置；</li>
<li>轰炸瞄准具；</li>
<li>更大的载弹量；</li>
<li>夜间导航；</li>
<li>多名机枪手协同防御。</li>
</ul><p>德国 <strong>哥达 G 系列</strong>重型轰炸机在 1917 年开始对英国实施远程轰炸，这些袭击让公众切身感受到：后方城市也可能遭到空中攻击。</p><p>英国则发展出 <strong>汉德利·佩季 O/100、O/400</strong> 等大型双发轰炸机。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Bundesarchiv_Bild_146-1971-045-48%2C_Flugzeug_wird_mit_Bomben_best%C3%BCckt--ff-92d30c0c8f84.32ira5im6k.webp" alt="1917 年德国哥达轰炸机挂载炸弹" /><figcaption>1917 年德国哥达轰炸机挂载炸弹</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Handley_Page_O-400--ff-aef184a4d899.4g5ae6to9z.webp" alt="1918 年汉德利·佩季 O/400 双发重型轰炸机" /><figcaption>1918 年汉德利·佩季 O/400 双发重型轰炸机</figcaption></figure></div><p>到了 1918 年，飞机已经明显分化为多种承担专门任务的机型：</p>

<table><thead><tr><th>类型</th><th>主要任务</th><th>武器系统重点</th></tr></thead><tbody><tr><td>侦察 / 观察机</td><td>拍照、炮兵校射</td><td>无线电、相机、活动机枪</td></tr><tr><td>战斗机</td><td>拦截敌机、争夺制空</td><td>固定同步前射机枪、机动性</td></tr><tr><td>轻型轰炸 / 对地攻击机</td><td>攻击战场目标</td><td>炸弹、前射机枪</td></tr><tr><td>重型轰炸机</td><td>远程攻击城市、铁路、基地</td><td>大载弹量、多机枪防御、导航</td></tr></tbody></table></section><section><h2>高射机枪：地面上的反制手段<a href="#高射机枪地面上的反制手段"><span>#</span></a></h2><p>飞机成为武器以后，变化并不只发生在天空。</p><p>地面部队也马上遇到一个全新的问题：</p><p><strong>怎样才能反制天上的飞机？</strong></p><p>一战早期飞机飞得不高、速度也远低于后来战斗机，因此普通机枪并非完全没有机会。</p><p>最直接的做法就是：</p><blockquote><p>把现成机枪架得更高<br />
→ 给枪架更大的仰角<br />
→ 尝试计算提前量<br />
→ 对飞过阵地的飞机射击</p></blockquote><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/INGENIOUS_ANTI-AIRCRAFT_GUN._A_Lewis_gun_for_anti-aircraft_purposes%2C_Bestanddeelnr_158-1949--ff-4b5ab147aa43.b9p22wi7s.webp" alt="一战时期安装在临时高射枪架上的 Lewis 机枪" /><figcaption>一战时期安装在临时高射枪架上的 Lewis 机枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/American_soldiers_HD-SN-99-02281--ff-f9452c2ea97a.3k8syqjzoh.webp" alt="1918 年 3 月，美军士兵用 Hotchkiss M1914 向德国观察机射击" /><figcaption>1918 年 3 月，美军士兵用 Hotchkiss M1914 向德国观察机射击</figcaption></figure></div><p>飞机移动很快，子弹飞抵时目标已经离开原位，因此射手须预留提前量。</p><p>澳大利亚战争纪念馆保存着 1917—1918 年间为 Lewis 机枪在前线制作的临时高射瞄具。其环形准星甚至针对不同高度、速度和射角给出不同提前量参考。</p><p>对空射击进一步推动了装备与训练的发展：</p><ul>
<li>专用高射枪架；</li>
<li>环形 / 提前量瞄具；</li>
<li>防空观察；</li>
<li>专门射击训练。</li>
</ul><p>随后，野战炮和专用高射炮也会沿着同样方向发展。</p><hr /></section></section>
<section><h1>坦克：步兵无法有效推进，那就借助装甲<a href="#坦克步兵无法有效推进那就借助装甲"><span>#</span></a></h1><p>坦克可能是一战最具象征性的“新武器”。</p><p>研发须逐一解决行驶、跨壕、武器布置、驾驶与机械可靠性问题。</p><p>这条路线从英国的“小威利”开始，到法国雷诺 FT 时，已经能看到后来坦克布局的雏形。</p><section><h2>Little Willie（“小威利”）：仅仅是“能够行驶”<a href="#little-willie小威利仅仅是能够行驶"><span>#</span></a></h2><p>1915 年，英国 Landships Committee（陆地战舰委员会）推动履带装甲车辆实验。</p><div><div><div></div><div>Note</div></div><div><p><strong>tank 名称的来源</strong></p><p>英语 tank 原本指储水等用途的容器。英国为掩饰新式装甲车辆的研发用途，将项目称作“tank”，这个保密代号后来成为这类车辆的通称。</p></div></div><p>由 William Foster &amp; Co. 制造的 <strong>“小威利”</strong> 是其中最重要的早期原型之一。</p><p>它已经具备几个关键部件：</p><ul>
<li>履带；</li>
<li>装甲车体；</li>
<li>内燃机。</li>
</ul><p>设计者希望它能通过泥地、越过障碍。</p><p>但它仍然像一个装在履带上的巨大铁箱。</p><p>最大问题之一，是<strong>跨壕能力不够</strong>。</p><p>西线壕沟往往有数米宽。“小威利”的短履带车体容易使车辆在跨壕时失去有效支撑，无法顺利通过。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Little_Willie1915--ff-550fe01c33ae.6poaxoeetg.webp" alt="1915 年试验中的 “小威利” 坦克原型" /><figcaption>1915 年试验中的 “小威利” 坦克原型</figcaption></figure><p></p><p>工程师 William Tritton 与 Walter Gordon Wilson 随后改变思路。</p><p>与其把履带放在车体下面，不如让履带直接环绕整个车体，前后形成向上翘起的巨大轮廓。</p><p>这便产生了后来极具辨识度的英国“菱形坦克”。</p></section><section><h2>Mother（“母亲”号）与 Mark I（马克 I 型坦克）：坦克的首次实战<a href="#mother母亲号与-mark-i马克-i-型坦克坦克的首次实战"><span>#</span></a></h2><p>新的原型后来被称为 <strong>“母亲”号</strong>，在此基础之上，又设计成了 <strong>“马克 I 型坦克”</strong>。</p><p>马克 I 型的履带环绕车体，可以用长长的前后“角”爬上障碍并跨越壕沟。</p><p>由于顶部安装大型旋转炮塔会增加车高、抬高重心，英国设计者把主炮放到了车体两侧突出的**炮廓（sponson）**里。</p><p>根据武器种类不同，分为两种型号：</p><section><h3>Male“雄性”<a href="#male雄性"><span>#</span></a></h3><p>主要装备两门 6 磅炮，并配机枪。</p><p>任务重点是摧毁机枪阵地和坚固火力点。</p></section><section><h3>Female“雌性”<a href="#female雌性"><span>#</span></a></h3><p>主要装备多挺机枪。</p><p>设计者担心装备火炮的坦克在近距离会被敌军步兵包围，因此用更多机枪增强反步兵能力。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mark_I_series_tank--ff-edbbe80fd9ca.1ht0aolewf.webp" alt="1916 年英国马克 I 型坦克与乘员" /><figcaption>1916 年英国马克 I 型坦克与乘员</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Tank_preparing_to_advance_at_Flers-Courcelette_15-09-1916_IWM_Q_5574--ff-24cdd8db3cc5.7w7m6a3bl5.webp" alt="1916 年 9 月 15 日弗莱尔—库尔斯莱特战斗前的马克 I 型“Clan Leslie”坦克" /><figcaption>1916 年 9 月 15 日弗莱尔—库尔斯莱特战斗前的马克 I 型“Clan Leslie”坦克</figcaption></figure></div><p>1916 年 9 月 15 日，马克 I 型在索姆河战役中的弗莱尔—库尔斯莱特第一次投入实战。</p><p>计划投入的坦克数量并不少，可大量车辆在抵达战斗位置前就因为机械故障、地形或其他问题掉队。真正驶入无人区的车辆远少于计划。</p><p>同时，车内环境同样恶劣。</p><p>发动机、变速箱和战斗室基本挤在一个空间里。噪声巨大，温度很高，燃油和废气气味浓重；履带、齿轮和钢板不断震动。敌军子弹即使没有击穿装甲，也可能打下装甲内侧碎屑造成损害。</p><p>对早期坦克乘员来说，车内远谈不上舒适，更像是和发动机一起被塞进一台移动的钢铁机器。</p></section></section><section><h2>Mark IV（马克 IV 型坦克）：真正大规模生产<a href="#mark-iv马克-iv-型坦克真正大规模生产"><span>#</span></a></h2><p>马克 II 型和马克 III 型更多承担过渡和训练任务。真正把英国菱形坦克推向大规模使用的，是<strong>马克 IV 型</strong>。</p><p>1917 年开始服役后，它成为一战产量最大的英国重型坦克型号之一，也是康布雷战役的主力。</p><p>相比马克 I 型，它改进了：</p><ul>
<li>装甲与内部布局；</li>
<li>油箱位置；</li>
<li>炮廓结构；</li>
<li>适合批量生产的结构设计；</li>
<li>部分机械系统的可靠性。</li>
</ul><p>其中一个非常实际的变化，是把油箱从靠近乘员舱的位置移到车体后部更安全的地方。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/A_Mark_IV_%28Male%29_tank_of_H_Battalion%2C_Hyacinth%2C_ditched_in_a_German_trench_while_supporting_1st_Battalion%2C_Leices--ff-5f841ecb5d58.icwxlfiza.webp" alt="1917 年康布雷战役中陷入德军战壕的英国马克 IV 型“Hyacinth”坦克" /><figcaption>1917 年康布雷战役中陷入德军战壕的英国马克 IV 型“Hyacinth”坦克</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mark_IV_%28Excellent%29_front-left_2017_Bovington--ff-10951a7b5662.3gp710qx8f.webp" alt="博文顿坦克博物馆保存的马克 IV 型“Excellent”坦克" /><figcaption>博文顿坦克博物馆保存的马克 IV 型“Excellent”坦克</figcaption></figure></div><p>1917 年康布雷战役中，大批马克 IV 型集中投入进攻。</p><p>坦克能够压过铁丝网、跨越部分壕沟，并用火炮和机枪攻击火力点。</p></section><section><h2>Mark V（马克 V 型坦克）：操纵性能明显改善<a href="#mark-v马克-v-型坦克操纵性能明显改善"><span>#</span></a></h2><p>1918 年的<strong>马克 V 型</strong>外形与马克 IV 型非常相似。</p><p>真正重要的变化藏在内部。</p><p>此前英国重型坦克的转向十分麻烦，需要多名乘员共同操作不同变速机构。马克 V 型引入更强的 <strong>Ricardo 150 马力发动机</strong>和改进的周转式转向传动系统。驾驶员终于能够更直接地通过控制左右履带速度进行转向。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/British_Mark_V_%28male%29_tank--ff-bde7682b275c.etazspku7.webp" alt="一战时期英国马克 V 型“雄性”重型坦克官方照片" /><figcaption>一战时期英国马克 V 型“雄性”重型坦克官方照片</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mark_V_tank_Bovington_Flickr_4773871655--ff-dccec4e7ef05.8dxnuv4p1f.webp" alt="博文顿坦克博物馆保存的英国马克 V 型坦克" /><figcaption>博文顿坦克博物馆保存的英国马克 V 型坦克</figcaption></figure></div><p>它仍然慢，仍然重，乘员环境仍然恶劣。</p></section><section><h2>Schneider CA1（施耐德 CA1）与 Saint-Chamond（圣沙蒙）：法国的试错路线<a href="#schneider-ca1施耐德-ca1与-saint-chamond圣沙蒙法国的试错路线"><span>#</span></a></h2><p>法国此前尝试过两种较笨重的坦克方案，与它们相比，雷诺 FT 的优点尤其突出。</p><section><h3>Schneider CA1（施耐德 CA1）：法国第一种投入实战的坦克<a href="#schneider-ca1施耐德-ca1法国第一种投入实战的坦克"><span>#</span></a></h3><p>法国 <strong>施耐德 CA1</strong> 同样建立在履带拖拉机技术基础上。</p><p>它的车体像一个巨大的装甲箱，前部突出一个用于压倒铁丝网的“船首”，75 mm 短炮则偏置安装在车体右前侧，另外配有哈奇开斯机枪。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Schneider_CA1._Mus%C3%A9e_des_Blind%C3%A9s%2C_Saumur%2C_France._11-7-2022_%2853274810286%29--ff-2836d9b1c635.4g5ae6toi5.webp" alt="索米尔装甲博物馆保存的施耐德 CA1" /><figcaption>索米尔装甲博物馆保存的施耐德 CA1</figcaption></figure><p></p><p>它的问题和早期很多坦克一样：</p><p>履带本身太短，车体却向前后伸出很多。</p><p>面对宽壕沟时，车体很容易先“架”在沟边。</p><p>1917 年第一次大规模投入实战时，施耐德 CA1 遭受很重损失，但它仍然让法军获得了宝贵经验。</p></section><section><h3>Saint-Chamond（圣沙蒙）：强大的火力，糟糕的设计<a href="#saint-chamond圣沙蒙强大的火力糟糕的设计"><span>#</span></a></h3><p><strong>圣沙蒙</strong> 更加极端。</p><p>它安装了一门威力更强的 75 mm 火炮，后期甚至使用接近标准野战炮性能的主炮。</p><p>可为了容纳武器、乘员和机械系统，车体前后都远远伸出较短的履带之外。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Char_St_Chamond_tank--ff-324d552893a3.58i5vxa9xr.webp" alt="一战时期法国圣沙蒙坦克，可清楚看到远远伸出履带前方的车鼻" /><figcaption>一战时期法国圣沙蒙坦克，可清楚看到远远伸出履带前方的车鼻</figcaption></figure><p></p><p>在平整地面上，这种布局并不一定糟糕。</p><p>一旦遇到：</p><ul>
<li>宽壕沟；</li>
<li>弹坑；</li>
<li>陡坡；</li>
<li>松软泥地；</li>
</ul><p>巨大的车鼻就很容易插进地面，让车辆“搁浅”。</p></section></section><section><h2>Medium Mark A Whippet（A型中型“惠比特”坦克）：坦克第一次开始追求“跑得更快”<a href="#medium-mark-a-whippeta型中型惠比特坦克坦克第一次开始追求跑得更快"><span>#</span></a></h2><p>英国菱形重型坦克从设计之初就不追求高速。</p><p>它们只需要：</p><ul>
<li>和步兵差不多快；</li>
<li>压倒铁丝网；</li>
<li>跨越壕沟；</li>
<li>顶着轻武器火力向前。</li>
</ul><p>可一旦第一道防线真的被打开，新的问题出现了：</p><p><strong>有没有一种坦克能抓住防线被突破的机会，迅速向敌军后方推进？</strong></p><p><strong>A型中型“惠比特”坦克</strong> 就是这个思路的早期答案。</p><p>它比马克 IV 型、马克 V 型小得多，使用两台汽车发动机，最高速度超过 8 mph，约为同时代英国重型坦克的两倍。</p><p>武器主要是哈奇开斯机枪，而不是大口径火炮。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Whippet%2C_Aug_1918--ff-6f5b0b4ad21f.8l0vqaqunm.webp" alt="1918 年 8 月法国 Grevillers 附近跨越战壕的 A型中型“惠比特”坦克" /><figcaption>1918 年 8 月法国 Grevillers 附近跨越战壕的 A型中型“惠比特”坦克</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Whippet--ff-623c570cb77e.7so08ka8xf.webp" alt="布鲁塞尔皇家军事博物馆收藏的 A型中型“惠比特”坦克" /><figcaption>布鲁塞尔皇家军事博物馆收藏的 A型中型“惠比特”坦克</figcaption></figure></div><p>重型坦克突破防线、较快车辆扩大战果的分工，在战间期得到继续发展。</p></section><section><h2>Renault FT（雷诺 FT 轻型坦克）：现代坦克布局真正出现<a href="#renault-ft雷诺-ft-轻型坦克现代坦克布局真正出现"><span>#</span></a></h2><p>法国<strong>雷诺 FT</strong> 采用了后来广泛使用的坦克布局。</p><p>雷诺 FT 很小。</p><p>只有两名乘员：</p><ul>
<li>驾驶员在车体前部；</li>
<li>车长兼炮手位于后方炮塔内。</li>
</ul><p>发动机放在车体后部。</p><p>最关键的是：</p><p><strong>主要武器安装在一个可以 360° 旋转的炮塔里。</strong></p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/American_troops_going_forward_to_the_battle_line_in_the_Forest_of_Argonne._France%2C_September_26%2C_1918._-_NARA_-_5--ff-18125ad615e9.8dxnuv4osj.webp" alt="1918 年 9 月 26 日，阿贡森林前线与美军共同前进的两辆法国雷诺 FT" /><figcaption>1918 年 9 月 26 日，阿贡森林前线与美军共同前进的两辆法国雷诺 FT</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Renault_FT_%E2%80%98753%E2%80%99_%2837632013970%29--ff-2300cc96d482.3rc0u665gn.webp" alt="保存于博物馆中的 雷诺 FT轻型坦克" /><figcaption>保存于博物馆中的 雷诺 FT轻型坦克</figcaption></figure></div><p>这套布局后来几乎成了坦克的标准布局：</p><blockquote><p>驾驶舱在前<br />
→ 战斗室与旋转炮塔在中<br />
→ 发动机在后</p></blockquote><p>雷诺 FT 可以装备机枪，也可以装备 37 mm 炮。</p><p>它不像英国重型坦克那样依靠巨大的车体跨越超宽壕沟，而是追求：</p><ul>
<li>小型化；</li>
<li>大量生产；</li>
<li>分散配属；</li>
<li>更灵活地跟随步兵。</li>
</ul><p>1918 年，雷诺 FT 开始大量投入战斗。</p><p>战争结束以后，它又被许多国家采用或仿制，对战间期坦克发展影响极大。</p></section><section><h2>A7V：没来得及展开使用的坦克<a href="#a7v没来得及展开使用的坦克"><span>#</span></a></h2><p>德国也发展了自己的坦克：</p><p><strong>A7V 重型坦克</strong></p><p>它在 1918 年进入前线。</p><p>A7V 外形更像一个巨大的装甲箱，前部安装 57 mm 火炮，车体周围布置多挺 MG 08 机枪，需要数量众多的乘员。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/A7V_Wotan--ff-68e52f2492af.4n8i9mftjl.webp" alt="1918 年德国 A7V“Wotan”坦克" /><figcaption>1918 年德国 A7V“Wotan”坦克</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/A7V_tank%2C_Sept_1918--ff-c8e6362615c4.3k8syqjzny.webp" alt="1918 年新西兰士兵检查被俘获的德国 A7V“Schnuck”坦克" /><figcaption>1918 年新西兰士兵检查被俘获的德国 A7V“Schnuck”坦克</figcaption></figure></div><p>A7V 拥有一些优于英国早期坦克的机械设计，但德国只制造了大约 20 辆，数量与英法相差悬殊。德国装甲部队还大量使用缴获的英军坦克。</p><p>1918 年 4 月 24 日，维莱-布勒托讷附近发生了历史上最早的坦克战：英国马克 IV 型与德国 A7V 正面遭遇。</p><hr /></section></section>
<section><h1>从普通步枪到 Tankgewehr（反坦克步枪）：反坦克武器第一次出现<a href="#从普通步枪到-tankgewehr反坦克步枪反坦克武器第一次出现"><span>#</span></a></h1><p>1916 年 9 月，英军坦克第一次出现时，德军前沿步兵几乎没有任何应对方案。</p><p>最初，人们只能用手头已有的武器和手段尝试应对：</p><ul>
<li>普通步枪和机枪集中射击；</li>
<li>手榴弹；</li>
<li>野战炮直接瞄准；</li>
<li>迫击炮低角度射击；</li>
<li>挖更宽的反坦克壕。</li>
</ul><p>小口径步枪弹虽然很难直接在装甲上打出大洞，却可能在较薄装甲内侧打下金属碎屑，伤害乘员。</p><p>德军很快进一步尝试专门的穿甲弹药。</p><section><h2>K bullet（K型穿甲弹）：先让普通武器尝试对付坦克<a href="#k-bulletk型穿甲弹先让普通武器尝试对付坦克"><span>#</span></a></h2><p>所谓 <strong>K型穿甲弹</strong>，通常指带硬质钢芯的 7.92×57 mm 穿甲弹。</p><p>它可以由普通毛瑟98式步枪、MG 08 等 7.92 mm 武器发射，因此最大的优势是：</p><p><strong>不需要立刻发明一种全新的枪。</strong></p><p>面对早期薄装甲坦克时，这类穿甲弹在近距离确实可能穿透装甲或使装甲内侧剥落。</p><p>可坦克装甲也在进步。</p><p>当马克 IV 型等新型号增加防护以后，7.92 mm 步枪弹能够提供的穿甲能力大大降低。</p></section><section><h2>Mauser Tankgewehr M1918（毛瑟 M1918 反坦克步枪）：极端大口径步枪<a href="#mauser-tankgewehr-m1918毛瑟-m1918-反坦克步枪极端大口径步枪"><span>#</span></a></h2><p>1918 年，德国推出 <strong>毛瑟 M1918 反坦克步枪</strong>。</p><p>它看上去像一支被放大到夸张程度的栓动步枪：</p><ul>
<li>口径约 13 mm；</li>
<li>枪身巨大；</li>
<li>使用两脚架；</li>
<li>单发装填；</li>
<li>后坐力极强。</li>
</ul><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Two_of_our_officers_with_a_German_anti-tank_gun_%284687973105%29--ff-64852992eba9.1sfu3u0n76.webp" alt="1918 年英军军官在德军战壕中查看缴获的 M1918 反坦克步枪" /><figcaption>1918 年英军军官在德军战壕中查看缴获的 M1918 反坦克步枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Mauser_Tank-Gewehr_M1918--ff-7b8680d22744.7lksd4o3c5.webp" alt="法国陆军博物馆收藏的 毛瑟 M1918 反坦克步枪" /><figcaption>法国陆军博物馆收藏的 毛瑟 M1918 反坦克步枪</figcaption></figure></div><p>Tankgewehr 的思路非常直接：</p><blockquote><p>既然普通步枪弹穿不透坦克，那就把步枪和子弹都做得更大。</p></blockquote><p>它使用的并非后来反坦克火箭中的成形装药。</p><p>它只是用更大、更快的实心弹试图打穿薄钢板，杀伤里面的乘员或破坏部件。</p><hr /></section></section>
<section><h1>没有正式登场：战争差一点选择的武器<a href="#没有正式登场战争差一点选择的武器"><span>#</span></a></h1><p>如果只看最终列装并大量使用的武器，一战的技术发展会显得比真实历史整齐得多。</p><p>仿佛军队先拥有栓动步枪，然后自然发展出轻机枪、冲锋枪和坦克；不合适的路线则像从来没有存在过。</p><p>实际上恰恰相反。</p><p>1914—1918 年间，大量设计被制造成样枪或原型车，有些已经通过相当正式的测试，有些甚至获得采购计划，却因为可靠性、工业能力、战术变化或者停战而没有真正投入大规模实战。</p><p>还有一些项目更特别，仅仅停留在了试验阶段。我们今天甚至很难确定它们准确叫什么、由谁设计，又究竟走到了哪一步。</p>

<table><thead><tr><th>命运</th><th>典型例子</th><th>没有成为一战制式武器的主要原因</th></tr></thead><tbody><tr><td>技术方向有吸引力，但工业条件跟不上</td><td>刘将军步枪</td><td>量产设备、组织与时间不足</td></tr><tr><td>原型表现尚可，但已有成熟替代品</td><td>霍特自动步枪</td><td>刘易斯已形成成熟供应体系</td></tr><tr><td>已接近列装或采购阶段，但战争先结束</td><td>法夸尔-希尔步枪、佩德森装置</td><td>1918 年停战使需求骤降</td></tr><tr><td>为战壕近战专门开发，原型尚未来得及成熟</td><td>Annihilator（“歼灭者”）原型冲锋枪</td><td>项目已进入原型阶段，但未赶上一战实战</td></tr><tr><td>概念超前，但可靠性或性能仍不成熟</td><td>Ribeyrolles 1918、Burton Machine Rifle</td><td>弹药、机构与使用概念尚未定型</td></tr><tr><td>史料极少，甚至连正式名称都不确定</td><td>Hellriegel 1915、“SMG 08/18”</td><td>只留下少量照片或实物，项目档案缺失</td></tr><tr><td>试图用极端尺寸突破堑壕</td><td>FCM 2C、K-Wagen</td><td>过重、过慢、工业成本巨大，且被停战截断</td></tr><tr><td>坦克布局还没有标准答案</td><td>A7V-U、LK II、沙皇坦克、Boirault Machine、骨架坦克</td><td>各国仍在试探履带、车体、动力和跨壕方式</td></tr></tbody></table><section><h2>自动步枪的十字路口：下一支步兵武器究竟应该是什么？<a href="#自动步枪的十字路口下一支步兵武器究竟应该是什么"><span>#</span></a></h2><p>自动装填步枪、轻量化机枪与降低弹药威力的自动武器，在战争结束前都有原型，但实现量产和列装还受不同条件限制。</p><section><h3>General Liu Rifle（刘将军步枪）：受制于工业条件的半自动步枪方案<a href="#general-liu-rifle刘将军步枪受制于工业条件的半自动步枪方案"><span>#</span></a></h3><p>刘庆恩设计的“刘氏步枪”——今天更常被称作 <strong>刘将军步枪</strong>——是这一时期最有意思的半自动步枪项目之一。</p><p>它大约从 1914 年开始研发，使用 7.92 mm 毛瑟步枪弹，并采用枪口集气式自动机构。</p><p>它最特别的一点，是可以在两种工作方式之间切换：</p><ul>
<li>枪口装有集气筒，火药燃气推动它带动连杆完成自动抛壳与上膛；</li>
<li>旋转枪口导气箍，也可以把它当作传统栓动步枪使用。</li>
</ul><p>这种设计思路非常符合当时军队面对新技术时的谨慎态度。</p><p>半自动机构出现故障，步枪仍然可以使用；士兵可以手动操作枪机，继续射击。</p><p>1915 年制造的试验枪今天仍保存在瑞典陆军博物馆。1916 年前后，中国方面对不同样枪进行了测试；到 1918 年，相关试验又在美国 Springfield Armory 进行。</p><p>中国方面曾尝试从美国采购生产设备，但采购、运输、生产组织和时间上的困难，使刘将军步枪未能进入大规模生产。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/General_Liu_rifle_-_1915--ff-42ee22705890.1apsf8z9cq.webp" alt="瑞典陆军博物馆收藏的 1915 年刘将军步枪试验枪" /><figcaption>瑞典陆军博物馆收藏的 1915 年刘将军步枪试验枪</figcaption></figure><p></p></section><section><h3>Huot Automatic Rifle（霍特自动步枪）：把已经淘汰的 Ross（罗斯）步枪“救回来”<a href="#huot-automatic-rifle霍特自动步枪把已经淘汰的-ross罗斯步枪救回来"><span>#</span></a></h3><p>加拿大的 <strong>霍特自动步枪</strong> 走的是完全不同的路线。</p><p>1916 年，加拿大已经拥有大量退出一线使用的罗斯步枪。Joseph Alphonse Huot 的想法并不是从头造一支新枪，而是利用这些现有步枪的机匣和生产基础，把它改造成全自动轻机枪。</p><p>他增加了：</p><ul>
<li>导气系统；</li>
<li>自动循环机构；</li>
<li>25 发弹鼓；</li>
<li>散热和护罩结构；</li>
<li>更适合持续射击的支撑装置。</li>
</ul><p>最终得到的武器，从外形上仍能看出罗斯步枪的痕迹，用途却已完全不同。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Major_Robert_Blair_with_Huot--ff-e43869132e6f.7p4eauh608.webp" alt="Major Robert Blair 与 霍特自动步枪试验枪" /><figcaption>Major Robert Blair 与 霍特自动步枪试验枪</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Huot_and_Lewis_-_1--ff-5e83d11e4536.51ey0ho4l0.webp" alt="霍特自动步枪与刘易斯轻机枪的比较照片" /><figcaption>霍特自动步枪与刘易斯轻机枪的比较照片</figcaption></figure></div><p>Huot 并不是“完全不能用”。</p><p>加拿大军方确实进行了认真的测试，项目甚至走到了相当接近采用的阶段。</p><p>问题是英国和加拿大已经有了更成熟的刘易斯机枪。</p><p>一旦一件成熟武器已经形成稳定生产、训练和零件供应体系，另一件“也不错”的武器就必须证明自身优势足以抵消整套换装的成本。</p><p>Huot 没能做到这一点。</p><p>加拿大战争博物馆今天仍将它归为只达到原型阶段、从未量产的武器。</p></section><section><h3>Farquhar-Hill（法夸尔-希尔步枪）：已获正式名称，却没等到投入生产<a href="#farquhar-hill法夸尔-希尔步枪已获正式名称却没等到投入生产"><span>#</span></a></h3><p>英国 <strong>法夸尔-希尔步枪</strong> 则更加接近真正列装。</p><p>Farquhar 与 Hill 从战前就开始研究自动装填步枪。经过多轮改进后，战争后期的军用版本使用 .303 British 弹药和大型弹鼓，通过导气系统驱动枪机。</p><p>到 1918 年，它甚至获得了正式名称：</p><p><strong>Rifle, .303 inch, Pattern 1918</strong>。</p><p>英国军方已经提出大规模采购需求。</p><p>1918 年 11 月停战，截断了这一采购进程。</p><p>随着军需迅速收缩，为一种结构复杂、此前没有建立生产线的新式半自动步枪投入大量资源扩充产能，就失去了战争时期的紧迫性。</p></section><section><h3>Pedersen Device（佩德森装置）：让一把步枪兼顾远近距离<a href="#pedersen-device佩德森装置让一把步枪兼顾远近距离"><span>#</span></a></h3><p>美国采取的方案更加另类。</p><p>John Pedersen 并不要求士兵放弃 M1903 Springfield。</p><p>他的方案是：</p><blockquote><p>在需要远距离射击时，仍然按原有方式使用 .30-06 栓动步枪；</p><p>准备进入壕沟近战时，把原来的枪机抽出来，再把一套半自动装置塞进步枪。</p></blockquote><p>这就是 <strong>佩德森装置</strong>。</p><p>它使用威力远低于 .30-06 的专用 .30 口径弹药，并通过长弹匣供弹。经过专门改造的 M1903 Mk I 机匣侧面还开有抛壳口。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/U.S._AUTOMATIC_PISTOL%2C_CAL..30%2C_MODEL_OF_1918_%28PEDERSEN_DEVICE%29--ff-c3af73937bc2.icwxiinw3.webp" alt="Springfield Armory Museum 保存的 佩德森装置" /><figcaption>Springfield Armory Museum 保存的 佩德森装置</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Kit_for_Pedersen_Device--ff-3c0ac4ae3741.64enbdjyh8.webp" alt="安装 佩德森装置 的 M1903 Mk I 与相关套件" /><figcaption>安装 佩德森装置 的 M1903 Mk I 与相关套件</figcaption></figure></div><p>为了保密，美国甚至把它称作：</p><p><strong>Automatic Pistol, Caliber .30, Model of 1918</strong>。</p><p>它原本计划在 1919 年的大规模进攻中突然投入，让步兵在接近德军壕沟后获得比普通栓动步枪高得多的射击速度。</p><p>实际制造数量已经达到数万套。</p><p>结果战争在计划实施以前结束。</p><p>佩德森装置后来也没有成为美国步兵武器的长期方向，因为它需要：</p><ul>
<li>对原枪进行专门改造；</li>
<li>同时携带两种弹药；</li>
<li>保管拆下来的原步枪枪机；</li>
<li>在战斗环境中完成机构转换。</li>
</ul></section><section><h3>Annihilator（“歼灭者”）：更适合扫荡战壕的冲锋枪<a href="#annihilator歼灭者更适合扫荡战壕的冲锋枪"><span>#</span></a></h3><p>后来因为美国禁酒令、黑帮电影和第二次世界大战而闻名的 <strong>Thompson Submachine Gun（汤普森冲锋枪）</strong>，它的研发起点也可以追溯到第一次世界大战。</p><p>美国准将 <strong>John T. Thompson（约翰·T·汤普森）</strong> 在战争期间希望得到一种比传统机枪轻得多、能够由单兵携带，并在狭窄壕沟中提供密集自动火力的武器。</p><p>他后来把这种设想概括成一个很形象的词：</p><p><strong>“trench broom”——“战壕扫帚”。</strong></p><p>这与 BAR、刘易斯轻机枪等武器的思路并不完全相同。</p><p>汤普森真正想要的不是一支可以在几百米外持续射击的轻机枪，而是一种：</p><ul>
<li>由单兵直接携带；</li>
<li>体积明显小于步枪和轻机枪；</li>
<li>使用手枪弹；</li>
<li>能够进行全自动射击；</li>
<li>专门服务于几十米以内近距离突击</li>
</ul><p>的新型自动武器。</p><p>1916 年，汤普森参与创立 <strong>Auto-Ordnance Corporation（自动军械公司）</strong>。设计团队最初曾尝试把所谓 **Blish principle（布利什原理）**用于发射 .30-06 全威力步枪弹的自动步枪，但很快发现这种弹药对方案而言过于强劲，于是转而把同一思路应用到威力更低的 <strong>.45 ACP</strong> 手枪弹。</p><p>最早的一条试验路线被称为 <strong>Persuader（“说服者”）</strong>。它尝试采用布制弹带供弹。</p><p>这种方案在纸面上可以提供很大的连续火力，但实际测试时供弹非常不可靠。据后来整理 Auto-Ordnance 早期档案的资料，Persuader 往往只连续射击几发，就会因为供弹机构故障停止工作。</p><p>设计团队因此继续修改机构，并研制出后来被称为 **Annihilator（“歼灭者”）**的一系列原型。</p><p>到 1918 年，已经出现可以测试的 <strong>Annihilator I</strong> 原型；不过最早的一批型号在战争结束时仍没有准备好投入实战。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/John_Taliaferro_Thompson%2C_U.S._Army_officer%2C_founder_of_the_Auto-Ordnance_Corporation%2C_and_inventor_of_the_Thomps--ff-6c062f0e5937.4g5ae6toaz.webp" alt="1915 年的 John T. Thompson，后来 Auto-Ordnance 与汤普森冲锋枪项目的核心推动者" /><figcaption>1915 年的 John T. Thompson，后来 Auto-Ordnance 与汤普森冲锋枪项目的核心推动者</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Thompson_1921_submachine_gun--ff-121dff35f8fc.6f1h4iz6un.webp" alt="战后由 Annihilator 系列继续演化而来的 Thompson Model 1921 冲锋枪" /><figcaption>战后由 Annihilator 系列继续演化而来的 Thompson Model 1921 冲锋枪</figcaption></figure></div><div><div><div></div><div>Note</div></div><div><p>右图为战后发展的 Thompson M1921，并非 Annihilator 原型。</p></div></div><p>战争结束以后，Annihilator 系列没有被简单丢弃。</p><p>Auto-Ordnance 继续修改供弹、枪托、瞄具和机匣结构。1919—1920 年间出现的多批实验型已经越来越接近后来熟悉的汤普森外形，随后又发展为 <strong>Model 1921（M1921 汤普森冲锋枪）</strong>。</p></section><section><h3>Ribeyrolles 1918（利贝罗勒 1918 自动步枪）：开始探索中间威力弹<a href="#ribeyrolles-1918利贝罗勒-1918-自动步枪开始探索中间威力弹"><span>#</span></a></h3><p>法国的 <strong>Ribeyrolles 1918</strong> 更像是一件从未来短暂闯入一战的武器。</p><p>它尝试使用威力明显低于全尺寸步枪弹、却高于普通手枪弹的实验弹药，并让单兵武器能够进行自动射击。</p><p>这个方向会让人很自然地想到后来的“中间威力弹”和突击步枪。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Ribeyrolle1918--ff-d30754d46171.3yf8plsaw9.webp" alt="Ribeyrolles 1918 自动卡宾枪的现代展示图" /><figcaption>Ribeyrolles 1918 自动卡宾枪的现代展示图</figcaption></figure><p></p><p>但“思路像未来”不等于“当时已经成熟”。</p><p>Ribeyrolles 的试验没有证明它具备足够可靠性和有效射程，战争结束以后也没有继续成为法国制式步兵武器。</p><p>但在几十年以后，中间威力弹会成为自动步枪发展最重要的答案之一。</p></section><section><h3>Burton Machine Rifle（伯顿机关枪）：连真正用途都是谜团<a href="#burton-machine-rifle伯顿机关枪连真正用途都是谜团"><span>#</span></a></h3><p>美国 Winchester 工程师 Frank Burton 在 1917—1918 年前后还设计过一件更加奇特的 <strong>Burton Machine Rifle / Light Machine Rifle（伯顿 LMR）</strong>。</p><p>它使用一种实验性中等威力弹药，拥有可选择半自动/全自动射击能力、手枪握把和可拆卸弹匣；最引人注意的是，枪上可以同时装两只弹匣，以便迅速切换供弹。</p><p>今天保存下来的实物让它看起来异常“现代”。</p><p>但 Cody Firearms Museum 对这件武器的态度反而很谨慎：相关档案极少，我们甚至很难确定它原本究竟准备承担什么任务、经历过怎样的正式试验。</p></section></section><section><h2>一战武器史里的“幽灵”<a href="#一战武器史里的幽灵"><span>#</span></a></h2><p>有些实验武器至少留下完整样枪和测试记录。</p><p>还有一些，只留下几张照片和一个难以解释的实物。</p><section><h3>Hellriegel 1915（黑尔里格尔 1915）：只留下了一些照片<a href="#hellriegel-1915黑尔里格尔-1915只留下了一些照片"><span>#</span></a></h3><p>1915 年 10 月，奥匈帝国留下了一组极为著名的试验照片。</p><p>照片里，一种由士兵操作的自动武器带有粗大的冷却套筒、木制枪托和手枪握把；某些照片还出现了大容量供弹装置。</p><p>后人通常称它为 <strong>Standschütze Hellriegel M1915</strong>。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Brzostrelka_Hellriegel_-_1--ff-5b7db570f913.pg4sy4szp.webp" alt="1915 年奥匈军 Hellriegel 实验自动武器测试照片" /><figcaption>1915 年奥匈军 Hellriegel 实验自动武器测试照片</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Brzostrelka_Hellriegel_-_3--ff-67a5dd24a106.3yf8plsami.webp" alt="Hellriegel 实验武器及其供弹装置的另一张测试照片" /><figcaption>Hellriegel 实验武器及其供弹装置的另一张测试照片</figcaption></figure></div><p>问题是，我们缺少真正完整的设计文件。</p><p>它究竟使用什么样的内部机构？</p><p>是否只制造了一件？</p><p>准备把它当作轻机枪、冲锋枪，还是另一种当时尚未定型的自动武器？</p><p>很多问题都已经没有足够资料来回答。</p></section><section><h3>“SMG 08/18”：甚至没有留下名字<a href="#smg-0818甚至没有留下名字"><span>#</span></a></h3><p>另一件更加神秘的武器如今保存在俄罗斯图拉。</p><p>它通常被网络资料称作 <strong>SMG 08/18、马克沁 SMG</strong>，有时也会被称为 Schwarzlose SMG。</p><p>这些都是现代资料中的称呼，尚未得到一战德国军方档案确认。</p><p>它与真正的 <strong>MG 08/18</strong> 不是同一种武器。MG 08/18 是德国在 MG 08/15 基础上发展的 7.92 mm 风冷轻量化机枪；这里所说的实验枪则发射 9×19 mm Parabellum 手枪弹。</p><p>它的机构很是缩小版的马克沁式肘节闭锁系统，供弹装置更奇怪：使用多列弹夹，将弹药逐列送入机匣。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Schwarzlose_SMG--ff-57f5c24e2a6e.7i16fev0ps.webp" alt="今天保存的神秘 9 mm 马克沁式实验自动武器" /><figcaption>今天保存的神秘 9 mm 马克沁式实验自动武器</figcaption></figure><p></p><p>真正确定的事实反而很少。我们不知道：</p><ul>
<li>正式军用名称是什么；</li>
<li>谁完整设计了它；</li>
<li>到底制造了多少支；</li>
<li>是否参加过军方正式测试；</li>
<li>有没有任何前线使用记录。</li>
</ul><hr /></section></section><section><h2>坦克也没有“标准答案”：各国曾认真尝试过完全不同的怪物<a href="#坦克也没有标准答案各国曾认真尝试过完全不同的怪物"><span>#</span></a></h2><p>一战末期，飞机至少已经大致形成侦察机、战斗机和轰炸机的轮廓。</p><p>坦克的基本形态却还远未定型。</p><p>1918 年的工程师甚至还没有完全确定：</p><ul>
<li>坦克应该有多大；</li>
<li>应该把履带放在哪里；</li>
<li>应该用机械传动还是电传动；</li>
<li>主炮应该装在炮塔还是车体侧面；</li>
<li>是制造少数“陆地战舰”，还是制造大量小型车辆。</li>
</ul><p>所以，一战实验坦克的形态会显得异常夸张。</p><section><h3>FCM 2C / Char 2C（夏尔 2C 超重型坦克）：为一战准备的“陆地战舰”<a href="#fcm-2c--char-2c夏尔-2c-超重型坦克为一战准备的陆地战舰"><span>#</span></a></h3><p>法国的 <strong>FCM 2C 超重型坦克</strong> 是“战争结束得比武器研发更快”的典型。</p><p>它的设计源于一战突破坚固防线的需求，希望获得远超雷诺 FT 和早期突击坦克的防护、越壕能力与火力。</p><p>最终量产车辆重约 69 吨，车体长达十米左右，并使用旋转炮塔安装 75 mm 火炮。</p><p>从体量上说，它已经非常接近“陆地战舰”这个词的字面含义。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Berry_in_1928_%28moar_contrast_%2B_cropped%29--ff-8f620e7b46e6.362d7vbovv.webp" alt="1928 年演习中的法国 FCM 2C“Berry”" /><figcaption>1928 年演习中的法国 FCM 2C“Berry”</figcaption></figure><p></p><p>但它没赶上一战。</p><p>真正交付和服役已经是在 1921 年以后。</p><p>所以 FCM 2C 不能算“一战参战坦克”，却又确确实实是一件<strong>由一战问题催生、为突破一战式防线而设计</strong>的武器。</p><p>这也暗示了它的命运：它最终被投入二战，但那时其设计理念早已过时，真正到达战场时，部队已经开始撤退，而它跟不上部队的撤退速度。为防止敌人缴获，在真正参与战斗前便被炸毁。</p></section><section><h3>Großkampfwagen / K-Wagen（K型超重型坦克）：德国也曾把坦克推向 120 吨<a href="#großkampfwagen--k-wagenk型超重型坦克德国也曾把坦克推向-120-吨"><span>#</span></a></h3><p>德国的对应极端方案是 <strong>Großkampfwagen / K-Wagen</strong>。</p><p>1917 年，德国开始发展这种超重型突破车辆。</p><p>设计重量达到约 120 吨，计划在车体两侧布置多门 77 mm 火炮和多挺机枪，需要数量众多的乘员。</p><p>整车甚至大到必须考虑分段运输，到前线附近再完成组装。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/German_K_Panzerkampfwagen_1918--ff-2f632b7f2d9d.1ht0aolesf.webp" alt="1918 年德国 K-Wagen 超重型坦克原型照片" /><figcaption>1918 年德国 K-Wagen 超重型坦克原型照片</figcaption></figure><p></p><p>两辆原型车在停战时已经接近完成，却没有机会投入战斗。</p></section><section><h3>A7V-U：德国借鉴英国菱形坦克的布局<a href="#a7v-u德国借鉴英国菱形坦克的布局"><span>#</span></a></h3><p>A7V 实战以后，德国人很容易发现它最大的先天问题之一：</p><p><strong>车体很大，但履带相对较短，跨越宽壕沟的能力并不理想。</strong></p><p>于是出现了 <strong>A7V-U</strong>。</p><p>它采用类似英国 Mark 系列的环绕式履带，让履带包围整个车体轮廓。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/A7V-U--ff-7e7069285f72.7so08ka8gi.webp" alt="1918 年 A7V-U 原型车" /><figcaption>1918 年 A7V-U 原型车</figcaption></figure><p></p><p>项目没有进入量产。</p></section><section><h3>Leichter Kampfwagen II（LK II 轻型坦克）：德国轻型坦克没有赶上一战，却在瑞典“继续活了下去”<a href="#leichter-kampfwagen-iilk-ii-轻型坦克德国轻型坦克没有赶上一战却在瑞典继续活了下去"><span>#</span></a></h3><p>德国也并不是只研究过重型坦克。</p><p>Joseph Vollmer 设计的 <strong>Leichter Kampfwagen II（LK II）</strong> 代表完全相反的方向：更小、更轻、更容易生产。</p><p>到 1918 年，这种轻型坦克已经制造出原型，军方也制定了后续生产计划，但停战让德国本土的大规模计划终止。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Leichter_Kampfwagen_II_%28Version_MG%29_%2839076260322%29--ff-e3f48c4bf134.szqqnxvtk.webp" alt="德国坦克博物馆展示的 LK II / Strv m/21 系车辆" /><figcaption>德国坦克博物馆展示的 LK II / Strv m/21 系车辆</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Leichter_Kampfwagen_II_Vernietete_Panzerung--ff-11dbe537ac97.2h93nuo60w.webp" alt="LK II 的装甲与车体结构展示" /><figcaption>LK II 的装甲与车体结构展示</figcaption></figure></div><p>故事并没有在 1918 年结束。</p><p>战后，瑞典秘密购入了数辆 LK II，并把车辆改装、发展为 <strong>Stridsvagn m/21</strong>。</p></section><section><h3>Tsar Tank（沙皇坦克）：除了履带，或许还可以尝试放大“车轮”<a href="#tsar-tank沙皇坦克除了履带或许还可以尝试放大车轮"><span>#</span></a></h3><p>在履带成为坦克的主流行走装置以前，工程师并不能确定“跨越壕沟一定要靠履带”。</p><p>俄国 <strong>沙皇坦克</strong> 提出的理念是：</p><p><strong>把车轮做得大到可以直接越过障碍。</strong></p><p>它拥有两个直径接近九米的巨大前轮，后方则使用一个小得多的转向轮，整车看起来更像一台装有武器的巨型三轮机械。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Tsar_tank--ff-8fb8781f5161.7zr83zweb4.webp" alt="试验中的俄国沙皇坦克" /><figcaption>试验中的俄国沙皇坦克</figcaption></figure><p></p><p>真正试验时，问题也非常直观。</p><p>后方较小的车轮容易陷入软地，巨大的前轮没有办法让整辆车脱困。</p><p>项目很快停止。</p></section><section><h3>Boirault Machine：履带可以转，整个框架也可以转起来<a href="#boirault-machine履带可以转整个框架也可以转起来"><span>#</span></a></h3><p>法国工程师 Louis Boirault 在 1914—1915 年间提出的 <strong>Appareil Boirault</strong>，甚至比沙皇坦克更难归入我们熟悉的“坦克”概念。</p><p>它让六个巨大的框架连接成一条环形结构，整个框架围绕中央动力舱不断翻转，试图直接压过铁丝网与宽阔障碍。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Appareil_Boirault_1914--ff-cf53128527e7.6il328s96w.webp" alt="法国第一型 Boirault Machine 原型" /><figcaption>法国第一型 Boirault Machine 原型</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Boirault_machine_underway--ff-7d16b7ceac86.6writ40k3g.webp" alt="试验中行进的 Boirault Machine" /><figcaption>试验中行进的 Boirault Machine</figcaption></figure></div><p>然而，它的速度极慢，转向困难，体积巨大，几乎无法成为真正实用的战场车辆。</p><p>于是它获得了一个颇具嘲讽意味的绰号：</p><p><strong>Diplodocus militaris——“军用梁龙”。</strong></p></section><section><h3>Skeleton Tank（骨架坦克）：有马克 V 型的长度，却没有马克 V 型的重量<a href="#skeleton-tank骨架坦克有马克-v-型的长度却没有马克-v-型的重量"><span>#</span></a></h3><p>美国 <strong>骨架坦克</strong> 的思路完全不同。</p><p>设计者承认英国马克 IV 型和马克 V 型的长履带布局非常适合跨壕。</p><p>问题是：</p><p><strong>为什么一定要把履带中间全部填满沉重装甲？</strong></p><p>于是 Pioneer Tractor Company 制造了一个巨大的管状框架，让履带沿外圈运行，而真正需要装甲保护的乘员舱则缩在中央。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/AD1116719-014_Skeleton_Tank--ff-94e40d667c98.6f1h4iz6fn.webp" alt="1918 年美国 骨架坦克 原型" /><figcaption>1918 年美国 骨架坦克 原型</figcaption></figure><p></p><p>美国陆军资料显示，这辆原型车只有大约 9 吨，却仍保持约 25 英尺的车长，希望同时获得：</p><ul>
<li>长车体的跨壕能力；</li>
<li>较小装甲舱带来的重量优势；</li>
<li>比英国重型坦克更好的机动性。</li>
</ul><p>但开放框架同样带来结构复杂、实际防护和维护上的问题，最终只制造了一辆。</p></section><section><h3>Holt Gas-Electric Tank（霍尔特油电坦克）：1918 年就尝试“汽油机发电，再用电机驱动履带”<a href="#holt-gas-electric-tank霍尔特油电坦克1918-年就尝试汽油机发电再用电机驱动履带"><span>#</span></a></h3><p>美国 <strong>霍尔特油电坦克</strong> 则把实验重点放在传动系统。</p><p>它不是让汽油发动机通过传统机械变速箱直接带动履带。</p><p>而是：</p><blockquote><p>汽油发动机
→ 发电机
→ 电动机
→ 左右履带</p></blockquote><p>今天看，这种“油电传动”思路并不陌生。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Holt_prototype_gas_electric_tank--ff-533ac0388492.70b4qttmw8.webp" alt="1918 年美国 霍尔特油电坦克 原型" /><figcaption>1918 年美国 霍尔特油电坦克 原型</figcaption></figure><p></p><p>问题在于，当时的发动机、电气设备和整车重量很难实现理想匹配。</p><p>车辆过重而动力不足，越野性能没有达到预期，最终没有进入量产。</p><p>圣沙蒙等一战车辆也尝试了电传动，希望改善重型履带车辆的动力分配。</p></section><section><h3>Ford 3-Ton M1918（福特3吨M1918坦克）：另一种极端——坦克能不能小到像一辆装甲拖拉机？<a href="#ford-3-ton-m1918福特3吨m1918坦克另一种极端坦克能不能小到像一辆装甲拖拉机"><span>#</span></a></h3><p>和 K-Wagen、FCM 2C 相比，美国的 <strong>福特3吨M1918坦克</strong> 走了相反极端的路线。</p><p>它是一辆非常小的双人轻型坦克，使用成熟的汽车工业部件，设计目标之一就是廉价和大规模生产。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261004-WWI-ST2-industrial-war-weapons/Ford_Three-ton--ff-b1eb18d47bfb.6poaxoeeq5.webp" alt="1918 年 Aberdeen Proving Ground 的 福特3吨M1918坦克" /><figcaption>1918 年 Aberdeen Proving Ground 的 福特3吨M1918坦克</figcaption></figure><p></p><p>美国一度为它准备了极大的生产计划，但战事结束、测试结果和军方需求变化让原有计划失去了实施的必要。</p><p>这时再回头看 1918 年，会发现各国对“坦克应该是什么”仍然没有统一答案：</p>

<table><thead><tr><th>方案</th><th>设计思路</th></tr></thead><tbody><tr><td>FCM 2C / K-Wagen</td><td>把坦克做成能独立压倒防线的“陆地战舰”</td></tr><tr><td>A7V-U</td><td>模仿已经验证跨壕能力的英国菱形布局</td></tr><tr><td>LK II</td><td>追求更轻、更容易生产的装甲车辆</td></tr><tr><td>沙皇坦克</td><td>用巨大车轮直接跨越障碍</td></tr><tr><td>Boirault Machine</td><td>让整个框架像巨型履带一样翻转</td></tr><tr><td>骨架坦克</td><td>保留长履带轮廓，但把中间“掏空”减重</td></tr><tr><td>霍尔特油电坦克</td><td>用电传动解决重型履带车辆的动力分配</td></tr><tr><td>Ford 3-Ton</td><td>尽可能简单、小型并利用汽车工业量产</td></tr></tbody></table><hr /></section></section></section>
<section><h1>从 1914 到 1918：四年战争究竟改变了什么？<a href="#从-1914-到-1918四年战争究竟改变了什么"><span>#</span></a></h1><p>战争改变了武器的设计目标，也推动了配发规模与战术分工的变化：</p>

<table><thead><tr><th>1914 年更典型的思路</th><th>战争中的变化</th><th>1918 年看到的结果</th></tr></thead><tbody><tr><td>每名步兵主要依靠制式步枪</td><td>狙击镜、枪榴弹等附件增加</td><td>同一支步枪开始承担更专业的任务</td></tr><tr><td>手枪主要作为军官与专业人员副武器</td><td>枪托、大容量供弹与近距离突击需求结合</td><td>LP 08 等武器把手枪推向“高容量近战火力”</td></tr><tr><td>重机枪作为少量支援装备</td><td>自动火力不断下放</td><td>MG 08/15、刘易斯、绍沙、BAR 等形成多条轻型化路线</td></tr><tr><td>自动武器仍主要发射步枪弹</td><td>手枪弹的较小后坐力与全自动火力开始结合</td><td>Villar Perosa、OVP 与 MP 18 展示冲锋枪逐渐成形</td></tr><tr><td>速射野战炮足以承担主要任务</td><td>堑壕要求高爆弹、更大口径与高弧弹道</td><td>榴弹炮、重炮、迫击炮和复杂引信体系大量发展</td></tr><tr><td>化学战依赖气瓶和风向</td><td>炮弹与工程投射装置发展</td><td>利文斯投射器等让毒剂投放更便于按作战计划实施</td></tr><tr><td>飞机首先是观察平台</td><td>固定前射同步机枪、活动枪架、炸弹架出现</td><td>战斗机、侦察机、轰炸机开始明确分工</td></tr><tr><td>履带装甲车只是实验概念</td><td>英法反复试验车体、履带和武器布局</td><td>马克 V 型、“惠比特”、雷诺 FT 显示不同坦克角色开始分化</td></tr><tr><td>坦克几乎没有专门天敌</td><td>穿甲步枪弹、火炮直射和障碍物被用于反坦克</td><td>K 弹之后出现专用 M1918 反坦克步枪</td></tr><tr><td>武器形态仍在剧烈试错</td><td>半自动步枪、自动卡宾枪、超重坦克、轻型坦克等路线同时竞争</td><td>一部分在战间期继续发展，另一部分只留下原型和技术经验</td></tr></tbody></table><p>一战把许多已有技术推向大规模应用，也淘汰了大量未能适应前线的方案。一件设计要改变战争，还须能够批量生产、由部队掌握，并持续获得零件、维修与弹药供应。四年间形成的标准装备，正是战场需求与工业条件共同筛选的结果。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>Royal Armouries — Arms of the First World War</strong><br />
系统介绍一战时期 Lee-Enfield、Lebel、Mauser 系步枪，以及 Vickers、MG 08、Lewis 等机枪的发展、结构与战场用途，是本文步枪与机枪部分的重要参考。<br />
<a href="https://royalarmouries.org/objects-and-stories/stories/arms-of-the-first-world-war" target="_blank">Royal Armouries：Arms of the First World War</a></p>
</li>
<li>
<p><strong>Royal Armouries — Trench Warfare</strong><br />
提供 SMLE、Gewehr 98、Lebel、Mosin-Nagant 等一战步枪的基础资料，并讨论堑壕环境对步枪和近战武器使用的影响。<br />
<a href="https://royalarmouries.org/schools/learning-resources/trench-warfare" target="_blank">Royal Armouries：Trench Warfare</a></p>
</li>
<li>
<p><strong>National Army Museum — Weapons of the Western Front</strong><br />
综合介绍步枪、机枪、炮兵、迫击炮、毒气和坦克在西线的实际作用，用于核对不同武器在战场体系中的位置。<br />
<a href="https://www.nam.ac.uk/explore/weapons-western-front" target="_blank">National Army Museum：Weapons of the Western Front</a></p>
</li>
<li>
<p><strong>National Army Museum — Short Magazine Lee-Enfield Rifle Mk III*</strong><br />
馆藏资料介绍一战期间简化生产的 SMLE Mk III*、10 发弹仓与桥夹装填等特点。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1993-07-154-1" target="_blank">National Army Museum：SMLE Mk III*</a></p>
</li>
<li>
<p><strong>Musée de l’Armée — 75 mm field gun, 1897 model</strong><br />
详细介绍 Canon de 75 modèle 1897 的液气式后坐复进系统、快速射击能力、弹药类型以及其在一战中的优势与局限。<br />
<a href="https://www.musee-armee.fr/en/collections/museum-treasures/75-mm-field-gun-1897-model.html" target="_blank">Musée de l’Armée：75 mm field gun, 1897 model</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Canon de 75 mm modèle 1897</strong><br />
介绍法国 75 炮的后坐系统、炮闩、定装弹药与一战服役背景，用于核对技术结构。<br />
<a href="https://www.awm.gov.au/collection/C240163" target="_blank">Australian War Memorial：Canon de 75 mm modèle 1897</a></p>
</li>
<li>
<p><strong>National Army Museum — 18 Pounder QF Mark 1 guns</strong><br />
介绍 18-pounder 作为英军一战主力野战炮的使用情况及索姆河战役中的巨大弹药消耗。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2004-09-3-12" target="_blank">National Army Museum：A column of 18 Pounder QF Mark 1 guns</a></p>
</li>
<li>
<p><strong>Australian War Memorial — 3 inch Stokes Mortar</strong><br />
馆藏资料与一战时期操作手册用于核对 Stokes 迫击炮的结构、弹药与西线使用情况。<br />
<a href="https://www.awm.gov.au/collection/C212197" target="_blank">Australian War Memorial：3 inch Stokes Mortar</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Handbook for Stokes trench mortar, 3”, Mark I</strong><br />
1916 年英军总参谋部发布的 Stokes 迫击炮手册，提供当时官方操作与训练背景。<br />
<a href="https://www.awm.gov.au/collection/C2602129" target="_blank">Australian War Memorial：Handbook for Stokes trench mortar</a></p>
</li>
<li>
<p><strong>Smithsonian National Air and Space Museum — The Evolution of World War I Aircraft</strong><br />
介绍 Fokker Eindecker 的同步射击装置、Sopwith Camel、SPAD XIII、Fokker D.VII 等一战战斗机技术演进。<br />
<a href="https://airandspace.si.edu/explore/stories/world-war-i-laboratory-air" target="_blank">National Air and Space Museum：The Evolution of World War I Aircraft</a></p>
</li>
<li>
<p><strong>Smithsonian National Air and Space Museum — SPAD XIII “Smith IV”</strong><br />
介绍 SPAD XIII 的高速、坚固结构、生产规模和战斗运用。<br />
<a href="https://airandspace.si.edu/collection-objects/spad-xiii-smith-iv/nasm_A19200001000" target="_blank">National Air and Space Museum：SPAD XIII</a></p>
</li>
<li>
<p><strong>Smithsonian Institution — Sopwith F.1 Camel</strong><br />
介绍 Camel 的双机枪布局、旋转发动机和一战空战表现。<br />
<a href="https://www.si.edu/object/nasm_A20170105000" target="_blank">Smithsonian：Sopwith F.1 Camel</a></p>
</li>
<li>
<p><strong>The Tank Museum — The First Tanks</strong><br />
介绍 Little Willie、Mother 与 Mark I 的研发过程，以及早期原型跨壕能力的问题。<br />
<a href="https://tankmuseum.org/article/the-first-tanks/" target="_blank">The Tank Museum：The First Tanks</a></p>
</li>
<li>
<p><strong>The Tank Museum — Tank Armament in the First World War</strong><br />
介绍英国 Mark I“雄性 / 雌性”坦克的武器配置与形成原因。<br />
<a href="https://tankmuseum.org/article/tank-armament-first-world-war/" target="_blank">The Tank Museum：Tank Armament in the First World War</a></p>
</li>
<li>
<p><strong>The Tank Museum — Mark IV</strong><br />
介绍 Mark IV 的生产规模、武器配置和康布雷等战斗中的使用。<br />
<a href="https://tankmuseum.org/tank_collection/mark-iv/" target="_blank">The Tank Museum：Mark IV</a></p>
</li>
<li>
<p><strong>The Tank Museum — Mark V</strong><br />
重点介绍 Mark V 的 Ricardo 发动机、周转式转向机构以及相比 Mark IV 的驾驶改进。<br />
<a href="https://tankmuseum.org/article/mark-v/" target="_blank">The Tank Museum：Mark V</a></p>
</li>
<li>
<p><strong>The Tank Museum — The Renault FT: Development and Combat Debut</strong><br />
介绍 Renault FT 的设计理念、1918 年首次实战和其对后世坦克布局的重要影响。<br />
<a href="https://tankmuseum.org/article/ft-17/" target="_blank">The Tank Museum：Renault FT</a></p>
</li>
<li>
<p><strong>The Tank Museum — Action Debut of the A7V Tank</strong><br />
介绍 A7V 的数量、武器配置、1918 年首次战斗与德国坦克部队的规模限制。<br />
<a href="https://tankmuseum.org/article/action-debut-of-the-a7v-tank/" target="_blank">The Tank Museum：Action Debut of the A7V</a></p>
</li>
<li>
<p><strong>National Army Museum — German 13 mm bolt action anti-tank Mauser rifle, 1918</strong><br />
用于核对 Tankgewehr M1918 的反坦克用途与一战末期专用反装甲武器的出现。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1992-08-154-1" target="_blank">National Army Museum：13 mm anti-tank Mauser rifle</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Maxim MG08/15 Light Machine Gun</strong><br />
馆藏资料详细说明 MG 08/15 的短后坐、水冷、弹链供弹结构及其与 MG 08 的技术关系，用于补充德国机枪轻量化路线。<br />
<a href="https://www.awm.gov.au/collection/C239474" target="_blank">Australian War Memorial：MG08/15 Light Machine Gun</a></p>
</li>
<li>
<p><strong>National Army Museum — Bergmann 9 mm MP18-1 submachine gun, 1918</strong><br />
馆藏资料介绍 MP 18 作为早期广泛使用冲锋枪、使用 9 mm 手枪弹并面向壕沟近战的背景。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1992-08-168-1" target="_blank">National Army Museum：Bergmann MP18-1</a></p>
</li>
<li>
<p><strong>Royal Armouries — Arms of the First World War：Sniper rifles and rifle grenades</strong><br />
同一专题中详细介绍一战英军狙击镜、SMLE 狙击改装、德国光学瞄具优势，以及杆式枪榴弹与杯式发射器的发展。<br />
<a href="https://royalarmouries.org/objects-and-stories/stories/arms-of-the-first-world-war" target="_blank">Royal Armouries：Arms of the First World War</a></p>
</li>
<li>
<p><strong>Smithsonian National Museum of American History — Browning Model 1918 Automatic Rifle</strong><br />
馆藏资料用于核对 M1918 BAR 的年代、武器类别与基本历史背景。<br />
<a href="https://americanhistory.si.edu/collections/object/nmah_1590822" target="_blank">Smithsonian：Browning Model 1918 Automatic Rifle</a></p>
</li>
<li>
<p><strong>Australian War Memorial — 9.2 inch Shrapnel and High-Explosive Howitzer Shells</strong><br />
两件剖切炮弹馆藏用于区分一战榴霰弹、高爆弹和不同炮弹用途，并说明火炮可以发射高爆、榴霰、毒气和烟幕等多种弹药。<br />
<a href="https://www.awm.gov.au/collection/C223305" target="_blank">AWM：Sectioned 9.2 inch Shrapnel Shell</a><br />
<a href="https://www.awm.gov.au/collection/C223295" target="_blank">AWM：9.2 inch High-Explosive Shell</a></p>
</li>
<li>
<p><strong>Australian War Memorial — 7.6 cm leichte Minenwerfer</strong><br />
介绍 1914 与 1916 型轻型 Minenwerfer 的膛线、后膛装填、后坐机构与前沿步兵支援用途。<br />
<a href="https://www.awm.gov.au/collection/C212204" target="_blank">AWM：7.6 cm leichter Minenwerfer 1914</a><br />
<a href="https://www.awm.gov.au/collection/C212205" target="_blank">AWM：7.6 cm leichter Minenwerfer 1916</a></p>
</li>
<li>
<p><strong>Imperial War Museums / Canadian War Museum — Livens Projector</strong><br />
展览和馆藏资料说明 Livens Projector 以成组埋设的金属管同时投射毒气弹，作为气瓶与普通炮弹之外的重要化学战投送方式。<br />
同一份 IWM 展览指南还介绍了 tank 一词作为保密代号的来源。<br />
<a href="https://www.iwm.org.uk/sites/default/files/files/2023-10/first_world_war_large_print_guide.pdf" target="_blank">Imperial War Museums：First World War Galleries guide</a><br />
<a href="https://www.warmuseum.ca/collections/artifact/2106440/" target="_blank">Canadian War Museum：Livens Projector Gas Cylinder</a></p>
</li>
<li>
<p><strong>The Tank Museum — French First World War Tanks</strong><br />
介绍 Schneider CA1、Saint-Chamond 与 Renault FT 的发展关系、生产情况和不同布局的优缺点。<br />
<a href="https://tankmuseum.org/article/french-first-world-war-tanks/" target="_blank">The Tank Museum：French First World War Tanks</a></p>
</li>
<li>
<p><strong>The Tank Museum — Whippet</strong><br />
介绍 Medium Mark A Whippet 作为速度更快的中型坦克，用于利用重型坦克打开的突破口，以及其双发动机和机枪配置。<br />
<a href="https://tankmuseum.org/tank-nuts/tank-collection/whippet/" target="_blank">The Tank Museum：Whippet</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Mauser T-Gewehr Anti Tank Rifle</strong><br />
用于核对 13 mm Tankgewehr M1918 的单发栓动结构、钢芯穿甲弹与反坦克用途。<br />
<a href="https://www.awm.gov.au/collection/C240046" target="_blank">Australian War Memorial：Mauser T-Gewehr</a></p>
</li>
<li>
<p><strong>Smithsonian National Air and Space Museum — Dayton-Wright DH-4 / World War I aviation</strong><br />
用于补充一战后期飞机从通用侦察平台向轰炸、战斗等专门任务分化，以及飞机工业化生产的背景。<br />
<a href="https://airandspace.si.edu/collection-objects/dayton-wright-dh-4/nasm_A19190051000" target="_blank">National Air and Space Museum：Dayton-Wright DH-4</a></p>
</li>
<li>
<p><strong>Armémuseum / DigitaltMuseum — Kinas första automatgevär（General Liu Rifle）</strong><br />
瑞典陆军博物馆围绕馆藏 1915 年刘将军步枪试验枪整理的研究文章，涉及刘庆恩、汉阳兵工厂、美国生产设备、1916 年北京测试和 1918 年 Springfield Armory 测试等背景。<br />
<a href="https://preprod.digitaltmuseum.se/0211814746967/kinas-forsta-automatgevar" target="_blank">DigitaltMuseum：Kinas första automatgevär</a></p>
</li>
<li>
<p><strong>Canadian War Museum — Huot-Ross Automatic Rifle</strong><br />
馆藏资料明确说明 Huot 将 Ross 步枪改造成全自动轻机枪，该武器只达到原型阶段，从未大规模生产。<br />
<a href="https://www.warmuseum.ca/firstworldwar/objects-and-photos/weapons-and-ammunition/firearms/huot-ross-automatic-rifle/" target="_blank">Canadian War Museum：Huot-Ross Automatic Rifle</a></p>
</li>
<li>
<p><strong>Royal Armouries — Arms of the First World War：Pedersen Device</strong><br />
介绍 Pedersen Device 的秘密研制、M1903 Mk I 改造、1919 年攻势设想，以及停战后该方案为什么没有继续发展。<br />
<a href="https://royalarmouries.org/objects-and-stories/stories/arms-of-the-first-world-war" target="_blank">Royal Armouries：Arms of the First World War</a></p>
</li>
<li>
<p><strong>Springfield Armory Museum / Wikimedia Commons — Pedersen Device</strong><br />
Springfield Armory Museum 馆藏照片用于核对 Automatic Pistol, Caliber .30, Model of 1918 的实物形态和美国官方命名。<br />
<a href="https://commons.wikimedia.org/wiki/File:U.S._AUTOMATIC_PISTOL,_CAL..30,_MODEL_OF_1918_(PEDERSEN_DEVICE).jpg" target="_blank">Wikimedia Commons：Pedersen Device 馆藏照片</a></p>
</li>
<li>
<p><strong>Cody Firearms Museum — Burton Machine Rifle</strong><br />
馆方说明该枪由 Winchester 工程师 Frank Burton 于 1917—1918 年前后设计，现存样枪曾用于政府测试，但相关档案极少，具体用途仍有不确定性。<br />
<a href="https://centerofthewest.org/2021/02/05/points-west-favorite-firearms/" target="_blank">Cody Firearms Museum：Favorite Firearms — Burton Machine Rifle</a></p>
</li>
<li>
<p><strong>Forgotten Weapons — Experimental German 08/18 Maxim SMG</strong><br />
介绍俄罗斯图拉保存的 9 mm Maxim 式实验自动武器及其多列供弹机构，同时明确指出设计者、正式名称、制造数量和是否实战等关键信息仍然未知。<br />
<a href="https://www.forgottenweapons.com/submachine-guns/experimental-german-0818-maxim-smg/" target="_blank">Forgotten Weapons：Experimental German 08/18 Maxim SMG</a></p>
</li>
<li>
<p><strong>Austrian National Library / Europeana — Hellriegel 1915 试验照片</strong><br />
1915 年 10 月留下的原始测试照片，是研究 Hellriegel 实验自动武器最主要的视觉证据；现由 Austrian National Library / Europeana 数字化公开。<br />
<a href="https://commons.wikimedia.org/wiki/Category:Standsch%C3%BCtze_Hellriegel_M1915" target="_blank">Wikimedia Commons：Standschütze Hellriegel M1915</a></p>
</li>
<li>
<p><strong>Chars français — Char FCM 2C</strong><br />
法国装甲车辆资料站整理 Char 2C 的设计定位、约 69 吨重量、长车体和战后 1921 年正式服役等信息。<br />
<a href="https://www.chars-francais.net/index.php/engins-blindes/chars?catid=13&amp;id=1780%3A1921-char-fcm-2c&amp;view=article" target="_blank">Chars français：1921 Char FCM 2C</a></p>
</li>
<li>
<p><strong>The Tank Museum / Swedish Armour Historical Society — LK II 与 Strv m/21</strong><br />
The Tank Museum 将 LK II 作为一战末期德国轻型坦克发展的一部分；瑞典装甲历史协会依据战争档案整理战后瑞典秘密购买相关零部件并组装为 Strv m/21 的过程。<br />
<a href="https://tankmuseum.org/article/latest-from-the-museums-youtube-channel-2/" target="_blank">The Tank Museum：WW1 &amp; Interwar Tanks in Action</a><br />
<a href="https://www.sphf.se/svenskt-pansar/historia/en_purchase-and-manufacturing-of-stridsvagn-m21-swedish-lk-ii/" target="_blank">SPHF：Purchase of LK II from Germany</a></p>
</li>
<li>
<p><strong>U.S. Army Center of Military History — Experimental American “Skeleton” Tank, 1918</strong><br />
美国陆军官方资料介绍 Skeleton Tank 以管状框架减轻重量、保留长车体跨壕能力，只制造一辆原型车。<br />
<a href="https://history.army.mil/portals/143/Images/Publications/catalog/70-128.pdf" target="_blank">U.S. Army CMH：The Great War — U.S. Army Artifacts</a></p>
</li>
<li>
<p><strong>U.S. Army Center of Military History — U.S. tank experimentation</strong><br />
用于补充一战期间美国本土装甲车辆试验、Ford M1918 3-ton 等项目所处的技术背景。<br />
<a href="https://history.army.mil/Portals/143/Images/Publications/Publication%20By%20Title%20Images/M%20Pdf/CMH_30-23-1.pdf" target="_blank">U.S. Army CMH：Mobility, Shock, and Firepower</a></p>
</li>
<li>
<p><strong>Smithsonian National Museum of American History — Model 1921AC Thompson Submachine Gun</strong><br />
馆方说明汤普森冲锋枪最初就是为第一次世界大战堑壕作战开发，但最早型号在战争结束时尚未准备好投入实战；用于本文判断 Annihilator 项目与停战时间关系时采用这一较谨慎的表述。<br />
<a href="https://americanhistory.si.edu/collections/object/nmah_416040" target="_blank">Smithsonian：Model 1921AC Thompson Submachine Gun</a></p>
</li>
<li>
<p><strong>American Rifleman — A Look Back at the Thompson Submachine Gun</strong><br />
根据 Auto-Ordnance 早期资料梳理 John T. Thompson、Blish 原理、.45 ACP 自动卡宾枪方案与 Annihilator I 的发展，并记载 1918 年已经存在可测试原型。<br />
<a href="https://www.americanrifleman.org/content/a-look-back-at-the-thompson-submachine-gun/" target="_blank">American Rifleman：A Look Back at the Thompson Submachine Gun</a></p>
</li>
<li>
<p><strong>American Rifleman — The Thompson Submachine Gun: Model Of 1919</strong><br />
进一步整理 Persuader 的弹带供弹试验、Annihilator Trial Mechanism、Annihilator I 与战后 Model of 1919 系列的演进，用于区分一战时期试验概念与后来成熟的汤普森型号。<br />
<a href="https://www.americanrifleman.org/content/the-thompson-submachine-gun-model-of-1919/" target="_blank">American Rifleman：The Thompson Submachine Gun: Model Of 1919</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Model 1917 Enfield Rifle</strong><br />
说明 M1917 由英国 P14 改为使用美国 .30-06 弹药，并明确指出它实际上比 M1903 Springfield 更普遍地装备美国远征军。<br />
<a href="https://www.awm.gov.au/collection/C238173" target="_blank">Australian War Memorial：Model 1917 Enfield Rifle</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Springfield M1903 Rifle</strong><br />
馆藏 1917 年制造的 M1903，用于核对其一战服役、毛瑟技术影响以及美军战前制式步枪背景。<br />
<a href="https://www.awm.gov.au/collection/C235820" target="_blank">Australian War Memorial：Springfield M1903 Rifle</a></p>
</li>
<li>
<p><strong>NRA National Firearms Museum — Mosin-Nagant Model 1891</strong><br />
介绍 M1891 作为俄军一战标准步枪、5 发弹仓和俄国制式步枪形成背景。<br />
<a href="https://www.nramuseum.org/guns/the-galleries/world-war-i-and-firearms-innovation/case-32-wwi-america-and-the-allies/mosin-nagant-model-1891-bolt-action-rifle.aspx" target="_blank">NRA Museum：Mosin-Nagant Model 1891</a></p>
</li>
<li>
<p><strong>Heeresgeschichtliches Museum / Habsburger.net — Mannlicher M1895</strong><br />
由维也纳军事史博物馆合作整理奥匈制式步枪发展背景，用于补充 M1895 在奥匈军中的位置。<br />
<a href="https://ww1.habsburger.net/en/chapters/lorenz-gun-koeniggraetz-ordnance-weapon-m1895" target="_blank">Habsburger.net：From the Lorenz Gun of Königgrätz to the Ordnance Weapon M1895</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Carcano M1891 Rifle</strong><br />
明确说明 Carcano M1891 是意大利陆军一战标准步兵武器。<br />
<a href="https://www.awm.gov.au/collection/C238165" target="_blank">Australian War Memorial：Carcano M1891</a></p>
</li>
<li>
<p><strong>Australian War Memorial — RSC Model 1917 Rifle</strong><br />
介绍法国 RSC M1917 半自动步枪的 1917 年有限生产，并指出其选择考虑了不打乱弹药生产和补给体系。<br />
<a href="https://www.awm.gov.au/collection/RELAWM03746" target="_blank">Australian War Memorial：RSC Model 1917 Rifle</a></p>
</li>
<li>
<p><strong>Tula State Museum of Weapons — Fedorov Automatic Rifle</strong><br />
介绍费德洛夫自动步枪的 25 发弹匣、单发 / 连发选择、6.5 mm 日本弹药，以及第 189 伊兹梅尔团专门连队于 1916 年 12 月赴前线的记录。<br />
<a href="https://heroes-arms.ru/en/fedorov.html" target="_blank">Tula State Museum of Weapons：Fedorov Automatic Rifle</a></p>
</li>
<li>
<p><strong>Royal Armouries — St. Étienne M1907 与 Hotchkiss M1914</strong><br />
详细比较圣艾蒂安复杂导气机构在堑壕环境中的可靠性问题，以及 Hotchkiss M1914 的导气、风冷和金属弹板供弹路线。<br />
<a href="https://royalarmouries.org/objects-and-stories/stories/arms-of-the-first-world-war" target="_blank">Royal Armouries：Arms of the First World War</a></p>
</li>
<li>
<p><strong>National Army Museum — French machine-gunner with St. Étienne Mle 1907</strong><br />
馆方指出圣艾蒂安在泥泞堑壕中容易停顿，并逐渐被更容易制造、可靠性更高的 Hotchkiss M1914 取代。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2002-02-589--83" target="_blank">National Army Museum：St. Étienne Mle 1907</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Schwarzlose M07/12 Machine Gun</strong><br />
用于核对 Schwarzlose M07/12 作为奥匈军一战重机枪、水冷结构与 1917 年制造实例。<br />
<a href="https://www.awm.gov.au/collection/C253768" target="_blank">Australian War Memorial：Schwarzlose M07/12</a></p>
</li>
<li>
<p><strong>Italian Ministry of Culture — FIAT–Revelli Modello 1914</strong><br />
意大利国家文化遗产目录中的 M1914 实物记录，用于核对其水冷结构、军用型号和机枪技术背景。<br />
<a href="https://catalogo.cultura.gov.it/detail/ScientificOrTechnologicalHeritage/0400618414" target="_blank">Catalogo Generale dei Beni Culturali：FIAT–Revelli M1914</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Madsen M1902/04 Light Machine Gun</strong><br />
馆藏记录显示一挺 M1902/04 于 1916 年 7 月 23 日在波济耶尔从德军手中缴获，证明麦德森确实进入一战西线使用。<br />
<a href="https://www.awm.gov.au/collection/C240043" target="_blank">Australian War Memorial：Madsen M1902/04</a></p>
</li>
<li>
<p><strong>United States Army — Arming the Army, John M. Browning</strong><br />
介绍 Browning M1917 于 1917 年 5 月通过陆军试验、采用水冷弹链供弹并迅速投入大规模生产。<br />
<a href="https://www.army.mil/article/15705/arming_the_army_john_m_browning" target="_blank">U.S. Army：Arming the Army, John M. Browning</a></p>
</li>
<li>
<p><strong>NRA National Firearms Museum — Winchester Model 1897 Trench Shotgun</strong><br />
馆藏一战军用 M1897，说明其使用鹿弹并获得“trench broom（战壕扫帚）”称呼。<br />
<a href="https://www.nramuseum.org/guns/the-galleries/world-war-i-and-firearms-innovation/case-33-wwi-more-arms/us-winchester-model-1897-slide-action-trench-shotgun.aspx" target="_blank">NRA Museum：Winchester Model 1897 Trench Shotgun</a></p>
</li>
<li>
<p><strong>U.S. Department of State — 1918 German Protest Against American Shotguns</strong><br />
收录德国通过瑞士提出的正式抗议，以及美国 1918 年 9 月 28 日拒绝该抗议、坚持霰弹枪合法使用的外交文件。<br />
<a href="https://history.state.gov/historicaldocuments/frus1918Supp02/d911" target="_blank">Office of the Historian：German protest</a><br />
<a href="https://history.state.gov/historicaldocuments/frus1918Supp02/d912" target="_blank">Office of the Historian：U.S. reply</a></p>
</li>
<li>
<p><strong>National Army Museum — Webley Mk VI Service Revolver</strong><br />
说明 Webley 左轮是一战英军与英联邦军官的标准手枪之一，也配发给机枪组、坦克乘员、飞行员并用于堑壕突袭。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1965-10-204-39-1" target="_blank">National Army Museum：Webley Mk VI</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Mauser C96 “Red 9”</strong><br />
说明德国为弥补 P08 产量不足订购 9 mm C96，并以握把红色“9”区分原有 7.63 mm 型。<br />
<a href="https://www.awm.gov.au/collection/RELAWM15944" target="_blank">Australian War Memorial：Mauser C96 Red 9</a></p>
</li>
<li>
<p><strong>Smithsonian National Museum of American History — M1917 Revolvers</strong><br />
说明美国因 M1911 产量不足，以 Colt 和 Smith &amp; Wesson M1917 左轮补充 .45 ACP 手枪供应，并使用半月形弹夹解决无底缘弹定位与退壳。<br />
<a href="https://americanhistory.si.edu/collections/object/nmah_416021" target="_blank">Smithsonian：Colt M1917 Revolver</a><br />
<a href="https://americanhistory.si.edu/collections/object/nmah_416015" target="_blank">Smithsonian：Smith &amp; Wesson M1917 Revolver</a></p>
</li>
<li>
<p><strong>U.S. Army Corps of Engineers — World War One Historical Artifacts</strong><br />
馆方将 M1917 trench knife 描述为几乎完全以格斗为目的设计的军用堑壕刀，并特别指出其护拳结构。<br />
<a href="https://www.usace.army.mil/About/History/Exhibits/World-War-One/WWI-Historical-Artifacts/" target="_blank">U.S. Army Corps of Engineers：World War One Artifacts</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Lewis Gun Anti-Aircraft Sight</strong><br />
保存 1917—1918 年前线制作的 Lewis 机枪高射瞄具，其不同环形准星用于估算对不同高度和速度飞机射击时的提前量。<br />
<a href="https://www.awm.gov.au/collection/C114423" target="_blank">Australian War Memorial：Lewis Gun Anti Aircraft Sight</a></p>
</li>
<li>
<p><strong>Royal Armouries — Lange Pistole 08、Villar Perosa 与 Bergmann MP 18,I</strong><br />
Royal Armouries 的一战武器专题详细说明 LP 08 的枪托与 32 发弹鼓、突击用途及其对 MP 18 的启发；同时介绍 Villar Perosa 在意军中的短程轻机枪式使用方式，以及 MP 18 于 1918 年抵达突击部队前线的背景。<br />
<a href="https://royalarmouries.org/objects-and-stories/stories/arms-of-the-first-world-war" target="_blank">Royal Armouries：Arms of the First World War</a></p>
</li>
<li>
<p><strong>Library of Congress — From the Submachine Gun to the Assault Rifle</strong><br />
美国国会图书馆研究指南概述一战时期欧洲对全自动手枪、机器手枪和冲锋枪的探索，包括 Steyr M1912/P16 与 Bergmann MP 18，并用于区分“机器手枪”与后来成熟冲锋枪形态。<br />
<a href="https://guides.loc.gov/submachine-gun-to-the-assault-rifle" target="_blank">Library of Congress：From the Submachine Gun to the Assault Rifle</a></p>
</li>
<li>
<p><strong>Catalogo Generale dei Beni Culturali — Moschetto automatico OVP mod. 1918</strong><br />
意大利国家文化遗产目录记录一支 1918 年为航空用途生产的 OVP：由 Villar Perosa M1915 的一套枪管—机匣组件、新击发机构和木制枪托组成，并通过双扳机实现单发 / 连发选择。<br />
<a href="https://catalogo.beniculturali.it/detail/ScientificOrTechnologicalHeritage/0400618424" target="_blank">Catalogo Generale：OVP mod. 1918</a></p>
</li>
<li>
<p><strong>Catalogo Generale dei Beni Culturali — Villar Perosa mod. 1915</strong><br />
意大利国家文化遗产目录保存带 1916 年采用两脚架的 Villar Perosa M1915 实物记录，用于核对其双联结构、25 发弹匣与意大利军用配置。<br />
<a href="https://catalogo.cultura.gov.it/detail/ScientificOrTechnologicalHeritage/0400618432" target="_blank">Catalogo Generale：Villar Perosa mod. 1915</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Steyr M1912 Pistol</strong><br />
澳大利亚战争纪念馆收藏一支 1916 年制造、1917 年在兴登堡防线缴获的 Steyr M1912，用于核对奥匈制式半自动手枪的固定弹仓、桥夹装填与一战实际使用。<br />
<a href="https://www.awm.gov.au/collection/C2089781" target="_blank">Australian War Memorial：Steyr M1912</a></p>
</li>
<li>
<p><strong>Mémoire 14-45 — Ruby pistol</strong><br />
法国地方博物馆联合数据库中的 Ruby 实物记录，明确说明该 7.65 mm 半自动手枪在西班牙制造并供法国陆军使用，用于补充法军战争时期大量外购手枪的背景。<br />
<a href="https://www.memoire14-45.eu/en/notice/1993-3-003-ruby-musee-des-canonniers-sedentaires-lille-417c1433-93b4-4df9-86a6-e2bbb408db96" target="_blank">Mémoire 14-45：Ruby pistol</a></p>
</li>
<li>
<p><strong>Canadian War Museum — First World War Artillery</strong><br />
教育资料第 15 页说明 18 磅炮按弹丸重量命名。<br />
<a href="https://www.museedelaguerre.ca/cwm/images/colbook/colbook%288.5x11%29.pdf#page=15" target="_blank">First World War Artillery</a></p>
</li>
<li>
<p><strong>SAAMI — Gauge</strong><br />
关于霰弹枪号数与一磅铅球数量之间的传统计量关系。<br />
<a href="https://saami.org/glossary/gauge/" target="_blank">SAAMI：Gauge</a></p>
</li>
<li>
<p><strong>SAAMI — Designation: Metric Cartridge</strong>
术语表说明公制弹药名称通常由名义弹径与弹壳长度组成，单位为毫米。
<a href="https://saami.org/glossary/designation-metric-cartridge/" target="_blank">SAAMI：公制弹药命名</a></p>
</li>
<li>
<p><strong>Smithsonian National Air and Space Museum — Power and Control in the Air</strong>
介绍旋转式发动机的固定曲轴、随螺旋桨转动的发动机本体、散热特点与陀螺效应带来的操控问题。
<a href="https://airandspace.si.edu/explore/stories/power-and-control-air" target="_blank">史密森尼航空航天博物馆：飞行中的动力与操控</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>瑞典陆军博物馆收藏的 李-恩菲尔德 Mk III 步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Lee-Enfield_Mk_III_%28No_1_Mk_3%29_-_AM.032056_%28LHS%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。瑞典陆军博物馆 Armémuseum / DigitaltMuseum 馆藏与图像；馆方将该图像释放至公有领域。</p>
</li>
<li>
<p><strong>一战时期安装 Pattern 1907（1907式）刺刀的李-恩菲尔德步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:SMLE_rifle_with_bayonet_fixed.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏，编号 Q 70901；摄影者不详，拍摄于 1914—1918 年；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>德国毛瑟98式步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Gewehr_98.jpg" target="_blank">Wikimedia Commons 原图页</a>。原物及图像来自瑞典陆军博物馆 Armémuseum / DigitaltMuseum；Commons 页面标记为 <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a> 授权。</p>
</li>
<li>
<p><strong>法国勒贝尔 Mle 1886 步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Lebel_Mle._1886.JPG" target="_blank">Wikimedia Commons 原图页</a>。原物及图像来自瑞典陆军博物馆 Armémuseum / DigitaltMuseum；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>瑞典陆军博物馆收藏的俄国莫辛-纳甘 M1891 步兵步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mosin-Nagant_M1891_-_Ryssland_-_AM.032971.jpg" target="_blank">Wikimedia Commons 原图页</a>。Armémuseum（瑞典陆军博物馆）馆藏并提供图像；DigitaltMuseum 来源；馆方将图像释放至公有领域。</p>
</li>
<li>
<p><strong>瑞典陆军博物馆收藏的奥匈曼利夏 M1895 直拉式步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mannlicher_M1895_from_the_Swedish_Army_Museum.jpg" target="_blank">Wikimedia Commons 原图页</a>。Armémuseum（瑞典陆军博物馆）馆藏与图像；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>瑞典陆军博物馆收藏的意大利 Carcano M1891 步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Carcano_M1891.jpg" target="_blank">Wikimedia Commons 原图页</a>。Armémuseum（瑞典陆军博物馆）馆藏与图像；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>瑞典陆军博物馆收藏的美国 M1917 Enfield 步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:M1917_Enfield_right_side.jpg" target="_blank">Wikimedia Commons 原图页</a>。Armémuseum（瑞典陆军博物馆）馆藏与图像；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>保存至今的法国 RSC M1917 半自动步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:FSA-1917-detoured.jpg" target="_blank">Wikimedia Commons 原图页</a>。Quickload 摄制，拍摄对象为法国 RSC/FSA 1917 半自动步枪；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>芬兰军事博物馆收藏的 Fedorov Avtomat M1916 自动步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Fedorov_avtomat.jpg" target="_blank">Wikimedia Commons 原图页</a>。MKFI 摄于芬兰军事博物馆；作者将照片释放至公有领域。</p>
</li>
<li>
<p><strong>1918 年 5 月，法国 Badonviller，一支带光学瞄准镜并经过伪装的美军 Springfield 狙击步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:American_First_World_War_Official_Exchange_Collection_Q103350.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国官方摄影人员摄；Imperial War Museums 馆藏编号 Q 103350 / 美国档案编号 111-SC-13239；美国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>一战期间法国士兵向美军海军陆战队演示 VB 枪榴弹发射器</strong>：<a href="https://commons.wikimedia.org/wiki/File:111-SC-2123_-_French_Soldiers_demonstrating_use_of_rifle_grenades_-_NARA_-_55165599_%28cropped%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国国家档案馆（NARA）馆藏，NAID 55165599；美国军事官方照片，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年 Viven-Bessières 枪榴弹结构图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Gewehr-Granate_VB.jpg" target="_blank">Wikimedia Commons 原图页</a>。Hmaag 根据 1916 年结构资料整理；以 CC0 方式释出。</p>
</li>
<li>
<p><strong>博物馆收藏的德国 Luger P08（鲁格 P08）半自动手枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Luger_P08_%286971793777%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Askild Antonsen 摄；原图来自 Flickr，经 Commons 审核为 <a href="https://creativecommons.org/licenses/by/2.0/" target="_blank">CC BY 2.0</a> 授权。</p>
</li>
<li>
<p><strong>美国陆军 1912 年手册中的 Colt M1911 半自动手枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Colt_M1911_Pistol_from_1912_Army_manual.png" target="_blank">Wikimedia Commons 原图页</a>。来自美国陆军军械部 1912 年 M1911 官方手册图版，后经数字整理；美国联邦政府作品，公有领域。</p>
</li>
<li>
<p><strong>安装木制枪托与 32 发 Trommelmagazin 08 弹鼓的 Lange Pistole 08（LP 08，“炮兵鲁格”）</strong>：<a href="https://commons.wikimedia.org/wiki/File:Arty08.jpg" target="_blank">Wikimedia Commons 原图页</a>。Kar98 摄 / 上传，展示装有木制枪托与 32 发 Trommelmagazin 08 弹鼓的 Lange Pistole 08；Commons 文件页提供 GFDL 与 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>1916 年索姆河战役期间的英军 维克斯重机枪组</strong>：<a href="https://commons.wikimedia.org/wiki/File:Vickers_machine_gun_crew_with_gas_masks.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 3995；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>德国 MG 08 重机枪与滑橇式枪架</strong>：<a href="https://commons.wikimedia.org/wiki/File:Maschinengewehr_08_%28MG_08%29_on_sled_mount_-_Braunschweigisches_Landesmuseum_-_DSC04729.JPG" target="_blank">Wikimedia Commons 原图页</a>。Daderot 摄于德国 Braunschweigisches Landesmuseum；Commons 文件页将该图标记为公有领域。</p>
</li>
<li>
<p><strong>巴黎陆军博物馆收藏的 St. Étienne Mle 1907 机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mitrailleuse_Saint-Etienne_mod%C3%A8le_1907%2C_Mus%C3%A9e_de_l%27Arm%C3%A9e.jpg" target="_blank">Wikimedia Commons 原图页</a>。Rama 摄于巴黎陆军博物馆；按 <a href="https://creativecommons.org/licenses/by-sa/2.0/fr/deed.en" target="_blank">CC BY-SA 2.0 France</a> 等许可授权。</p>
</li>
<li>
<p><strong>博物馆收藏的 Hotchkiss M1914 重机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Hotchkiss%2C_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。Stanislav Kozlovskiy 摄于塞瓦斯托波尔黑海舰队博物馆；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>罗马步兵博物馆收藏的 Schwarzlose M07/12 重机枪、弹箱与布质弹链</strong>：<a href="https://commons.wikimedia.org/wiki/File:Schwarzlose_MG_M07_12.jpg" target="_blank">Wikimedia Commons 原图页</a>。Rabax63 摄于罗马步兵博物馆；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>1918 年皮亚韦河战线，意大利士兵搬运 FIAT–Revelli Modello 1914 重机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Italian_Army_-_World_War_I_-_Italian_troops_carry_a_Fiat%E2%80%93Revelli_Modello_1914_machine_gun_on_the_Piave_front%2C_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Italian Army 提供的一战皮亚韦河战线照片；Commons VRTS 已确认授权，按 <a href="https://creativecommons.org/licenses/by/2.5/" target="_blank">CC BY 2.5</a> 使用。</p>
</li>
<li>
<p><strong>美国陆军中尉 Val Browning 与 Browning M1917 重机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Val_Browning_M1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国陆军历史照片，Army Heritage and Education Center 来源；美国联邦政府作品，公有领域。</p>
</li>
<li>
<p><strong>美国陆军通信兵资料中的 1918 年 Browning M1917 水冷重机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Browning_Heavy_Machine_Gun%2C_water_cooled%2C_M1917%2C_1918%2C_WWI_%2832916860696%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国陆军通信兵原始照片，由 National Museum of the U.S. Navy 提供；美国政府作品，公有领域。</p>
</li>
<li>
<p><strong>1916 年索姆河战役中英军展示缴获的德军武器，其中包括一挺麦德森轻机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Battle_of_the_Somme%2C_July-november_1916_Q132.jpg" target="_blank">Wikimedia Commons 原图页</a>。Royal Engineers No. 1 Printing Company 摄于 1916 年 7 月；Imperial War Museums 馆藏编号 Q 132；英国政府作品，公有领域。照片中的缴获武器包括德军使用的麦德森轻机枪。</p>
</li>
<li>
<p><strong>1918 年阿兹布鲁克战役期间的英军 刘易斯轻机枪组</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_Lewis_gun_team_Battle_of_Hazebrouck_1918_IWM_Q_10902.jpg" target="_blank">Wikimedia Commons 原图页</a>。T. K. Aitken 少尉摄；Imperial War Museums 馆藏，编号 Q 10902；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年索姆地区法军士兵操作 绍沙轻机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:French_soldier_with_Chauchat_taken_by_Jacques_Ridel_in_Le_Monchel_May_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Jacques Ridel 摄于 1918 年 5 月；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>博物馆收藏的绍沙Mle 1915 轻机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Chauchat_1_%2850643241222%29_%28cropped%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。National Guard Militia Museum 提供图像；Commons 页面标记为美国联邦政府作品 / 公有领域。</p>
</li>
<li>
<p><strong>1917 年卡波雷托战役中的德军突击分队，携带 MG 08/15</strong>：<a href="https://commons.wikimedia.org/wiki/File:Plotone_d%27assalto_tedesco_con_MG0815.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详，1917 年；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>博物馆保存的 MG 08/15 轻量化马克沁机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:MG_08-15.JPG" target="_blank">Wikimedia Commons 原图页</a>。Richard Huber 摄于 2010 年德国展览；按 Commons 文件页所列 Creative Commons 许可使用。</p>
</li>
<li>
<p><strong>1918 年 2 月，John Browning 之子 Val Browning 中尉手持 M1918 BAR</strong>：<a href="https://commons.wikimedia.org/wiki/File:Val_Browning_M1918_BAR.jpg" target="_blank">Wikimedia Commons 原图页</a>。Dreyfuss 摄于 1918 年 2 月 19 日；美国陆军官方照片，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>Villar Perosa M1915 双联手枪弹自动武器</strong>：<a href="https://commons.wikimedia.org/wiki/File:Villar-Perosa_M15.jpg" target="_blank">Wikimedia Commons 原图页</a>。Atirador 摄于 2008 年，展示 Villar Perosa Modello 1915 双联自动武器；Commons 文件页提供 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 与 GFDL 授权。</p>
</li>
<li>
<p><strong>由 Villar Perosa 单侧机构发展而来的 OVP Modello 1918</strong>：<a href="https://commons.wikimedia.org/wiki/File:Ovp_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Commons 文件页标注来源与作者为 Beretta、日期为 1918 年；页面按意大利摄影作品期限及 PD-1996 标记为公有领域。</p>
</li>
<li>
<p><strong>Braunschweigisches Landesmuseum 收藏的 Bergmann MP 18,I，配 32 发蜗牛形弹鼓</strong>：<a href="https://commons.wikimedia.org/wiki/File:Bergmann_MP18.1_submachine_gun_with_drum_magazine%2C_Theodor_Bergmann%2C_Suhl%2C_Germany%2C_1918-1920_AD_-_Braunschweigisches_Landesmuseum_-_DSC04716.JPG" target="_blank">Wikimedia Commons 原图页</a>。Daderot 摄于 Braunschweigisches Landesmuseum；Commons 文件页标明为可自由复用图像。</p>
</li>
<li>
<p><strong>法国 M1897式75毫米野战炮（法国75炮）</strong>：<a href="https://commons.wikimedia.org/wiki/File:French_75_mm_field_gun_and_limber_-_Imperial_War_Museum_1.jpg" target="_blank">Wikimedia Commons 原图页</a>。Emőke Dénes 摄于 Imperial War Museum London；Commons 页面提供 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 等许可。</p>
</li>
<li>
<p><strong>1914 年加拿大瓦尔卡捷营地的 QF 18磅炮 野战炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:QF_18-pounder_Valcartier_Quebec_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。1914 年拍摄；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>都柏林 Collins Barracks 馆藏的 QF 18磅炮 野战炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:QF_18_Pounder_at_Collins_Barracks.jpg" target="_blank">Wikimedia Commons 原图页</a>。Emperor Gibus the 3rd 摄；按 <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a> 授权。</p>
</li>
<li>
<p><strong>1918 年摄影资料中的德国 7.7 cm FK 96 n.A. 野战炮与弹药</strong>：<a href="https://commons.wikimedia.org/wiki/File:7.7_cm_FK_96_n.A._With_Ammunition.jpeg" target="_blank">Wikimedia Commons 原图页</a>。1919 年《Field Artillery Journal》转引的 1918 年照片；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>Battlefields Park 展出的 7.7 cm FK 96 n.A. 野战炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:7.7_cm_FK_96_n.A._Battlefields_Park_01.jpg" target="_blank">Wikimedia Commons 原图页</a>。DXR 摄；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>1917 年阿拉斯战役前，英军 9.2 英寸榴弹炮隐藏在伪装网下</strong>：<a href="https://commons.wikimedia.org/wiki/File:9.2inchHowitzer91stBatteryRGAArras1April1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 6460；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1915 年英军 18磅炮 Mk VI 榴霰弹结构图，可见内部大量弹丸</strong>：<a href="https://commons.wikimedia.org/wiki/File:18pdrShrapnelDiagram1.jpg" target="_blank">Wikimedia Commons 原图页</a>。来源为 1915 年《Shrapnel shell manufacture》，基于英国政府军械资料；公有领域。</p>
</li>
<li>
<p><strong>1916 年英军 18磅炮 高爆弹结构图</strong>：<a href="https://commons.wikimedia.org/wiki/File:18pdrHEShellDiagram.jpg" target="_blank">Wikimedia Commons 原图页</a>。来源为 1916 年《High-explosive shell manufacture》；美国版权到期，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年 7 月，皇家苏格兰团突击队领取米尔斯手榴弹</strong>：<a href="https://commons.wikimedia.org/wiki/File:11th_Royal_Scots_raiding_party_12-07-1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。L. F. Bartholomew 摄；Imperial War Museums 馆藏，编号 Q 11750；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>巴黎陆军博物馆收藏的英国 Mills Bomb Mk I（米尔斯手榴弹）</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mills_bomb_Mk_I-997.493-IMG_7525-white.jpg" target="_blank">Wikimedia Commons 原图页</a>。Rama 摄，拍摄对象为巴黎陆军博物馆（Musée de l’Armée）馆藏；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/fr/deed.en" target="_blank">CC BY-SA 3.0 France</a> 等许可授权。</p>
</li>
<li>
<p><strong>原装1917型德国木柄手榴弹</strong>：<a href="https://commons.wikimedia.org/wiki/File:Stielhandgranate17.JPG" target="_blank">Wikimedia Commons 原图页</a>。Snipersnoop 摄；文件说明明确标注弹体为原装 1917 型 木柄手榴弹、拉绳为复制品；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 等许可授权。</p>
</li>
<li>
<p><strong>美国国家枪械博物馆收藏、安装刺刀的 Winchester Model 1897 堑壕霰弹枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:M1897_with_bayonet.jpg" target="_blank">Wikimedia Commons 原图页</a>。Amendola90 摄于美国 National Firearms Museum；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>一战时期的3英寸斯托克斯迫击炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Portuguese_Expeditionary_Corps_on_the_Western_Front,_1917-1918_Q6440.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 6440；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>布鲁塞尔军事博物馆收藏的德国 7.58 cm leichter 掷雷器</strong>：<a href="https://commons.wikimedia.org/wiki/File:German_7.58_cm_minenwerfer.jpg" target="_blank">Wikimedia Commons 原图页</a>。Max Smith 摄于 2006 年；作者将照片释放至公有领域。</p>
</li>
<li>
<p><strong>1916 年德军 Double Kleif M.1915 火焰喷射器小组</strong>：<a href="https://commons.wikimedia.org/wiki/File:A_squad_with_Double_Kleif_M.1915_Flammenwerfer.jpg" target="_blank">Wikimedia Commons 原图页</a>。Flickr 用户 Ww1 German Militaria Images 提供图像并以公有领域方式发布；Commons 页面标记为 Public Domain Mark。</p>
</li>
<li>
<p><strong>美国档案中的德国火焰喷射器实物照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:111-SC-2076_-_Chemical_Warfare_Service_-_German_flamethrower_or_flame_projector_-_NARA_-_55165505.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国国家档案馆（NARA）馆藏；Commons 页面标记为美国政府作品 / 公有领域。</p>
</li>
<li>
<p><strong>美国 Fort Lewis 军事博物馆收藏的 M1917 堑壕刀</strong>：<a href="https://commons.wikimedia.org/wiki/File:U.S._M1917_trench_knife.jpg" target="_blank">Wikimedia Commons 原图页</a>。Thornfield Hall 摄于 Fort Lewis Military Museum；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>1918 年梅特伦前线佩戴呼吸器的皇家苏格兰团士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:12th_Royal_Scots_in_gas_masks_25-06-1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 6775；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>一战期间英军士兵为一组 利文斯投射器 装填毒气弹</strong>：<a href="https://commons.wikimedia.org/wiki/File:Livens_gas_projector_loading.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏编号 Q 14945；一战英国官方摄影作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1917 年加拿大部队使用的 利文斯投射器 阵地</strong>：<a href="https://commons.wikimedia.org/wiki/File:CWM_-_Livens_Projector.jpg" target="_blank">Wikimedia Commons 原图页</a>。Canadian War Museum George Metcalf Archival Collection，编号 19930013-348；历史照片，Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1916—1917 年英国皇家飞行队正在服役的 B.E.2c 侦察机</strong>：<a href="https://commons.wikimedia.org/wiki/File:Operational_B.E.2c.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏，编号 Q 67525；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>伦敦帝国战争博物馆收藏的 皇家飞机制造厂 B.E.2c</strong>：<a href="https://commons.wikimedia.org/wiki/File:Royal_aircraft_factory_BE2c_at_the_Imperial_War_Museum.jpg" target="_blank">Wikimedia Commons 原图页</a>。Max Smith 摄；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>保存至今的 Fokker E.III Eindecker（福克 E.III 单翼战斗机）</strong>：<a href="https://commons.wikimedia.org/wiki/File:Fokker_E.III_Eindecker_%28210-16%29_%2819036385131%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Alan Wilson 摄，拍摄对象为英国 Science Museum 收藏的现存 Fokker E.III；按 Commons 文件页所列 Creative Commons 许可使用。</p>
</li>
<li>
<p><strong>1917 年投入使用的 Sopwith F.1 Camel（索普威思“骆驼”战斗机）</strong>：<a href="https://commons.wikimedia.org/wiki/File:1917_Sopwith_F.1_Camel.jpg" target="_blank">Wikimedia Commons 原图页</a>。Sanjay Acharya 摄，拍摄对象为 Smithsonian National Air and Space Museum 展品；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>法国 斯帕德 XIII 战斗机</strong>：<a href="https://commons.wikimedia.org/wiki/File:SPAD_XIII_USAF.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国空军国家博物馆图片；美国政府作品，Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年 9 月 Handley Page（汉德利·佩季）轰炸机前舱观察员使用安装在 斯卡夫环形枪架上的 刘易斯机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Royal_Flying_Corps_on_the_Western_Front%2C_1914-1918_Q9973.jpg" target="_blank">Wikimedia Commons 原图页</a>。David McLellan 摄于 1918 年 9 月 25 日；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>保存于 Bristol F.2B 上的 斯卡夫环形枪架与 刘易斯机枪安装结构</strong>：<a href="https://commons.wikimedia.org/wiki/File:Scarff_ring_sc.jpg" target="_blank">Wikimedia Commons 原图页</a>。Mark Murphy 摄于 Shuttleworth Collection；按 Commons 文件页所列 CC BY-SA / GFDL 许可使用。</p>
</li>
<li>
<p><strong>1917 年德国哥达轰炸机挂载炸弹</strong>：<a href="https://commons.wikimedia.org/wiki/File:Bundesarchiv_Bild_146-1971-045-48%2C_Flugzeug_wird_mit_Bomben_best%C3%BCckt.jpg" target="_blank">Wikimedia Commons 原图页</a>。德国联邦档案馆（Bundesarchiv）馆藏，约 1917 年 11 月；按 Commons 文件页所列 Bundesarchiv 授权使用。</p>
</li>
<li>
<p><strong>1918 年汉德利·佩季 O/400 双发重型轰炸机</strong>：<a href="https://commons.wikimedia.org/wiki/File:Handley_Page_O-400.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国空军资料图；美国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>一战时期安装在临时高射枪架上的 Lewis 机枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:INGENIOUS_ANTI-AIRCRAFT_GUN._A_Lewis_gun_for_anti-aircraft_purposes%2C_Bestanddeelnr_158-1949.jpg" target="_blank">Wikimedia Commons 原图页</a>。一战时期历史照片，Nationaal Archief（荷兰国家档案馆）Fotocollectie Eerste Wereldoorlog 收藏；以 <a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank">CC0 1.0</a> 释出。</p>
</li>
<li>
<p><strong>1918 年 3 月，美军士兵用 Hotchkiss M1914 向德国观察机射击</strong>：<a href="https://commons.wikimedia.org/wiki/File:American_soldiers_HD-SN-99-02281.JPEG" target="_blank">Wikimedia Commons 原图页</a>。Lt. Edwin H. Cooper 摄于 1918 年 3 月 5 日，原说明为美军士兵以 Hotchkiss Mle 1914 向德国观察机射击；美国军方作品，公有领域。</p>
</li>
<li>
<p><strong>1915 年试验中的 “小威利” 坦克原型</strong>：<a href="https://commons.wikimedia.org/wiki/File:Little_Willie1915.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏，编号 Q 70931；摄影者不详，英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年英国马克 I 型坦克与乘员</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mark_I_series_tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。图像来自美国国会图书馆 Prints and Photographs Division，Bain News Service 收藏；页面标记为无已知版权限制 / 公有领域。</p>
</li>
<li>
<p><strong>1916 年 9 月 15 日弗莱尔—库尔斯莱特战斗前的马克 I 型“Clan Leslie”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Tank_preparing_to_advance_at_Flers-Courcelette_15-09-1916_IWM_Q_5574.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 5574；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1917 年康布雷战役中陷入德军战壕的英国马克 IV 型“Hyacinth”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:A_Mark_IV_%28Male%29_tank_of_%27H%27_Battalion%2C_%27Hyacinth%27%2C_ditched_in_a_German_trench_while_supporting_1st_Battalion%2C_Leicestershire_Regiment_near_Ribecourt_during_the_Battle_of_Cambrai%2C_20_November_1917._Q6432.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏，编号 Q 6432；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>博文顿坦克博物馆保存的马克 IV 型“Excellent”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mark_IV_%28Excellent%29_front-left_2017_Bovington.jpg" target="_blank">Wikimedia Commons 原图页</a>。Morio 摄于 The Tank Museum, Bovington；按 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>一战时期英国马克 V 型“雄性”重型坦克官方照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_Mark_V_%28male%29_tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏，编号 Q 14486；Department of Tank Design and Experiment Collection；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>博文顿坦克博物馆保存的英国马克 V 型坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mark_V_tank_Bovington_Flickr_4773871655.jpg" target="_blank">Wikimedia Commons 原图页</a>。davehighbury 摄；按 <a href="https://creativecommons.org/licenses/by-sa/2.0/" target="_blank">CC BY-SA 2.0</a> 授权。</p>
</li>
<li>
<p><strong>索米尔装甲博物馆保存的施耐德 CA1</strong>：<a href="https://commons.wikimedia.org/wiki/File:Schneider_CA1._Mus%C3%A9e_des_Blind%C3%A9s%2C_Saumur%2C_France._11-7-2022_%2853274810286%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Alan Wilson 摄于 Musée des Blindés；按 Commons 文件页所列 Creative Commons 许可使用。</p>
</li>
<li>
<p><strong>一战时期法国圣沙蒙坦克，可清楚看到远远伸出履带前方的车鼻</strong>：<a href="https://commons.wikimedia.org/wiki/File:Char_St_Chamond_tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。Imperial War Museums 馆藏编号 Q 14646，Department of Tank Design and Experiment Collection；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年 8 月法国 Grevillers 附近跨越战壕的 A型中型“惠比特”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Whippet%2C_Aug_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Henry Armytage Sanders 摄；新西兰国家图书馆馆藏，Commons 标记为新西兰公有领域作品。</p>
</li>
<li>
<p><strong>布鲁塞尔皇家军事博物馆收藏的 A型中型“惠比特”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Whippet.jpg" target="_blank">Wikimedia Commons 原图页</a>。Paul Hermans 摄于 2005 年；按 Commons 文件页所列许可使用。</p>
</li>
<li>
<p><strong>1918 年 9 月 26 日，阿贡森林前线与美军共同前进的两辆法国雷诺 FT</strong>：<a href="https://commons.wikimedia.org/wiki/File:American_troops_going_forward_to_the_battle_line_in_the_Forest_of_Argonne._France,_September_26,_1918._-_NARA_-_530748.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国陆军通信兵系统档案，现藏美国国家档案馆（NARA），NAID 530748；Commons 标记为美国政府作品 / 公有领域。</p>
</li>
<li>
<p><strong>保存于博物馆中的 雷诺 FT轻型坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Renault_FT_%E2%80%98753%E2%80%99_%2837632013970%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Alan Wilson 摄，拍摄于库宾卡坦克博物馆 / Patriot Park 馆藏展区；按 <a href="https://creativecommons.org/licenses/by-sa/2.0/" target="_blank">CC BY-SA 2.0</a> 授权。</p>
</li>
<li>
<p><strong>1918 年德国 A7V“Wotan”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:A7V_Wotan.jpg" target="_blank">Wikimedia Commons 原图页</a>。Hermann Rex 绘 / 图版来源于 1926 年出版物；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年新西兰士兵检查被俘获的德国 A7V“Schnuck”坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:A7V_tank,_Sept_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Henry Armytage Sanders 摄于 1918 年 9 月 8 日；新西兰国家图书馆馆藏；Commons 页面标记为新西兰公有领域作品。</p>
</li>
<li>
<p><strong>1918 年英军军官在德军战壕中查看缴获的 M1918反坦克步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Two_of_our_officers_with_a_German_anti-tank_gun_%284687973105%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。英国西线官方摄影资料，由 National Library of Scotland 提供；原始说明为英军推进至 Bapaume 期间在德军战壕中查看缴获的德国反坦克枪；页面标记为一战英国官方照片 / 公有领域。</p>
</li>
<li>
<p><strong>法国陆军博物馆收藏的 毛瑟 M1918 反坦克步枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mauser_Tank-Gewehr_M1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Rama 摄，拍摄对象藏于法国陆军博物馆；按 <a href="https://creativecommons.org/licenses/by-sa/2.0/fr/deed.en" target="_blank">CC BY-SA 2.0 France</a> 授权。</p>
</li>
<li>
<p><strong>瑞典陆军博物馆收藏的 1915 年刘将军步枪试验枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:General_Liu_rifle_-_1915.jpg" target="_blank">Wikimedia Commons 原图页</a>。Armémuseum（瑞典陆军博物馆）馆藏与图像；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>Major Robert Blair 与 霍特自动步枪试验枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Major_Robert_Blair_with_Huot.jpg" target="_blank">Wikimedia Commons 原图页</a>。霍特自动步枪 历史试验照片；Commons 页面列入 霍特自动步枪 馆藏类别。</p>
</li>
<li>
<p><strong>霍特自动步枪与刘易斯轻机枪的比较照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Huot_and_Lewis_-_1.jpg" target="_blank">Wikimedia Commons 原图页</a>。一战时期 Huot 与刘易斯对照资料图；Commons 页面列入 霍特自动步枪 馆藏类别。</p>
</li>
<li>
<p><strong>Springfield Armory Museum 保存的 佩德森装置</strong>：<a href="https://commons.wikimedia.org/wiki/File:U.S._AUTOMATIC_PISTOL%2C_CAL..30%2C_MODEL_OF_1918_%28PEDERSEN_DEVICE%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Springfield Armory Museum 馆藏照片；美国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>安装 佩德森装置 的 M1903 Mk I 与相关套件</strong>：<a href="https://commons.wikimedia.org/wiki/File:Kit_for_Pedersen_Device.jpg" target="_blank">Wikimedia Commons 原图页</a>。Springfield Armory Museum / 美国政府相关馆藏资料；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1915 年的 John T. Thompson，后来 Auto-Ordnance 与汤普森冲锋枪项目的核心推动者</strong>：<a href="https://commons.wikimedia.org/wiki/File:John_Taliaferro_Thompson%2C_U.S._Army_officer%2C_founder_of_the_Auto-Ordnance_Corporation%2C_and_inventor_of_the_Thompson_submachine_gun_LCCN2014647521_%28cropped%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Phillips Studio 摄于 1915 年；美国国会图书馆 Prints and Photographs Division 馆藏；Commons 页面标记为公有领域、无已知出版限制。</p>
</li>
<li>
<p><strong>战后由 Annihilator 系列继续演化而来的 Thompson Model 1921 冲锋枪</strong>：<a href="https://commons.wikimedia.org/wiki/File:Thompson_1921_submachine_gun.jpg" target="_blank">Wikimedia Commons 原图页</a>。Hmaag 摄；按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。正文明确将其作为战后演化型号展示，而非 Annihilator I 原型照片。</p>
</li>
<li>
<p><strong>Ribeyrolles 1918 自动卡宾枪的现代展示图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Ribeyrolle1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。Commons 文件页标记为 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权；由于一战原始照片资料有限，正文将其作为结构展示图而非历史实拍图使用。</p>
</li>
<li>
<p><strong>1915 年奥匈军 Hellriegel 实验自动武器测试照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Brzostrelka_Hellriegel_-_1.jpg" target="_blank">Wikimedia Commons 原图页</a>。Austrian National Library / Europeana 1914–1918 收藏，摄于 1915 年 10 月；Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>Hellriegel 实验武器及其供弹装置的另一张测试照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Brzostrelka_Hellriegel_-_3.jpg" target="_blank">Wikimedia Commons 原图页</a>。Austrian National Library / Europeana 1914–1918 收藏，摄于 1915 年 10 月；Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>今天保存的神秘 9 mm 马克沁式实验自动武器</strong>：<a href="https://commons.wikimedia.org/wiki/File:Schwarzlose_SMG.png" target="_blank">Wikimedia Commons 原图页</a>。图像源自俄罗斯图拉保存实物的研究照片，Commons 文件页将其描述为 Experimental German 08/18马克沁SMG，并标记为公有领域；正式名称与确切项目背景仍存在不确定性。</p>
</li>
<li>
<p><strong>1928 年演习中的法国 FCM 2C“Berry”</strong>：<a href="https://commons.wikimedia.org/wiki/File:Berry_in_1928_%28moar_contrast_%2B_cropped%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Agence Rol 1928 年历史照片的整理版本；Commons 页面提供 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。</p>
</li>
<li>
<p><strong>1918 年德国 K-Wagen 超重型坦克原型照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:German_K_Panzerkampfwagen_1918.jpg" target="_blank">Wikimedia Commons 原图页</a>。来源为英国 Royal Armoured Corps Centre / Tank Museum 旧资料扫描，图像标注为 1918 年德国 K Panzerkampfwagen；Commons 标记为英国政府公有领域作品。</p>
</li>
<li>
<p><strong>1918 年 A7V-U 原型车</strong>：<a href="https://commons.wikimedia.org/wiki/File:A7V-U.jpg" target="_blank">Wikimedia Commons 原图页</a>。原始资料来自 Daimler-Benz 档案系统的历史照片复制；Commons 文件说明标注为 1918 年 A7V-U 原型。</p>
</li>
<li>
<p><strong>德国坦克博物馆展示的 LK II / Strv m/21 系车辆</strong>：<a href="https://commons.wikimedia.org/wiki/File:Leichter_Kampfwagen_II_%28Version_MG%29_%2839076260322%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Triple-green 摄于 Deutsches Panzermuseum Munster；按 <a href="https://creativecommons.org/licenses/by-sa/2.0/" target="_blank">CC BY-SA 2.0</a> 授权。</p>
</li>
<li>
<p><strong>LK II 的装甲与车体结构展示</strong>：<a href="https://commons.wikimedia.org/wiki/File:Leichter_Kampfwagen_II_Vernietete_Panzerung.jpg" target="_blank">Wikimedia Commons 原图页</a>。Suit 摄于 Deutsches Panzermuseum Munster；按 Commons 文件页所列 Creative Commons 许可使用。</p>
</li>
<li>
<p><strong>试验中的俄国沙皇坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Tsar_tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。20 世纪 10 年代末至 20 年代初流传的沙皇坦克历史照片；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>法国第一型 Boirault Machine 原型</strong>：<a href="https://commons.wikimedia.org/wiki/File:Appareil_Boirault_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。一战时期法国 Boirault 实验车辆历史资料图；Commons 页面收录于 Boirault machine / Experimental tanks of France 类别。</p>
</li>
<li>
<p><strong>试验中行进的 Boirault Machine</strong>：<a href="https://commons.wikimedia.org/wiki/File:Boirault_machine_underway.jpg" target="_blank">Wikimedia Commons 原图页</a>。一战时期实验照片；Commons 页面收录于 Boirault machine 类别。</p>
</li>
<li>
<p><strong>1918 年美国 骨架坦克 原型</strong>：<a href="https://commons.wikimedia.org/wiki/File:AD1116719-014_Skeleton_Tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国陆军《Development of Armored Vehicles》资料图；美国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年美国 霍尔特油电坦克 原型</strong>：<a href="https://commons.wikimedia.org/wiki/File:Holt_prototype_gas_electric_tank.jpg" target="_blank">Wikimedia Commons 原图页</a>。1918 年历史照片；Commons 标记为美国公有领域作品。</p>
</li>
<li>
<p><strong>1918 年 Aberdeen Proving Ground 的 福特3吨M1918坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Ford_Three-ton.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国陆军试验照片，拍摄于 Aberdeen Proving Ground；Commons 标记为美国政府作品 / 公有领域。</p>
</li>
</ol></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/261002-wwi-st1-trench-life/</id>
      <title type="text">WWI-专题1 战壕日记：一名普通士兵的前线生活</title>
      <published>2026-10-02T00:00:00.000Z</published>
      <updated>2026-10-02T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/261002-wwi-st1-trench-life/"/>
      <summary type="text">“你”是西线战场的一名普通士兵，接下来的一段时间，你可能需要在这里度过。</summary>
      <content type="html"><![CDATA[
<div><div><div></div><div>Note</div></div><div><p>这并不是某一名真实士兵留下的日记。</p><p>本文中的“你”，是一名根据第一次世界大战西线英军士兵日记、军队资料、历史照片与相关研究综合塑造出来的普通步兵。为了让一天的节奏更加清晰，文中使用了具体时间作为叙事标尺；不同部队、战区、季节和战况下，实际安排可能有很大差异。</p></div></div>
<p>1916 年秋，法国北部，索姆河前线。</p>
<p>天还没有亮，你的肩膀先被人推了两下。</p>
<p>你从狭窄的休息处坐起来，摸到身旁的步枪。靴子落到鸭板上，下面的积水发出一声轻响。几个人已经从你面前挤过去，沿着弯曲的壕沟走向胸墙。</p>
<p>“Stand-to。”</p>
<p>口令继续向远处传。</p>
<p>一天的生活开始了。</p>

<table><thead><tr><th>大致时间</th><th>你的行动</th><th>堑壕生活的一面</th></tr></thead><tbody><tr><td>05：30</td><td>黎明前进入战斗位置</td><td>前沿壕与警戒</td></tr><tr><td>06：30</td><td>解除警戒、吃早餐</td><td>补给与前线日常</td></tr><tr><td>08：00</td><td>修壕、排水、搬运物资</td><td>泥水、鸭板与疾病</td></tr><tr><td>10：30</td><td>沿交通壕向后移动</td><td>多层堑壕体系</td></tr><tr><td>12：30</td><td>回到前线放哨、等待</td><td>狙击威胁与卫生问题</td></tr><tr><td>15：20</td><td>躲避炮击</td><td>掩体、炮兵与通信</td></tr><tr><td>17：30</td><td>黄昏再次警戒</td><td>前线一天的固定节奏</td></tr><tr><td>19：00</td><td>夜间劳动</td><td>补给、维修与伤员运输</td></tr><tr><td>23：00</td><td>越出胸墙巡逻</td><td>无人区</td></tr><tr><td>01：30</td><td>返回阵地休息</td><td>一天重新开始</td></tr></tbody></table>
<hr />
<section><h1>05：30｜天亮以前，保持戒备<a href="#0530天亮以前保持戒备"><span>#</span></a></h1><p>你把弹药带整理好，拿起步枪，跟着前面的人来到胸墙后方。</p><p>壕沟很窄，人与人擦肩而过时必须侧一下身体。前方的士兵踩上一级木土混合的台阶，你也跟着站上去。身体升高以后，视线终于越过胸墙上沿。</p><p>但你也不敢把头伸得太高，没有为什么。</p><p>外面仍是一片灰黑。</p><p>前方是铁丝网，再远一些是被炮弹翻过无数遍的土地。夜色还遮住了大部分细节。对面的德军阵地藏在晨雾和黑暗里，偶尔有一声枪响，从不知道哪个方向传来。</p><p>你紧紧握着步枪，死死地盯着前方。</p><p>没有进攻。</p><p>天色一点点变亮。</p><section><h2>黎明前的攻防<a href="#黎明前的攻防"><span>#</span></a></h2><p>英军把这种黎明前后的全员警戒称为 <strong>stand-to</strong>。黄昏时通常还会再进行一次。原因并不复杂：</p><ul>
<li>黎明与黄昏的光线最适合借助昏暗环境进行突袭；</li>
<li>前沿守军需要在可能遭遇突袭的时段提前进入射击位置；</li>
<li>即使当天没有任何进攻，固定警戒仍然会成为阵地生活的一部分。</li>
</ul><p>站在这里的士兵并不是躲在一条普通土沟里。成熟的前沿壕本身就是一件经过反复改进的防御工事。</p>

<table><thead><tr><th>结构</th><th>所在位置</th><th>主要作用</th></tr></thead><tbody><tr><td>胸墙（parapet）</td><td>面向敌军的一侧</td><td>遮蔽身体，阻挡子弹和部分弹片</td></tr><tr><td>射击踏板（fire step）</td><td>胸墙内侧</td><td>让士兵需要观察、射击时迅速站高</td></tr><tr><td>背墙（parados）</td><td>背向敌军的一侧</td><td>减少后方飞来的弹片和碎片威胁</td></tr><tr><td>鸭板（duckboards）</td><td>壕底</td><td>尽量让双脚离开积水与泥浆</td></tr><tr><td>排水设施</td><td>壕底或两侧</td><td>排走雨水和地下水</td></tr><tr><td>掩蔽部（dugout）</td><td>壕壁或阵地后方</td><td>休息、储物或躲避部分炮火</td></tr><tr><td>观察孔、潜望镜</td><td>胸墙附近</td><td>尽量不暴露头部地观察前方</td></tr></tbody></table><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Cheshire_Regiment_trench_Somme_1916--ff-c8c1dd1c39da.2vfjepv8kz.webp" alt="1916 年 7 月索姆河战役期间，奥维莱尔附近被英军占据的一段德军战壕" /><figcaption>1916 年 7 月索姆河战役期间，奥维莱尔附近被英军占据的一段德军战壕</figcaption></figure><p></p><p>前沿壕也很少是一条笔直的长沟，而是不断转折、分段。这样既能减少敌军闯入后沿整条壕沟纵向射击的机会，也能让一次爆炸的弹片和冲击尽量被限制在较短的壕段里。</p><hr /></section></section>
<section><h1>06：30｜短暂的早餐时间<a href="#0630短暂的早餐时间"><span>#</span></a></h1><p>“Stand down。”</p><p>口令传来以后，你从射击踏板上下来，把步枪靠回身边。</p><p>有人开始生火烧水，有人拆开口粮，有人已经坐到能找到的干燥位置上。刚才还挤满持枪士兵的壕段，很快变回了一个临时的生活空间。</p><p>你接过一杯热饮，找地方坐下。</p><p>食物谈不上精致。能不能吃到热的，很大程度取决于后方送来的东西什么时候抵达。饮水、面包、罐头和其他口粮都要从更安全的区域一路运到前线，越靠近前线，路便越难走。</p><p>吃完以后，你稍微擦拭了一下你的步枪。</p><p>枪机上沾了泥，衣服也是湿的。你用布把能够清理的地方重新擦一遍，再把装备收好。</p><section><h2>前沿壕的运转与维持<a href="#前沿壕的运转与维持"><span>#</span></a></h2><p>从后方到你手里的早餐，中间经过的是一条持续运转的补给链。前沿阵地本身无法长期独立运转，日常所需几乎都要从后方送来，包括：</p><ul>
<li><strong>食物和饮水</strong>：越靠近前沿，运输越依赖人力、畜力和狭窄通道；</li>
<li><strong>弹药</strong>：步枪弹、手榴弹、机枪弹药等必须持续补充；</li>
<li><strong>木料和沙袋</strong>：工事会被炮击和雨水不断破坏；</li>
<li><strong>医疗用品</strong>：前线伤员需要先接受简单处置，再向后转运；</li>
<li><strong>信件与个人物品</strong>：战争并没有切断士兵与后方生活的全部联系。</li>
</ul><p>武器保养也由部队统一组织。战壕命令把清晨有足够光线后的步枪清洁列为固定工作，由班长监督、检查；另一些命令要求军官在早晚警戒时检查步枪和弹药。泥沙一旦进入枪机，就可能使武器在急需射击时发生故障。</p><hr /></section></section>
<section><h1>08：00｜修补战壕<a href="#0800修补战壕"><span>#</span></a></h1><p>早餐结束，你和另外几个人领到铲子。</p><p>昨夜的雨水把一段壕壁泡软了，几个沙袋已经塌下来。你们先把松动的泥土铲开，再把装好的沙袋一只只垒回去。</p><p>旁边的人则正在清理被泥巴堵塞的排水沟。</p><p>再往前一点，有人在更换断裂的木板。一辆装着物资的小车无法直接来到这里，最后一段东西还得靠人抬。沙袋、木料、弹药箱沿壕沟一件件送过去。有人从你身边挤过，你停下铲子让出半步，然后继续干活。</p><p>炮弹只需要几秒钟就能毁掉一段工事，修复却可能花掉几小时甚至更久。</p><section><h2>泥水和鸭板<a href="#泥水和鸭板"><span>#</span></a></h2><p>你脚下铺着的木板被称作 <strong>“鸭板（duckboards）”</strong>。它们可以把脚与壕底最深的泥浆和积水隔开。</p><p>在排水困难的阵地，几块木板可能直接决定一段壕沟还能不能正常通行。持续降雨以后，情况会迅速恶化：土壁变软，运输困难，装备沾满泥浆，人的双脚则长时间泡在肮脏、寒冷的泥水里。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/1st_Division_laying_duckboards_over_the_muddy_ground_in_Passchendaele_%28AWM_E00837%29--ff-9c46aa38df9f.1apsf8y15y.webp" alt="1917 年 10 月，澳大利亚第 1 师士兵在帕申达勒附近的泥地上铺设鸭板" /><figcaption>1917 年 10 月，澳大利亚第 1 师士兵在帕申达勒附近的泥地上铺设鸭板</figcaption></figure><p></p><p>这也是“战壕足”（trench foot）成为一战前线典型疾病的背景。它并不是简单的“鞋里进了水”，而是双脚长期处于寒冷、潮湿和受压状态，血液循环受到影响后出现的一系列损伤。</p><p>轻者可能出现麻木、肿胀和疼痛，严重时可能发生溃烂、感染和组织坏死。军队因此要求保持双脚干燥、更换袜子并检查足部，也要不断改善排水。</p><hr /></section></section>
<section><h1>10：30｜前往后方<a href="#1030前往后方"><span>#</span></a></h1><p>上午的修整告一段落，你被叫去后方领取物资。</p><p>没有人让你爬出胸墙，从开阔地直接往后跑。你转过一个弯，进入一条从前沿向后延伸的狭窄壕沟。</p><p>这里同样泥泞，但比刚才的前线稍微安静一些。</p><p>迎面先来了一名传令兵，你侧身让开。再往前，一队人正抬着箱子向前走。两名担架员从另一个岔口经过，后面还跟着准备接替前沿值勤的士兵。</p><p>你继续沿着这条弯弯曲曲的壕沟往后方走。</p><section><h2>交通壕<a href="#交通壕"><span>#</span></a></h2><p>你走的这条就是 <strong>交通壕（communication trench）</strong>。它把前沿与后方连接起来，使人员和物资不必长时间暴露在开阔地上。</p><p>一套成熟的堑壕阵地通常包含多层防御和保障设施：</p>

<table><thead><tr><th>阵地组成</th><th>大致位置</th><th>主要任务</th></tr></thead><tbody><tr><td>前沿壕 / 火线壕</td><td>最接近无人区</td><td>警戒、射击，首先承受敌军攻击</td></tr><tr><td>支援壕</td><td>前沿壕后方</td><td>容纳支援兵力和部分物资</td></tr><tr><td>预备阵地</td><td>更靠后</td><td>保存预备兵力，必要时投入反击或增援</td></tr><tr><td>交通壕</td><td>连接前后各线</td><td>运送人员、弹药、食物和伤员</td></tr><tr><td>火力与观察阵地</td><td>分布于体系内</td><td>部署机枪、迫击炮和观察人员</td></tr><tr><td>指挥、通信与救护设施</td><td>相对靠后</td><td>维持命令传递、伤员处理和阵地运转</td></tr></tbody></table><p>1917 年拍摄的一张空中侦察照片能更直观地表现这种结构。壕沟在地面上弯折交错，双方阵地之间则夹着无人区。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Aerial_view_Loos-Hulluch_trench_system_July_1917--ff-b80c8845ee44.8vnpjg4uay.webp" alt="1917 年 7 月洛斯—于吕什地区的空中侦察照片，可见双方堑壕网络与无人区" /><figcaption>1917 年 7 月洛斯—于吕什地区的空中侦察照片，可见双方堑壕网络与无人区</figcaption></figure><p></p><p>地图上画成一条细线的“西线”，落到地面以后，其实有相当大的宽度和纵深。</p><hr /></section></section>
<section><h1>12：00｜“丰盛”的午餐<a href="#1200丰盛的午餐"><span>#</span></a></h1><p>你带着领回来的物资返回前沿。</p><p>其中还有今天的午饭。</p><p>两个人抬着一只大铁锅沿交通壕走来，锅身碰到木板时发出沉闷的响声。这种军中常见的大锅通常被士兵叫作 <strong>dixie</strong>。后方的炊事人员尽可能先把食物做好，再由杂役队沿交通壕一路送到各个排和班。</p><p>你看到今天送到的食物了——锅里有温热的炖肉——不过，你也不知道这到底是什么肉。</p><p>但对于平常食不果腹的你来说，能吃上一口炖汤已经十分满足。</p><p>你拿出自己的饭盒，把分到的一勺盛进去。肉未必很多，里面混着汤汁和已经煮软的蔬菜。旁边又分来一块面包和一些果酱。有人手里拿到的是咸牛肉罐头，有人还留着早上没有吃完的饼干。此外，还有一小杯热的茶水。</p><p>等它真正到你手里时已经谈不上温热，但在潮湿的壕沟里，能喝到稍微有点热量的东西本身就足够让人稍微取得一点慰藉。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_British_Army_on_the_Western_Front%2C_1914-1918_Q4846--ff-ad96e4cece5b.13mkjtbvph.webp" alt="1917 年 3 月阿拉斯附近，英军士兵在积雪覆盖的战壕中用餐" /><figcaption>1917 年 3 月阿拉斯附近，英军士兵在积雪覆盖的战壕中用餐</figcaption></figure><p></p><section><h2>午餐吃什么？<a href="#午餐吃什么"><span>#</span></a></h2><p>英军并不存在每天固定不变的“战壕菜单”。能送到前沿什么，受到补给、天气、炮击和阵地位置影响。</p><p>较常见的东西包括：</p><ul>
<li><strong>“Bully beef”咸牛肉罐头</strong>：耐储存、容易运输，是最典型的前线食品之一；</li>
<li><strong>面包和军用硬饼干</strong>：面包可能已经不新鲜，而硬饼干出了名地坚硬，士兵常会泡进茶、水或炖菜里再吃；</li>
<li><strong>果酱等配给</strong>：用来给单调的主食增加一些味道；</li>
<li><strong>培根和蔬菜</strong>：条件允许时可以加入炖锅，也可能由后方炊事人员统一烹调；</li>
<li><strong>Maconochie 等肉菜罐头或炖菜</strong>：加热后尚可入口，冷着吃则很难让人喜欢；</li>
<li><strong>茶</strong>：几乎贯穿英军士兵的一天，也是前线最常见的热饮之一。</li>
</ul><p>前线自身也不适合大规模生火做饭。炊烟可能暴露阵地，空间和燃料同样有限。因此，较完整的烹饪通常尽量放在后方完成，再把食物装进 dixie 送上来；如果条件允许，士兵也会使用小型炉具，或者把咸牛肉、饼干、培根和蔬菜混在一起煮成一锅简单的炖食。</p><div><div><div></div><div>Note</div></div><div><p><strong>应急配给：铁口粮</strong></p><p>每名士兵还会携带应急用的 <strong>iron ration</strong>，通常包括罐装牛肉、饼干、茶和糖等。它的用途是在炮火切断补给、部队进攻或其他情况下暂时维持生存，原则上不能因为今天的午饭难吃就自行拆开。</p></div></div><p>你用面包把饭盒里剩下的一点汤汁擦干净，又灌了几口茶，然后收好了餐具和杯子。</p><p>前线工作不会因为到了午饭时间而暂停。吃东西的时候仍有人需要站岗，吃完的人再去接替他们。</p></section></section>
<section><h1>12：30｜午后时分<a href="#1230午后时分"><span>#</span></a></h1><p>午后的壕沟没有上午那么忙。有人靠着壕壁补觉，有人在写信，也有人继续修理装备。</p><p>轮到你值勤了。</p><p>你踩上射击踏板，把身体保持在胸墙掩护后面，通过观察位置看向前方。无人区里没有成队士兵冲锋，只有一片被炮火反复翻过的土地。</p><p>头依然不能抬得太高。</p><p>对面某个肉眼看不清的位置，可能就有观察哨或者狙击手。</p><p>胸墙上方忽然“啪”地炸开一小团湿泥。</p><p>你几乎是本能地缩回壕壁后面。几粒泥土和麻袋纤维溅到脸上，你用袖口抹了一把，没有再探头。</p><p>过了几秒，什么也没有继续发生。</p><p>只有远处传来一声很难分辨方向的枪响。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Worcester_Regiment_sentry_in_trench_Ovillers_1916_IWM_Q_4100--ff-ae47697d4c1a.8vnpjg4uaq.webp" alt="1916 年 8 月索姆河奥维莱尔，一名伍斯特郡团哨兵透过战壕射孔观察前方" /><figcaption>1916 年 8 月索姆河奥维莱尔，一名伍斯特郡团哨兵透过战壕射孔观察前方</figcaption></figure><p></p><p>此时此刻，最难忍受的反而不是不时传来的枪声。</p><p>衣领里开始发痒。你伸手隔着衣服抓了几下，过一会儿又痒起来。</p><p>一只老鼠从堆放物品的地方钻过去，很快消失在壕壁的阴影里。</p><section><h2>虱患与卫生<a href="#虱患与卫生"><span>#</span></a></h2><p>堑壕同时是战斗位置和临时生活空间。大量人员长期聚集，再加上有限的洗浴、排污和垃圾处理条件，卫生条件十分恶劣。</p><ul>
<li><strong>虱子</strong>会藏在衣服接缝和被褥中，离开前线休整时可以集中清洗、除虱，但回到拥挤环境后很容易再次出现；</li>
<li><strong>老鼠</strong>能够利用食物残渣、垃圾和战场上的遗体生存；</li>
<li><strong>厕所和污物</strong>必须持续处理，否则会进一步污染生活环境；</li>
<li><strong>睡眠不足和长期紧张</strong>会不断损耗人的体力与精神；</li>
<li><strong>疾病</strong>在战斗之外也可以持续造成减员。</li>
</ul><p>卫生工作有明确的责任分工。军医要检查厕所、垃圾坑和饮水设施，卫生人员负责处理污物，空罐头和其他垃圾须集中收集。在使用便桶的阵地，污物还要趁夜运出掩埋。</p><hr /></section></section>
<section><h1>15：20｜炮弹落下<a href="#1520炮弹落下"><span>#</span></a></h1><p>远处先响了一声。</p><p>你停下手里的动作。</p><p>第二声更近。</p><p>泥土从胸墙边缘掉下来一点。</p><p>第三声之后，有人喊着让所有人进入掩蔽位置。刚才还稍显平静的壕沟立刻挤满移动的人。你跟着前面的人钻进掩体，把身体尽量往里面缩。</p><p>外面的爆炸声一阵接一阵。</p><p>木料在震动。</p><p>某一次爆炸以后，壕沟里传来喊声，有人去查看线路和受损的位置。电话还能不能用、哪一段工事被击中、炮击后会不会跟着步兵进攻，都必须尽快弄清楚。</p><p>你待在掩体里面等待。</p><p>不知道从第几发开始，你下意识地数了起来。</p><p>一。</p><p>二。</p><p>三。</p><p>每响一声，你就等着下一声。</p><p>数到第七下以后，外面忽然安静下来。没有人立刻往外走。</p><p>十几秒后，第八发落得更近，掩体顶上的泥土簌簌掉下来，敲在钢盔和肩膀上。</p><p>于是你重新开始数。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Beaumont_Hamel_bombardment_01-07-1916_IWM_Q_90--ff-7b70835fa822.5trti83i1w.webp" alt="1916 年 7 月 1 日索姆河博蒙阿梅尔，英军炮兵正在轰击德军阵地" /><figcaption>1916 年 7 月 1 日索姆河博蒙阿梅尔，英军炮兵正在轰击德军阵地</figcaption></figure><p></p><section><h2>炮兵“改变”了整条堑壕的生活<a href="#炮兵改变了整条堑壕的生活"><span>#</span></a></h2><p>堑壕能够降低平射武器和部分炮弹破片造成的伤亡，但直接命中、壕壁坍塌或掩体被摧毁，仍可能造成严重伤亡。</p><p>炮火可以：</p><ul>
<li>直接摧毁壕壁、掩体和铁丝网；</li>
<li>用爆炸和弹片杀伤暴露人员；</li>
<li>炸坏排水系统和交通路线；</li>
<li>切断电话线；</li>
<li>阻止增援和补给通过；</li>
<li>在步兵进攻前压制防御阵地。</li>
</ul><p>掩蔽部也不是统一规格。有的只是挖入壕壁的简陋空间，有的经过木料、钢材甚至混凝土加固；在经营时间更长、地质条件允许的阵地，还可能出现更深的地下设施。</p><p>工事质量受地形、地下水、物资、修筑时间与战术需要影响，同一支军队的不同阵地也可能差别极大。</p><div><div><div></div><div>Note</div></div><div><p><strong>布罗迪钢盔的宽檐</strong></p><p>英军在 1915 年选用布罗迪（Brodie）钢盔，次年逐渐普及。它可由一片钢板冲压成形，便于生产；向外伸出的宽檐增加了头部与肩部对上方弹片和碎屑的遮蔽。</p></div></div></section><section><h2>电话线断了以后<a href="#电话线断了以后"><span>#</span></a></h2><p>前沿电话既连向后方指挥所，也维持与两翼友军的联系。信号兵要沿阵地铺线、标记和巡查，测试通往营部及相邻连队的线路。战壕命令还要求把不同用途的电线分置壕沟两侧，固定在较低位置，并及时收起已经废弃的线路。</p><p>炮击一旦切断线路，通信就要改用其他办法。传令兵可以带走较完整的口头或书面命令，却必须亲自穿过火线；信号灯、旗号和信号弹依赖可见度，能表达的内容也受到限制。信鸽可以把消息带往后方，但需要专人照料，也会受到炮火、噪声和毒气威胁。</p><p>无线电当时已经用于战争，不过早期设备笨重、脆弱，通信距离有限，普通步兵无法像后来那样随身携带电台。指挥所没有收到报告时，往往难以判断前方是仍在坚守、已经遭到突破，还是仅仅因为线路损坏而暂时失联。</p><hr /></section></section>
<section><h1>17：30｜日落时分，重新开始戒备<a href="#1730日落时分重新开始戒备"><span>#</span></a></h1><p>炮击最后停了下来。</p><p>没有步兵跟上来。</p><p>天色却已经在变暗。</p><p>你从掩体里出来，把装备重新整理好。有人检查受损的壕段，有人查看电话线路，也有人把散落的泥土和木料往一旁清。</p><p>不久以后，熟悉的口令再次传来。</p><p>“Stand-to。”</p><p>你拿起步枪，踩上射击踏板。</p><p>和清晨一样，所有人朝着无人区。</p><p>脚下仍然是那块鸭板；胸前仍然是沙袋和泥土；身后的交通壕仍有人走动。早晨损坏的地方修过一遍，下午又有一部分被炮火打坏。</p><p>没有人知道黄昏会不会真的迎来进攻。</p><p>所以你继续等。</p><hr /></section>
<section><h1>19：00｜天黑以后<a href="#1900天黑以后"><span>#</span></a></h1><p>夜幕完全落下。</p><p>警戒解除，你却不能回去睡觉。</p><p>一批木料要送向前沿受损位置。你和另外几个人抬起其中一段，沿壕沟往前走。狭窄处只能一个接一个通过，遇到对面来人，还得临时找地方让开。</p><p>别的队伍也在工作。</p><p>有人向前送弹药，有人抬着担架向后走，有人在修铁丝网，还有人继续搬鸭板和沙袋。</p><p>白天容易被敌军观察的工作，很多都被推迟到黑暗以后。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Trench_at_night--ff-5a679186f85e.67y993bsyd.webp" alt="1917 年 1 月康布雷一带，英军杂役队夜间沿支援壕搬运鸭板" /><figcaption>1917 年 1 月康布雷一带，英军杂役队夜间沿支援壕搬运鸭板</figcaption></figure><p></p><p>黑暗降低了被观察的风险，却没有让战场安静下来。</p><p>对面也在做同样的事。</p><p>远处偶尔传来枪声。照明弹升起时，前方地面短暂亮成惨白色；光线消失后，黑暗又重新盖回来。</p><section><h2>夜间工作与巡逻的掩护<a href="#夜间工作与巡逻的掩护"><span>#</span></a></h2><p>夜间杂役还需要掩护。<a href="https://www.gutenberg.org/files/53126/53126-h/53126-h.htm" target="_blank">《英军战壕命令范例》</a>要求，为离开前沿壕的挖掘和架设铁丝网队伍安排掩护人员；派出巡逻队时，也须通知附近哨位其人数和预计往返时间，以免遭到己方误击。</p><p>施工队与掩护人员承担不同任务：前者挖掘、搬运和架设障碍，后者负责警戒，给正在使用工具的人提供保护。杂役队也应提前分配明确的任务，由熟悉本队的军官或军士带领；工程人员可以指导施工，步兵指挥官仍须负责阵地的作战需要。</p><hr /></section></section>
<section><h1>23：00｜“无人区”里的世界<a href="#2300无人区里的世界"><span>#</span></a></h1><p>今晚你被编进巡逻队。</p><p>你身着轻装，尽量不发出声音。</p><p>轮到你出去时，你先踩高一阶，把身体撑上胸墙，再伏低身体滑到另一边。踩上外面凹凸不平的泥地。</p><p>前面没有路。</p><p>你跟着前一个人的轮廓往前移动，时而绕过弹坑，时而停下来辨别前方的声音。残破的铁丝网横在泥里，地上的弹坑里灌满了泥浆。</p><p>前面一截下垂的铁丝挡住了路。</p><p>你伏低身体，一只手先压住铁丝，另一只手慢慢合拢钢丝剪。金属一点点变形，最后在手里断开，没有猛地弹出去。</p><p>就在这时，对面方向传来一声很轻的金属碰撞。</p><p>紧接着，像是有人压低声音说了半句话。</p><p>前面的人立刻抬起手。</p><p>整支巡逻队伏在泥地里，一动不动。</p><p>你握着钢丝剪，连呼吸都尽量放轻。</p><p>几分钟过去，那边没有再传来声音。</p><p>没有人开枪。</p><p>巡逻队绕开那一小片地方，继续今晚的巡逻工作，然后准备沿原路撤回。</p><p>突然，一枚照明弹升上天空。</p><p>你立刻伏倒在地面上。</p><p>刺眼的白光洒下，弹坑、铁丝网和远处模糊的地形一下子全部显出来。</p><p>但是看不见一个人影。</p><p>光线逐渐变暗，但你仍然不敢轻举妄动，直到队伍里开始有人慢慢爬了起来。</p><section><h2>“无人区”并不是“无人区”<a href="#无人区并不是无人区"><span>#</span></a></h2><p><strong>“无人区（No Man’s Land）”</strong> 指双方前沿阵地之间没有任何一方能够稳定控制的地带。它的宽度并不固定，有些地段双方相距较远，有些地方却近到可以听见对面的施工和说话声。</p><p>除巡逻和维修队外，夜间还可能有搜救伤员、回收遗体或试图捕获俘虏的小分队进入这里。巡逻可以用来观察敌方活动、搜集情报；堑壕突袭则可能进一步进入敌军阵地，杀伤守军、抓捕俘虏后撤回。俘虏能够提供部队和防御方面的情报，因此小规模行动也会有明确的情报目标。</p><p>较大的突袭需要炮兵、机枪手和工兵配合，有时会发展成一场事先准备的小型战斗。指挥官可能希望借此掌握敌情、保持部队警觉，执行任务的士兵却要承担很高的伤亡与被俘风险，对行动价值未必有相同看法。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Charles_Sensiers_Journal_during_the_Great_War_-_No_Mans_Land_in_Souchez%2C_January_1916--ff-c2158b58f15e.7w7m6cyh4r.webp" alt="1916 年 1 月苏谢前线从胸墙外拍摄的无人区，敌军阵地距拍摄点约 80 米" /><figcaption>1916 年 1 月苏谢前线从胸墙外拍摄的无人区，敌军阵地距拍摄点约 80 米</figcaption></figure><p></p><hr /></section></section>
<section><h1>01：30｜重返壕沟<a href="#0130重返壕沟"><span>#</span></a></h1><p>巡逻终于结束了。</p><p>你沿原路摸回阵地，抓住胸墙边缘翻进壕沟。</p><p>靴底重新踩到鸭板上。</p><p>这里还是潮湿、狭窄，到处是泥，却比几分钟前的无人区更像一个能够喘口气的地方。</p><p>你把装备放回身边，找地方坐下。还有人在值勤，远处偶尔响起炮声。睡眠不会持续太久，因为天亮以前，下一次 stand-to 又会开始。</p><p>不过，你也不会永远待在这里。</p><section><h2>部队轮换机制<a href="#部队轮换机制"><span>#</span></a></h2><p>部队轮换大体会在几个层次之间移动：</p>

<table><thead><tr><th>所在位置</th><th>主要任务</th><th>相对生活状态</th></tr></thead><tbody><tr><td>前沿壕</td><td>警戒、观察、直接防御</td><td>最接近敌军，狙击、炮击与突袭风险最高</td></tr><tr><td>支援壕 / 预备阵地</td><td>增援前沿、承担杂役、保存预备兵力</td><td>仍在战区内，但通常不必时时暴露在最前沿</td></tr><tr><td>战线后方</td><td>休整、清洁、维修、训练与补充</td><td>相对安全，可能住进村庄、农舍、谷仓或营地</td></tr></tbody></table><p>英国国家陆军博物馆用过一种常见安排作例子：前沿四天、预备位置四天、后方休整四天。各单位和战区的实际周期随战况与任务变化。进攻、伤亡或接替部队尚未到达，都可能打乱日程；同一时间，守备、支援与休整的部队会分别处在这套体系的不同位置。</p><hr /></section></section>
<section><h1>几天以后…<a href="#几天以后"><span>#</span></a></h1><p>黄昏的 stand-to 和往常一样结束。</p><p>天完全黑下来以后，壕沟里却比平时多出了一些陌生面孔。</p><p>接替你们的部队沿着交通壕一队接一队地上来。走在前面的军官和向导先确认各个位置，后面的士兵背着装备挤进前沿。你没有立刻离开，而是和来接替的人并排站了一会儿。</p><p>哪一段胸墙下午刚修过，哪个观察位置最容易遭到狙击，弹药放在哪里，水从哪边送来，夜里哪条路最容易堵——这些都得交代清楚。</p><p>新来的士兵接过位置。</p><p>你把步枪重新背好，扣紧装备，背起背包，然后转过身。</p><p>这一次，你终于可以回到后方进行休整。</p><p>队伍走得很慢。</p><p>迎面仍然不断有人向前。狭窄处一次只能过一边，你贴着湿冷的壕壁给一队搬运弹药的人让路，背包擦过沙袋。再往后走，又遇到两副担架。你们停下来，让担架员先过去。</p><p>前线的嘈杂声一点点远去。</p><section><h2>接防时要交接的记录与物资<a href="#接防时要交接的记录与物资"><span>#</span></a></h2><p>接防还涉及书面记录。《英军战壕命令范例》要求连长保管工作日志，逐日记录完成的工程，换防时交给接任者；另设战壕物资账册，接收方须清点并签收公用物资。尚未修完的工事因此能够继续施工，留在阵地里的器材也有明确的保管责任。</p><p>机枪也不一定与步兵同时换防。这份命令要求将两者的交接错开，避免步兵更换岗位时，机枪火力也一并出现空档。</p><p>工程维护还有另一套衔接办法：命令建议让少数工兵继续留在阵地，以保持施工的连续性。连队把所需工程材料报给营部，再由上级协调工兵单位提供，另行组织搬运。</p><hr /></section></section>
<section><h1>当晚｜先在预备壕睡一夜<a href="#当晚先在预备壕睡一夜"><span>#</span></a></h1><p>走了不知道多久，队伍终于停在一段更靠后的壕沟里。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Welsh_Guards_at_Guillemont_03-09-1916_IWM_Q_4415--ff-d0dbea5c8063.b9p22v9zq.webp" alt="1916 年 9 月索姆河吉耶蒙附近，威尔士近卫军士兵在一段预备壕中休息" /><figcaption>1916 年 9 月索姆河吉耶蒙附近，威尔士近卫军士兵在一段预备壕中休息</figcaption></figure><p></p><p>你把背包放下来，靠着壕壁坐了一会儿。</p><p>这里依然是战壕，却和前沿不太一样。胸墙外不再紧贴着无人区，也没有人一直盯着对面的动静。掩蔽部似乎更多，来往的人却没有少。</p><p>你继续往里走了一段，才发现这种差别不只体现在“离敌人远了一点”。</p><p>有些壕段明显比前沿宽，转角旁堆着弹药箱、水桶、木料和卷起的铁丝。壕壁上钉着简单的方向牌，电话线顺着木桩和泥墙向不同岔口延伸。某个掩蔽部入口旁，有传令兵停下来确认方向；另一处有人抱着文件进出，里面隐约能看见灯光。</p><p>再往后，一小块相对开阔的位置堆着更多物资。有人靠墙睡觉，有人正检查装备，还有担架从交通壕另一头被抬过来。</p><p>这里不像前沿那样每个人都朝着无人区，却显得更忙。</p><p>一队人正在把弹药继续往前送。</p><p>另一边有人搬食品和木料。</p><p>没过多久，你也被叫去帮忙搬了几只箱子。</p><p>再晚一些，一批伤员从前沿送下来。你和身边的人让开通道，看着担架从面前经过。</p><p>步枪始终放在伸手就能拿到的地方。</p><p>这天晚上，你终于不用直接守着无人区，却仍然睡在壕沟里。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_Battle_of_the_Somme%2C_July-november_1916_Q62--ff-2cd82407b583.4qs47c7o74.webp" alt="1916 年 7 月 1 日博蒙阿梅尔进攻前，英军士兵在支援壕中睡觉，身旁仍放着通往前沿的梯板" /><figcaption>1916 年 7 月 1 日博蒙阿梅尔进攻前，英军士兵在支援壕中睡觉，身旁仍放着通往前沿的梯板</figcaption></figure><p></p><section><h2>支援壕和预备壕还不算“后方”<a href="#支援壕和预备壕还不算后方"><span>#</span></a></h2><p>支援阵地仍属于防御部署。守军可以从这里向受攻击的前沿增援，也可以在敌人突入后投入反击；接防前，军官需要了解附近支援部队的位置和反击可用的前进路线。交通壕是否畅通、向导能否带人迅速到达指定位置，会直接影响这些兵力能否及时发挥作用。</p><p>前沿也不一定始终挤满全部守军。部分英军命令要求，在支援壕与交通壕条件允许时，白天只在最前方保留必要的哨兵、狙击手等人员，夜间再加强守备。其余人员可以利用较后的阵地休息和工作，同时保留应对警报的能力。</p><p>这些位置距离敌军更远，仍处在炮火威胁下；第一道防线被突破时，它们可能随即成为新的战斗位置。</p><hr /></section></section>
<section><h1>清晨｜离开战壕以后<a href="#清晨离开战壕以后"><span>#</span></a></h1><p>天刚亮，你们再次整队。</p><p>这一次没有沿着壕沟返回前沿，而是继续向后。</p><p>交通壕越来越浅。</p><p>泥墙先降到肩膀以下，再降到腰间，最后彻底消失。脚下从鸭板变成泥泞的道路。路边重新出现房屋、树木和还能辨认形状的农田。</p><p>你已经很久没有在这么开阔的地方走路了。</p><p>马车从旁边经过，一辆卡车溅起泥水。稍远一点的路上，还有另一队士兵朝相反方向前进。</p><p>你依然可以听见远处的炮声，只是这一次，不必每响一声就立刻判断它是不是朝自己来的。</p><p>最后，你们被安排进一座村庄里。</p><p>住处算不上真正的“房间”。几个人被分进一间谷仓，地上铺着干草，墙角已经堆着别人的背包和装备。</p><p>你把背包扔到铺位旁边，坐下来，终于有机会稍微打理一下自己。</p><p>没有鸭板，没有胸墙，头顶终于是一个完整的屋顶。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/British_soldiers_in_billets_France_%28Photo_24-318%29--ff-443f2fc910ab.77ecm9ek31.webp" alt="1915 年法国，一处宿营地外的两名英军士兵正在刮胡子" /><figcaption>1915 年法国，一处宿营地外的两名英军士兵正在刮胡子</figcaption></figure><p></p><section><h2>临时构筑的宿营地<a href="#临时构筑的宿营地"><span>#</span></a></h2><p>英军常用 <strong>billet</strong> 指部队临时驻扎的住处。</p><p>它没有统一形式，可能是：</p><ul>
<li>民房；</li>
<li>农舍；</li>
<li>谷仓；</li>
<li>学校或其他公共建筑；</li>
<li>临时营房。</li>
</ul><p>条件好的地方有完整的屋顶、相对干燥的地面和简单的家具；条件差的地方，可能仍然只是拥挤的谷仓或铺着干草的破屋。</p><p>它和真正意义上的“舒适住所”相差很远，但士兵终于可以在不守着胸墙的情况下睡觉、整理装备和处理个人卫生。</p><hr /></section></section>
<section><h1>第一天上午｜洗个“热”水澡<a href="#第一天上午洗个热水澡"><span>#</span></a></h1><p>刚住下没多久，命令就传来了：今天要去洗澡。你和同伴带着换洗物品排队往浴场走。</p><p>几天没有真正清洗过的身体终于碰到热水——或者至少是温水。</p><p>你脱下满是泥浆与虱子的制服，送去清洗、消毒或除虱，然后进入浴场，把身体冲洗干净。</p><p>终于重新穿上了相对干净一些的衣服，哪怕布料仍然粗糙，感觉也已经和昨天截然不同。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/New_Zealand_soldiers_recently_in_the_trenches_outside_the_Divisional_Baths%2C_France_%2821499630725%29--ff-06d64144ef2e.45igl1d7wd.webp" alt="1918 年法国伯特兰库尔，刚离开战壕的新西兰士兵在师属浴场外等待；类似浴场用于让撤出一线的部队洗浴和处理虱患" /><figcaption>1918 年法国伯特兰库尔，刚离开战壕的新西兰士兵在师属浴场外等待；类似浴场用于让撤出一线的部队洗浴和处理虱患</figcaption></figure><p></p><p>回到住处以后，你先把靴子上的泥刮掉，换上一双干袜子，再把步枪拆开重新清理。防毒面具、弹药和其他装备也要一件件检查。</p><p>脚上有一处磨破的地方已经疼了几天，现在也终于可以去找军医看看。</p><p>旁边还有人因为牙痛排队，也有人前来处理已经发炎的伤口。</p><p>等这些事情做完，已经过去了大半天。</p><section><h2>洗澡、除虱和医疗<a href="#洗澡除虱和医疗"><span>#</span></a></h2><p>前线恶劣的条件决定了很多问题无法在那里处理。</p><p>尤其是虱子。它们会藏在制服接缝中，所以只清洗身体并不能解决问题，衣物本身也需要清洗、消毒或除虱。</p><div><div><div></div><div>Note</div></div><div><p><strong>虱患与堑壕热</strong></p><p>体虱能够传播“堑壕热”（trench fever）。患者可能出现发热、头痛，以及腿部和背部的疼痛。</p></div></div><p>同时，前线还有许多必须要暂时忍耐的问题——例如：</p><ul>
<li>脚部磨伤；</li>
<li>战壕足早期症状；</li>
<li>皮肤感染；</li>
<li>牙痛；</li>
<li>小伤口感染；</li>
<li>装备损坏；</li>
</ul><p>这些问题要在撤出一线后集中处理，才能恢复人员与装备的状态。</p><hr /></section></section>
<section><h1>第一天晚上｜睡个“安稳觉”<a href="#第一天晚上睡个安稳觉"><span>#</span></a></h1><p>天黑以后，你重新回到谷仓。</p><p>终于可以脱下靴子，晾干衣服。</p><p>身边有人写信，有人倒头便睡；有人交谈，也有人掩面抽泣；有人靠墙点上一根香烟，也有人拆开家里寄来的包裹，和身边的人分食里面的东西。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/large_000000--ff-05510c1dc862.7w7m6a233q.webp" alt="1914 年第一次伊普尔战役期间，一批英军士兵在谷仓内休息；地面铺着稻草和毯子，步枪与个人装备靠墙放置" /><figcaption>1914 年第一次伊普尔战役期间，一批英军士兵在谷仓内休息；地面铺着稻草和毯子，步枪与个人装备靠墙放置</figcaption></figure><p></p><p>今晚，不用在夜间巡逻，也不用去修铁丝网。</p><p>你躺进干草里，此刻你只感觉倦意袭来。</p><p>你在昏黄的烛火光中沉沉睡去。</p><section><h2>睡眠质量依旧重要<a href="#睡眠质量依旧重要"><span>#</span></a></h2><p>前沿值勤通常按班轮替，睡眠会被哨位、杂役和临时警报打断。《英军战壕命令范例》要求连队安排工作、吃饭与休息时间，并尽量避免让尚未得到合理休息的士兵立即上哨；遇到无法避免的情况，则须向营长报告。</p><p>一份营级常备命令要求，夜间尽量安排双人哨，每次值勤不超过一小时，并把两人的交接错开半小时。新上岗的人可以先与尚未下岗的同伴共同观察，而不必让两个刚到岗位的人同时重新辨认周围动静。</p><p>撤到后方以后，较长的休息时间才更容易安排。脱下靴子、晾干衣服和装备，也不再像火线值勤时那样受到限制。不过，宿营地的拥挤、噪声与临时军务仍会打断睡眠。</p><hr /></section></section>
<section><h1>第二天｜并不能睡个懒觉<a href="#第二天并不能睡个懒觉"><span>#</span></a></h1><p>早晨，照样有人把你叫醒。早餐以后先点名，检查装备，接着便开始操练。</p><p>你和同伴在一块空地上重新练习步枪操作、队列和战术动作。新来的士兵跟着老兵一起训练，军官不断纠正他们的动作。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_British_Army_on_the_Western_Front_1914-1918_Q33326--ff-8e03253ced51.6writ3zbyp.webp" alt="一战期间，英军士兵在法国埃塔普勒附近的“Bull Ring”训练营进行刺刀训练" /><figcaption>一战期间，英军士兵在法国埃塔普勒附近的“Bull Ring”训练营进行刺刀训练</figcaption></figure><p></p><p>午饭以后，本以为可以稍微休息一会，却又被叫去修路。</p><p>一段道路被车辆压得坑坑洼洼。你拿着工具干了大半个下午，随后又帮忙搬了几批物资。</p><p>晚上回到住处时，手臂和肩膀早已累得酸痛。</p><p>所谓“休整”，和你原先想象的不太一样。</p><section><h2>无法免去的训练和劳动<a href="#无法免去的训练和劳动"><span>#</span></a></h2><p>英军所谓的 <strong>rest</strong> 包括恢复、训练和整顿。部队撤出一线以后，要把伤亡留下的缺额补起来，让新到士兵熟悉同伴、军官与本单位的工作方式。原有部队也需要检查和清洁武器，修理或更换损坏的装备。</p><p>训练会根据下一轮任务调整。除了队列、步枪操作和体能活动，还可能有战术讲解，以及进攻、防御和协同动作的练习。随着手榴弹、机枪等武器在小分队中的作用增加，士兵还要学习如何配合不同武器行动，利用火力和机动处理敌军据点。</p><p>休整单位同时也是后方可调用的人力。修筑道路、营地和新阵地，清理运输器材、搬运物资、协助前沿补给，都可能占用白天甚至夜间的时间。</p><hr /></section></section>
<section><h1>第二天晚上｜临时重返前沿<a href="#第二天晚上临时重返前沿"><span>#</span></a></h1><p>晚饭以后，天色渐渐暗了下来。你刚准备回谷仓，有人叫住了你。</p><p>今晚还缺几个人去送补给。</p><p>你和另外几名士兵跟着军士往村子另一头走。这里比前沿安静得多，却堆着完全不同的东西：成袋的面包、成箱的罐头、茶和糖，一些果酱，还有一口口正在烹饪中的铁锅——这些便是前线士兵今晚的晚餐。</p><p>负责补给的军士拿着单子核对数量。</p><p>哪一份属于哪一个连，哪几只箱子要送到哪一段阵地，都不能弄混。</p><p>你和同伴抬起一只装着热食的 <strong>dixie</strong>。旁边的人背上面包袋，有人提着饮水容器，还有人带着小件物资和信件。</p><p>离开村庄以后，路重新变得熟悉起来。</p><p>先是泥泞的道路，然后出现较浅的交通沟。再往前，壕壁一点点升到肩膀以上，你们重新进入战壕体系。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_British_Army_on_the_Western_Front%2C_1914-1918_Q4838--ff-ca28e3a8b761.3uvmrvxzr8.webp" alt="1917 年 3 月阿拉斯附近，英军第 6 营女王皇家团的配给搬运队抬着食物容器进入交通壕" /><figcaption>1917 年 3 月阿拉斯附近，英军第 6 营女王皇家团的配给搬运队抬着食物容器进入交通壕</figcaption></figure><p></p><p>离开前沿时看到的路，现在反过来走了一遍。</p><p>到了预备阵地，一部分物资需要交给等在那里的人；另一些还得继续向前运送。你们跟着队伍穿过支援壕，再进入更狭窄的交通壕。</p><p>越靠近前沿，路便越难走。</p><p>交通壕里十分泥泞，还有刚刚被炸塌的地方，你们得小心地绕过泥坑与废墟处。装满食物的大锅很沉，转弯时还要防止撞上壕壁。迎面有人经过，整支队伍就得贴到一边。</p><p>等真正来到前沿，dixie 已经不再烫手。</p><p>“今天是什么？”</p><p>有人先问。</p><p>掀开盖子以后，是一锅还带着一点温热的青菜汤。</p><p>其中一只箱子里，装着一些面包和果酱，大家都去拿了一些；有人听说还有茶，立刻问是不是还热着。</p><p>另一只箱子也被打开了，是一箱咸牛肉罐头，有人立刻说了一句：</p><p>“又是 bully beef。”</p><p>没人真的拒绝。</p><p>该吃的还是会被拿走。</p><p>你把最后一份东西交出去，又接过几个空箱子和需要带回后方的杂物。</p><p>今晚你不用留在前沿。</p><p>所以，你再次沿着交通壕往回走。</p><section><h2>为前沿的士兵送去热食<a href="#为前沿的士兵送去热食"><span>#</span></a></h2><p>大宗食品先经铁路、车辆和畜力运输进入战区，再逐级分送。接近阵地以后，道路、炮火与狭窄壕沟限制了车辆通行，最后一段往往要改用人力。这条供应链的几个环节，各有不同的组织任务：</p>

<table><thead><tr><th>环节</th><th>主要工作</th></tr></thead><tbody><tr><td>后方运输</td><td>铁路、车辆与畜力接续转运</td></tr><tr><td>营、连补给点</td><td>领取、分份与烹调</td></tr><tr><td>配给搬运队</td><td>人工送入交通壕与阵地</td></tr><tr><td>前沿分发</td><td>按守备位置和值勤安排分送</td></tr></tbody></table><p>《英军战壕命令范例》规定，前沿口粮和物资通常由支援壕、预备阵地组成的搬运队送上去，连军需军士随队到达。只有无法这样安排时，才抽调前沿人员；其中一份命令还规定，同一时间离开火线的人员不得超过守军的十分之一。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/British_Army_on_the_Western_Front_1914-1918_Q1766--ff-b4b7bc80cd31.83au1po8iw.webp" alt="1917 年 1 月，法国鲁昂的一处英军补给站堆放着大量装箱口粮" /><figcaption>1917 年 1 月，法国鲁昂的一处英军补给站堆放着大量装箱口粮</figcaption></figure><p></p><p>补给并没有适用于所有阵地的固定送达时刻。较暴露的路线可以借黄昏和夜色降低被观察的风险；交通壕较完善、敌情允许时，白天也能运送。搬运队还要尽量少发出声音，食物能否保温则取决于路程和沿途耽搁。</p><p>补给安排也照顾夜间值勤。命令要求争取在午夜到凌晨四点之间提供汤或热饮，未用完的口粮则交回军需人员。这里记录的是当时的供应要求，实际能否做到仍受炮击、道路与物资条件影响。</p><hr /></section></section>
<section><h1>第三天中午｜坐着吃完一顿饭<a href="#第三天中午坐着吃完一顿饭"><span>#</span></a></h1><p>第三天的早上，依旧是训练时间。</p><p>中午，你终于可以端着饭盒，坐在一张还算像样的桌子旁边。</p><p>这一次不用给站岗的人让位置，也不用担心被一发炮弹打得食欲全无。</p><p>此时，桌面上的炖菜还冒着热气。</p><p>面包没有在泥水里颠簸几个小时才送到手中，茶杯里的茶水也还是热的。</p><p>你慢慢把东西吃完。</p><p>内容未必比前线丰富多少，但“坐着吃完”本身已经是一种享受。</p><p>下午有人说，村里那家 <strong>estaminet</strong> 还开着。</p><p>你和几个同伴一起走过去。</p><p>门面很小，里面摆着简单的桌椅。你们会的法语加起来也没有几句，只能一边说、一边比划。</p><p>好不容易，终于弄明白了菜单。</p><p>你掏出包里的一点零钱，买了一份煎蛋，又加了一点薯条和咖啡，旁边的人则点了一杯啤酒。有人抱怨价格，有人学着当地人的发音念菜名，结果把整桌人都逗笑了。</p><p>这顿饭和军队配给没有什么关系。</p><p>也正因为如此，它格外像战前生活里的普通一餐。</p><section><h2>后方的饮食更接近正常生活<a href="#后方的饮食更接近正常生活"><span>#</span></a></h2><p>后方饮食的改善，首先来自炊事与供应条件。较完整的厨房和食堂能够集中烹调、烧水，食物也不用再经过通往火线的最后一段运输。军队的日常配给、士兵自行购买的食品和家中包裹，共同构成了实际吃到的东西。</p><p><strong>Estaminet</strong> 是英军对法国和比利时当地小酒馆、简易餐馆的常用称呼。这些店铺由当地人经营，士兵使用军饷消费；它们与军队按编制发放口粮的安排是两回事。能否外出、可以买到什么，以及能花多少钱，仍受到军纪、当地供应和个人余钱的限制。</p><p>军队食堂和 YMCA 等休息点则提供另一种选择，常见的是茶、可可与简单食品。军队和慈善团体经营这些设施，也希望士兵在酒馆之外有可去的地方，使休息和社交更容易组织。</p><hr /></section></section>
<section><h1>第三天下午｜写一封家书<a href="#第三天下午写一封家书"><span>#</span></a></h1><p>从 estaminet 出来以后，你去了附近的 YMCA 小屋。</p><p>里面有桌子、椅子和灯。</p><p>有人在喝茶，有人在看报，还有几个人围在一起打牌。</p><p>你找了一个位置坐下，摊开一张被揉的皱皱巴巴的纸。</p><p>笔尖点在纸上，却不知道先写什么。</p><p>前线的很多事情不能写，也不想写。</p><p>于是你先写天气，再写自己一切都好，刚刚洗过澡，吃到一顿热饭，村子里还有一家小酒馆。最后问家里最近怎么样。</p><p>如果包裹也在这几天送到，里面可能有巧克力、糖果、香烟、袜子，或者一张照片。你把能分的东西拿出来分享，身边的人也会这样做。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_Young_Mens_Christian_Association_on_the_Western_Front%2C_1914-1918_Q5402--ff-b09cc8ed4fef.2dphq7q915.webp" alt="1917 年法国圣皮埃尔一处 YMCA 小屋内部，士兵可以在这类设施中休息、阅读和购买简单饮食" /><figcaption>1917 年法国圣皮埃尔一处 YMCA 小屋内部，士兵可以在这类设施中休息、阅读和购买简单饮食</figcaption></figure><p></p><p>写完信以后，你没有立刻回住处。</p><p>桌上正好有几份旧报纸。</p><p>你翻了一会儿。</p><p>上面记载的俨然是另一个世界的事情。</p><section><h2>邮件、包裹与 YMCA<a href="#邮件包裹与-ymca"><span>#</span></a></h2><p>军邮由专门的人员和机构运转。英国寄往部队的邮件先在伦敦的 Home Postal Depot（本土军邮分拣中心）集中处理，再转往海外军邮系统。皇家工兵邮政分队（Royal Engineers Postal Section）从邮政部门招募熟悉业务的人员，负责为军队提供服务。投递需要跟随部队的位置变化，同时避免公开暴露军事部署。</p><div><div><div></div><div>Note</div></div><div><p><strong>家书审查与军用明信片</strong></p><p>普通信件通常由部队军官审查，涉及军事机密的内容会被删去。士兵有时可以使用绿色信封，让私人信件交由后方不认识自己的人员审查。</p><p>英军还使用制式的 Field Service Postcard（野战勤务明信片），卡上预先印好固定句子。士兵划去不适用的选项，再填写日期和签名，便可传递简短消息，通常不允许自行添写其他内容。</p></div></div><p><strong>YMCA，即基督教青年会</strong>，则是为士兵提供休息和服务的社会组织。它在战争期间设置小屋和休息设施，供应饮食，也提供免费的信纸与信封。这里的工作人员、照明、桌椅和阅览空间，使暂时离开前沿的人更容易完成写信、阅读等日常活动。</p><hr /></section></section>
<section><h1>第四天下午｜难得的个人时光<a href="#第四天下午难得的个人时光"><span>#</span></a></h1><p>这天下午没有额外杂役。</p><p>有人不知道从哪里找来一个足球。</p><p>于是，空地很快被划成临时球场。没有正规的球门，衣服和背包都能拿来做标记。</p><p>你跟着踢了一阵。</p><p>鞋底仍然沾着泥。</p><p>但这一次，泥只意味着球场太湿，而不是战壕快要积水。</p><p>旁边站着不少人看。</p><p>有人起哄，有人笑，也有人根本没兴趣，只坐在一旁抽烟。</p><p>到了晚上，附近又有一场临时演出。</p><p>你跟着人群挤进屋里。</p><p>节目很简单：唱歌、笑话、模仿，也夹着一小段戏曲。</p><p>有人唱得很好。</p><p>也有人明显只是故意出丑。</p><p>台下却一直有人笑。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/Entertainment_in_the_British_Army_on_the_Western_Front%2C_1914-1918_Q8959--ff-432cac8e1f07.1vzg1jshf9.webp" alt="1918 年 5 月，英军“The Queerios”慰问演出团在蒙特勒伊附近为军人表演" /><figcaption>1918 年 5 月，英军“The Queerios”慰问演出团在蒙特勒伊附近为军人表演</figcaption></figure><p></p><p>如果没有那阵始终隐约传来的炮声，这几个小时甚至很容易让人忘记自己还身处异乡。</p><p>演出结束以后，你和同伴摸黑走回住处。</p><p>路上碰到一辆救护车。</p><p>车灯被遮得很暗。</p><p>它从前线方向驶来。</p><p>人群自动给它让开道路。</p><p>刚才的欢乐一下便被抛之脑后。</p><section><h2>后方还有哪些娱乐？<a href="#后方还有哪些娱乐"><span>#</span></a></h2><p>除了足球和临时演出，士兵在后方还可能接触到：</p><ul>
<li>其他体育活动；</li>
<li>打牌、聊天和抽烟等最简单的消遣；</li>
<li>看书、读报，或使用营地和俱乐部里的简易阅览空间；</li>
<li>去食堂、俱乐部或 estaminet 喝茶、咖啡或酒，购买额外食品；</li>
<li>军乐或小型音乐会；</li>
<li>士兵自行组织的 <strong>concert party</strong>；</li>
<li>社交俱乐部；</li>
<li>宗教礼拜和随军牧师主持的活动。</li>
</ul><p><strong>Concert party</strong> 指组织起来演出的娱乐队伍，可以包括歌唱、短剧、喜剧与音乐厅式节目。一些演出临时凑成，后来也出现师、军一级的专门演出队，从不同部队选出演员。舞台、服装与道具常因陋就简，成员调动或伤亡又会迫使队伍重新组合。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/British_soldiers_at_play_France_%28Photo_24-320%29--ff-c4a656f5222b.7w7m6a233e.webp" alt="1915 年法国，几名英军士兵在休息时围坐打牌" /><figcaption>1915 年法国，几名英军士兵在休息时围坐打牌</figcaption></figure><p></p><p>具体条件因驻地而异。有些营地设施比较齐全，有些地方则几乎什么都没有。</p><hr /></section></section>
<section><h1>第五天｜仍然身处战争之中<a href="#第五天仍然身处战争之中"><span>#</span></a></h1><p>第五天，你被安排去帮忙搬运物资。</p><p>一路上，看到的东西和前线完全不同。</p><p>卡车装着弹药和食品驶向前方，马车拖着物资经过，一辆救护车从相反方向驶来。不远处还有兵员列队向前。</p><p>路边堆着箱子和木料，稍远的地方可能是仓库、修理点、医疗单位或铁路设施。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/The_Battle_of_the_Somme%2C_July-november_1916_Q1413--ff-d05f174b4bb9.64enbdiq82.webp" alt="1916 年 9 月索姆河后方，阿尔贝—亚眠公路上的陆军勤务团运输卡车与燃料桶堆场" /><figcaption>1916 年 9 月索姆河后方，阿尔贝—亚眠公路上的陆军勤务团运输卡车与燃料桶堆场</figcaption></figure><p></p><p>这里没有弹坑与铁丝网，却到处都是战争留下的痕迹。</p><p>你只是从直接开枪和挨炮的地方，退到了维持这一切继续运转的地方。</p><section><h2>伤员后送与物资回收<a href="#伤员后送与物资回收"><span>#</span></a></h2><p>向前送去兵员与物资的同时，战区还要组织伤员后送。伤员可能先经过靠近部队的团救护所，再转到较后的包扎站与伤员收容机构，随后送往基地医院。不同位置承担初步处置、继续救护和转运任务，能够步行的伤员与必须由担架搬运的人，也需要不同的安排。</p><p>《英军战壕命令范例》规定，无法自行携带装备的伤员，其步枪与装备应随同后送至救护站；其余损坏或不再需要的步枪和装备交回军需人员。损坏的枪弹与空弹壳也须收集后送，纳入回收管理。</p><p>伤员、装备和空容器的后送，与补给和增援的前送共用有限的道路与运输能力。前线战斗越激烈，两个方向的需求就可能同时增加，救护站、车辆和通道都需要重新分配。后方通常比前沿安全，但远程炮火、空袭和交通事故仍可能造成伤亡。</p><hr /></section></section>
<section><h1>几天之后｜重返前线<a href="#几天之后重返前线"><span>#</span></a></h1><p>整队的命令再次传来。</p><p>你把刚清理过没几天的制服穿好，步枪背回肩上。家里寄来的包裹里还剩下一点东西，被重新塞进背包。</p><p>有人还在抱怨昨天的训练劳累，有人已经开始猜这次会被送到哪一段阵地。</p><p>队伍缓缓离开村庄，房屋和树木慢慢被甩在身后。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261002-WWI-ST1-trench-life/British_Army_on_the_Western_Front%2C_1914-1918._Q518--ff-700aad0226be.4922ir6ali.webp" alt="1916 年 4 月，忠诚北兰开夏团士兵在法国瓦伊列队准备返回战壕" /><figcaption>1916 年 4 月，忠诚北兰开夏团士兵在法国瓦伊列队准备返回战壕</figcaption></figure><p></p><p>普通道路重新变成交通壕。</p><p>壕壁一点点升高，枪炮声也重新变得清晰可辨。</p><p>最后，那道熟悉的胸墙再次出现在前方。</p><p>你把背包放下来，重新放置好步枪，等待着下一次 stand-to。</p><p>不过，你心里也深知，等到下一次休整的时候，可能又会少几张熟悉的面孔。</p><p>也可能再也等不到下一次休整。</p><section><h2>接防后的确认工作<a href="#接防后的确认工作"><span>#</span></a></h2><p>回到阵地以后，排长要逐一检查士兵的射击位置。《英军战壕命令范例》要求确认每个人都能从自己的位置射击到最近一段己方铁丝网的下沿，检查胸墙、踏板与附近地形是否遮挡射界。</p><p>士兵也须确认本次指派区域附近的排长掩蔽部与连指挥所的位置。炮击、毒气警报或敌军进攻发生时，连内的传令兵、勤务人员等也各有职责，军官会通过警报演练检查这些安排。</p><hr /></section></section>
<section><h1>又一个清晨<a href="#又一个清晨"><span>#</span></a></h1><p>你趴在壕壁上，睡了不知道多久。</p><p>肩膀又被人推了两下。</p><p>远处仍然有炮声。</p><p>壕沟里已经有人开始移动。</p><p>你坐起来，摸到身旁的步枪，靴子踩上那块仍旧潮湿的鸭板。</p><p>“Stand-to。”</p><p>你跟着前面的人向胸墙走。</p><p>再次站上射击踏板。</p><p>再次面向无人区。</p><p>昨天修过的壕壁还在，下午炸坏的地方也已经被临时补好；夜里送来的弹药放在需要的位置，下一批补给还会继续沿交通壕向前。</p><p>天边开始发白。</p><p>前沿值勤、夜间劳动、后方休整与再次接防，组成了士兵反复经历的生活。睡眠、热饭和一封家书带来短暂喘息，随后又要回到那些被雨水浸泡、被炮火摧毁、再由人重新修好的壕沟里。</p><p>一个二十四小时过去，还有下一个二十四小时。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>National Army Museum — Walter Tull 教学资料</strong><br />
按一天的顺序介绍英军常见的黎明 stand-to、早餐前清理步枪、装备检查、日间杂役、黄昏 stand-to，以及夜间修铁丝网和巡逻等活动。<br />
<a href="https://www.nam.ac.uk/sites/default/files/learning-resource-files/2020-09/Transcript-WalterTull_FINAL.pdf" target="_blank">National Army Museum：Walter Tull transcript</a></p>
</li>
<li>
<p><strong>National Army Museum — Rifleman William Eve</strong><br />
William Eve 的 1915 年日记记录了前线值勤、修壕、填装和搬运沙袋、狙击手威胁、潮湿环境以及足部问题，可用于理解战斗之外的大量重复劳动。<br />
<a href="https://ww1.nam.ac.uk/stories/rifleman-william-eve/" target="_blank">National Army Museum：Rifleman William Eve</a></p>
</li>
<li>
<p><strong>National Army Museum — 1915: Early trench battles</strong><br />
介绍西线转入堑壕战后的早期发展，以及天气、积水、炮击和不断演化的防御工事。<br />
<a href="https://www.nam.ac.uk/explore/1915-early-trench-battles" target="_blank">National Army Museum：1915: Early trench battles</a></p>
</li>
<li>
<p><strong>Imperial War Museums — First World War Galleries</strong><br />
展览资料涉及堑壕、防御工事、前线饮食与“铁口粮”，也介绍了撤到后方的士兵如何使用军饷在法国城镇的 estaminet 消费、参加宗教活动以及收取邮件与包裹等生活，并说明部队军官审查家书及绿色信封的用途。
<a href="https://www.iwm.org.uk/sites/default/files/files/2023-10/first_world_war_large_print_guide.pdf" target="_blank">Imperial War Museums：First World War large print guide</a></p>
</li>
<li>
<p><strong>Australian War Memorial — First World War Galleries: Object Backgrounder</strong><br />
介绍西线堑壕、防御工事、鸭板、迫击炮、照明弹、机枪、无人区巡逻与突袭。<br />
<a href="https://www.awm.gov.au/media/press-releases/first-world-war-galleries-object-backgrounder" target="_blank">Australian War Memorial：First World War Galleries — Object Backgrounder</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Henry Stanley Davis diaries</strong><br />
Henry Stanley Davis 的日记涉及 1916 年法国的潮湿与泥泞、西线堑壕生活、波济耶尔战斗以及部队进出前线的经历。<br />
<a href="https://www.awm.gov.au/collection/C2085233" target="_blank">Australian War Memorial：Transcript of diaries of Henry Stanley Davis, 1915–1917</a></p>
</li>
<li>
<p><strong>1914-1918 Online: International Encyclopedia of the First World War — Warfare 1914–1918</strong><br />
从战术和作战层面解释堑壕体系在战争期间的演变，以及火力、后勤和增援能力为何使西线突破变得困难。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/warfare_1914-1918" target="_blank">1914-1918 Online：Warfare 1914–1918</a></p>
</li>
<li>
<p><strong>National Army Museum — Welsh Guards in a reserve trench at Guillemont</strong><br />
馆藏说明明确区分前沿壕、支援壕和预备壕，并指出士兵并不会始终留在最前线；未在前沿的部队可能处于支援位置，或撤到后方休息和训练。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2002-02-589-6" target="_blank">National Army Museum：Welsh Guards in a reserve trench at Guillemont</a></p>
</li>
<li>
<p><strong>National Army Museum — Soldiers outside the divisional baths near St. Pol</strong><br />
资料记录士兵离开前线后会定期使用浴场清洗，并在脱离火线期间进行除虱处理，可用于理解轮换后的卫生与休整生活。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1978-11-157-24-18" target="_blank">National Army Museum：Soldiers outside the divisional baths</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Albert Jacka VC lecture notes: Trench Warfare</strong><br />
一战时期的战壕作战讲义记录了接防通常利用夜色进行，以减少敌军观察，并强调向导、路线、物资和岗位交接的重要性。<br />
<a href="https://transcribe.awm.gov.au/transcription/item/awm201878510-4" target="_blank">Australian War Memorial：Lecture notes written by Albert Jacka VC</a></p>
</li>
<li>
<p><strong>National Army Museum — An army marches on its stomach</strong><br />
介绍一战英军日常配给、茶、面包、果酱、罐头食品与“铁口粮”，并说明前沿烹饪困难、士兵常将 bully beef、饼干、培根和蔬菜煮成简单炖食。<br />
<a href="https://www.nam.ac.uk/explore/army-marches-its-stomach" target="_blank">National Army Museum：An army marches on its stomach</a></p>
</li>
<li>
<p><strong>National Army Museum — A cook preparing food in a dixie on a charcoal fire at the front, 1915</strong><br />
说明前沿热食的烹调方式，以及 bully beef、饼干、培根、蔬菜和私人包裹中的食品如何被加入同一锅炖食，再分装到士兵的饭盒中。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2007-03-7-109" target="_blank">National Army Museum：A cook preparing food in a dixie</a></p>
</li>
<li>
<p><strong>National Army Museum — Battery Quartermaster Sergeant Samuel Pye</strong><br />
介绍轮换日程的示例、撤出一线后的训练与杂役，以及后方图书设施、食堂、教堂、体育和 concert party 的组织与演出。<br />
<a href="https://ww1.nam.ac.uk/stories/battery-quartermaster-sergeant-samuel-pye/" target="_blank">National Army Museum：Battery Quartermaster Sergeant Samuel Pye</a></p>
</li>
<li>
<p><strong>National Army Museum — British soldiers eating in a trench, 1917 (c)</strong><br />
馆藏照片记录士兵在战壕中开启罐头食品，也再次说明前沿、支援、预备阵地与后方休整之间的轮换关系。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2002-02-589-7" target="_blank">National Army Museum：British soldiers eating in a trench</a></p>
</li>
<li>
<p><strong>Specimens of British Trench Orders — Ration parties / Rations and cooking</strong><br />
收录一战时期加拿大军团等部队的战壕命令，涉及武器与卫生检查、通信线路维护、夜间工作掩护、巡逻通知、口粮搬运、哨兵与休息安排、换防记录与物资签收、工程衔接、装备回收及警报演练。
<a href="https://www.gutenberg.org/files/53126/53126-h/53126-h.htm" target="_blank">Project Gutenberg：Specimens of British Trench Orders</a></p>
</li>
<li>
<p><strong>National Army Museum — Battle of Menin Road: North-country troops bring up rations, 1917</strong><br />
馆藏说明记录了把食物和饮水送往前沿的危险，指出配给搬运队可能受到狙击威胁，部分地段还会调整运输时间和路线以降低风险。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=1999-11-70-35" target="_blank">National Army Museum：North-country troops bring up rations</a></p>
</li>
<li>
<p><strong>National Army Museum — “June 27th 1918: ‘3 day Fever’”</strong><br />
馆藏说明介绍体虱传播堑壕热，以及发热、头痛和腿部、背部疼痛等症状。<br />
<a href="https://collection.nam.ac.uk/detail.php?acc=2006-05-31-1" target="_blank">堑壕热患者素描与馆藏说明</a></p>
</li>
<li>
<p><strong>National Army Museum — Christmas greetings from the front line</strong><br />
关于 Field Service Postcard 预印句子、划去选项、日期和签名等使用规则。<br />
<a href="https://www.nam.ac.uk/explore/christmas-greetings-front-line" target="_blank">Christmas greetings from the front line</a></p>
</li>
<li>
<p><strong>National Army Museum — The Brodie Helmet</strong>
介绍英军 1915 年采用布罗迪钢盔、单片钢板冲压制造、宽檐防护及 1916 年配发普及的过程。
<a href="https://ww1.nam.ac.uk/1904/news/brodie-helmet/" target="_blank">英国国家陆军博物馆：布罗迪钢盔</a></p>
</li>
<li>
<p><strong>Canadian War Museum — Communication；Trench Raids；Trench Weapons；Supplying War</strong><br />
分别支持电话与其他通信手段的限制、巡逻与突袭的任务、小分队武器配合，以及从铁路站点到前沿的人力转运。<br />
<a href="https://www.warmuseum.ca/firstworldwar/history/battles-and-fighting/tactics-and-logistics-on-land/communication/" target="_blank">通信</a>、<a href="https://www.warmuseum.ca/firstworldwar/history/battles-and-fighting/tactics-and-logistics-on-land/trench-raids/" target="_blank">堑壕突袭</a>、<a href="https://www.warmuseum.ca/firstworldwar/history/battles-and-fighting/weapons-on-land/trench-weapons/" target="_blank">堑壕武器</a>、<a href="https://www.warmuseum.ca/firstworldwar/history/battles-and-fighting/tactics-and-logistics-on-land/supplying-war/" target="_blank">战争补给</a></p>
</li>
<li>
<p><strong>The Postal Museum — Search Room Guide：Army Postal History</strong><br />
馆藏指南介绍皇家工兵邮政分队、本土军邮分拣中心、海外军邮机构，以及军事地址和分拣安排。<br />
<a href="https://www.postalmuseum.org/wp-content/uploads/2016/10/Search-Room-Guide.pdf" target="_blank">英国邮政博物馆档案指南</a></p>
</li>
<li>
<p><strong>World YMCA — England &amp; Wales</strong><br />
机构历史说明一战期间面向军人的饮食服务，以及免费信纸和信封供应。<br />
<a href="https://www.ymca.int/countries/england-wales/" target="_blank">世界基督教青年会历史介绍</a></p>
</li>
<li>
<p><strong>Jessica Meyer — An Equal Burden: The Men of the Royal Army Medical Corps in the First World War，第三章“Carrying, Cleaning, Caring”</strong><br />
依据军医勤务人员经历与相关档案，讨论团救护所、包扎站和伤员收容机构之间的救护与后送分工。<br />
<a href="https://www.ncbi.nlm.nih.gov/books/NBK541851/" target="_blank">曼彻斯特大学出版社开放获取章节（NCBI Bookshelf）</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>1916 年 7 月索姆河战役期间，奥维莱尔附近被英军占据的一段德军战壕</strong>：<a href="https://commons.wikimedia.org/wiki/File:Cheshire_Regiment_trench_Somme_1916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者 John Warwick Brooke；Imperial War Museums 馆藏，编号 Q 3990；英国政府作品，Commons 标记为 <a href="https://commons.wikimedia.org/wiki/Template:PD-BritishGov" target="_blank">PD-BritishGov</a>，属公有领域。</p>
</li>
<li>
<p><strong>1917 年 10 月，澳大利亚第 1 师士兵在帕申达勒附近铺设鸭板</strong>：<a href="https://commons.wikimedia.org/wiki/File:1st_Division_laying_duckboards_over_the_muddy_ground_in_Passchendaele_%28AWM_E00837%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者 James Francis（Frank）Hurley；Australian War Memorial 馆藏编号 E00837；Commons 页面标记其因澳大利亚版权期限届满而进入公有领域。</p>
</li>
<li>
<p><strong>1917 年 7 月洛斯—于吕什地区的空中侦察照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Aerial_view_Loos-Hulluch_trench_system_July_1917.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者不详；Imperial War Museums 馆藏，编号 Q 45786；英国政府作品，Commons 标记为 <a href="https://commons.wikimedia.org/wiki/Template:PD-BritishGov" target="_blank">PD-BritishGov</a>，属公有领域。</p>
</li>
<li>
<p><strong>1917 年 3 月阿拉斯附近，英军士兵在积雪覆盖的战壕中用餐</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_British_Army_on_the_Western_Front,_1914-1918_Q4846.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏编号 Q 4846；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年 8 月索姆河奥维莱尔，一名伍斯特郡团哨兵透过战壕射孔观察前方</strong>：<a href="https://commons.wikimedia.org/wiki/File:Worcester_Regiment_sentry_in_trench_Ovillers_1916_IWM_Q_4100.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏编号 Q 4100；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年 7 月 1 日索姆河博蒙阿梅尔，英军炮兵轰击德军阵地</strong>：<a href="https://commons.wikimedia.org/wiki/File:Beaumont_Hamel_bombardment_01-07-1916_IWM_Q_90.jpg" target="_blank">Wikimedia Commons 原图页</a>。Royal Engineers No. 1 Printing Company 摄制；Imperial War Museums 馆藏编号 Q 90；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1917 年 1 月康布雷一带，英军杂役队夜间沿支援壕搬运鸭板</strong>：<a href="https://commons.wikimedia.org/wiki/File:Trench_at_night.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者 John Warwick Brooke；Imperial War Museums 馆藏，编号 Q 6420；英国政府作品，Commons 标记为 <a href="https://commons.wikimedia.org/wiki/Template:PD-BritishGov" target="_blank">PD-BritishGov</a>，属公有领域。</p>
</li>
<li>
<p><strong>1916 年 1 月苏谢前线从胸墙外拍摄的无人区</strong>：<a href="https://commons.wikimedia.org/wiki/File:Charles_Sensier%27s_Journal_during_the_Great_War_-_No_Man%27s_Land_in_Souchez,_January_1916.jpg" target="_blank">Wikimedia Commons 原图页</a>。Charles Sensier 摄，源自 Europeana 1914–1918 收藏；当前 Commons 文件由原日记图像裁切而来，按 <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>1916 年 9 月索姆河吉耶蒙附近，威尔士近卫军士兵在预备壕中休息</strong>：<a href="https://commons.wikimedia.org/wiki/File:Welsh_Guards_at_Guillemont_03-09-1916_IWM_Q_4415.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏编号 Q 4415；Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年 7 月 1 日博蒙阿梅尔进攻前，英军士兵在支援壕中睡觉</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Battle_of_the_Somme,_July-november_1916_Q62.jpg" target="_blank">Wikimedia Commons 原图页</a>。Royal Engineers No. 1 Printing Company 摄制；Imperial War Museums 馆藏编号 Q 62；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1915 年法国，一处宿营地外的两名英军士兵正在刮胡子</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_soldiers_in_billets_France_%28Photo_24-318%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。H. D. Girdwood 摄，British Library Photo 24/(318)；英国政府作品，Commons 标记为公有领域。该系列照片具有宣传摄影背景，部分画面可能经过摆拍，因此本文仅将其用于展示宿营环境与日常清洁场景。</p>
</li>
<li>
<p><strong>1918 年法国伯特兰库尔，刚离开战壕的新西兰士兵在师属浴场外等待</strong>：<a href="https://commons.wikimedia.org/wiki/File:New_Zealand_soldiers_recently_in_the_trenches_outside_the_Divisional_Baths,_France_%2821499630725%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。Henry Armytage Sanders 摄；原藏于 Alexander Turnbull Library / National Library of New Zealand。照片记录士兵离开前线后等待进入师属浴场的情景。</p>
</li>
<li>
<p><strong>1914 年第一次伊普尔战役期间，英军士兵在谷仓内休息</strong>：<a href="https://www.iwm.org.uk/collections/item/object/205306673" target="_blank">Imperial War Museums 馆藏页</a>。馆藏编号 Q 57278；照片展示一批英军士兵在木制谷仓内休息，地面铺有稻草和毯子，步枪及个人装备靠墙摆放，可用于观察西线早期临时 billet 的内部环境。</p>
</li>
<li>
<p><strong>一战期间，英军士兵在法国埃塔普勒附近的“Bull Ring”训练营进行刺刀训练</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_British_Army_on_the_Western_Front_1914-1918_Q33326.jpg" target="_blank">Wikimedia Commons 原图页</a>。英国官方摄影师摄；Imperial War Museums 馆藏编号 Q 33326；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1917 年 3 月阿拉斯附近，英军第 6 营女王皇家团配给搬运队抬着食物容器进入交通壕</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_British_Army_on_the_Western_Front,_1914-1918_Q4838.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏编号 Q 4838。照片记录配给搬运队携带食物容器进入并沿交通壕前进。</p>
</li>
<li>
<p><strong>1917 年 1 月，法国鲁昂英军补给站堆放的大量装箱口粮</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_Army_on_the_Western_Front_1914-1918_Q1766.jpg" target="_blank">Wikimedia Commons 原图页</a>。Ernest Brooks 摄；Imperial War Museums 馆藏编号 Q 1766；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1917 年法国圣皮埃尔一处 YMCA 小屋内部</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Young_Men%27s_Christian_Association_on_the_Western_Front,_1914-1918_Q5402.jpg" target="_blank">Wikimedia Commons 原图页</a>。John Warwick Brooke 摄；Imperial War Museums 馆藏编号 Q 5402；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1918 年 5 月，英军“The Queerios”慰问演出团在蒙特勒伊附近演出</strong>：<a href="https://commons.wikimedia.org/wiki/File:Entertainment_in_the_British_Army_on_the_Western_Front,_1914-1918_Q8959.jpg" target="_blank">Wikimedia Commons 原图页</a>。David McLellan 摄；Imperial War Museums 馆藏编号 Q 8959；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1915 年法国，几名英军士兵在休息时围坐打牌</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_soldiers_at_play_France_%28Photo_24-320%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。H. D. Girdwood 摄，British Library Photo 24/(320)；英国政府作品，Commons 标记为公有领域。该图来自 Girdwood 为印度政府制作的一组宣传摄影，馆藏说明提示其中部分照片可能经过摆拍，因此本文仅将其用于展示士兵休整时的娱乐与日常生活场景。</p>
</li>
<li>
<p><strong>1916 年 9 月索姆河后方，阿尔贝—亚眠公路上的陆军勤务团运输卡车与燃料桶堆场</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Battle_of_the_Somme,_July-november_1916_Q1413.jpg" target="_blank">Wikimedia Commons 原图页</a>。Ernest Brooks 摄；Imperial War Museums 馆藏编号 Q 1413；英国政府作品，Commons 标记为公有领域。</p>
</li>
<li>
<p><strong>1916 年 4 月，忠诚北兰开夏团士兵在法国瓦伊列队准备返回战壕</strong>：<a href="https://commons.wikimedia.org/wiki/File:British_Army_on_the_Western_Front,_1914-1918._Q518.jpg" target="_blank">Wikimedia Commons 原图页</a>。Ernest Brooks 摄；Imperial War Museums 馆藏编号 Q 518；英国政府作品，Commons 标记为公有领域。</p>
</li>
</ol></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/261001-wwi-4-verdun-somme-attrition/</id>
      <title type="text">WWI-4 血腥的僵局：凡尔登、索姆河与无止境的消耗战</title>
      <published>2026-10-01T00:00:00.000Z</published>
      <updated>2026-10-01T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/261001-wwi-4-verdun-somme-attrition/"/>
      <summary type="text">1916 年，各国试图用更长、更密集的战斗让对手先失去继续战争的能力。从凡尔登的要塞，到索姆河的炮火，分析消耗战为何看似能够通向胜利，却把双方的军队与整个社会一同推向深渊。</summary>
      <content type="html"><![CDATA[
<p>1916 年 2 月，很少有人能想到，法国东北部的凡尔登不久就会变成一片几乎寸草不生的战场。</p>
<p>默兹河穿城而过，周边的山地、树林与村庄之间，分布着一些要塞。从地图上看，这里只是西线向德军方向伸出的一处突出部。</p>
<p>而在接下来的几个月里，法国与德国会将数十万士兵、一批批重炮和难以计数的炮弹送往这里。</p>
<p>炮火一遍遍犁过这片土地，将一切化为焦土，留下连绵的弹坑。即使只为夺取一处阵地，也可能付出数以万计的伤亡。</p>
<p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/French-soldiers-of-the-87th-Regiment-shelter-in-their-trenches-at-Hill-304-at-Verdun--ff-ca15139ba785.2h93nul5hp.webp" alt="1916 年凡尔登 304 高地战壕中的法国第 87 步兵团士兵" /><figcaption>1916 年凡尔登 304 高地战壕中的法国第 87 步兵团士兵</figcaption></figure><p></p>
<p>1915 年，毒气、潜艇、加里波利远征与东线突破都未能结束战争。</p>
<p>于是，到了 1916 年，一种早已存在的作战思路，开始成为各国战略的重点：</p>
<p><strong>如果无法迅速突破敌人的防线，那就让敌人失去继续守住防线的能力。</strong></p>
<p>作战的重心，开始从“突破”转向“消耗”。</p>
<hr />
<section><h1>从突破战到消耗战<a href="#从突破战到消耗战"><span>#</span></a></h1><p>单从字面上看，“消耗战”似乎就是把士兵一批批送上战场，直到一方再也无人可用。</p><p>但一支军队能否继续作战，不只取决于还有多少士兵，也取决于士兵的训练水平，以及武器、弹药和补给的供应情况。而要维持这一切，又离不开工厂、铁路、煤炭、钢铁、农业、财政，以及政府的组织能力。</p><p>前线阵地看起来只有几公里宽，背后却连接着整个国家机器。</p><section><h2>“损失数量”与“承受能力”<a href="#损失数量与承受能力"><span>#</span></a></h2><p>比如，交战双方各损失十万人，受到的影响却未必相同。一方可能有充足的后备兵员，能够持续培养军官，工业也有能力补足装备，因而可以在几个月内补齐部队；另一方虽然还能征到人，却已经缺少带领新兵的军官和士官，甚至连步枪也配不齐。</p><p>同样是损失十万人，其中有多少人阵亡、负伤、被俘或失踪，对军队的影响也大不一样。部分伤员可以返回部队，俘虏却通常无法在短时间内归队。如果损失了大量经验丰富的基层骨干，即使补充了同样数量的新兵，也往往难以恢复部队原有的战斗力。</p><p>因此，消耗战真正要比较的是：<strong>能不能让敌人的损失速度超过其补充、恢复和重新组织的速度，同时确保己方仍能继续作战。</strong></p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/15inchHowitzerShellEnglebelmerWoodSeptember1916--ff-dca04678e128.7w7m6a0axm.webp" alt="1916 年 9 月，索姆河战场上借助轨道搬运 15 英寸榴弹炮炮弹的士兵" /><figcaption>1916 年 9 月，索姆河战场上借助轨道搬运 15 英寸榴弹炮炮弹的士兵</figcaption></figure><p></p><p>在后来的索姆河战场上，一枚重型榴弹炮的炮弹，就需要士兵借助轨道来搬运。把炮弹送到炮口，只是整个补给过程的最后一步；在此之前，还要经过钢铁生产、炸药制造、弹药装填、仓储和长途运输。</p><div><div><div></div><div>Note</div></div><div><p>本文所列的“伤亡”与“损失”数字，不等于阵亡人数。各项统计纳入的负伤、失踪与被俘人数，以及统计时段，都可能有所不同。尤其是索姆河和东线攻势的总损失估计，不能仅凭这些数字，就精确比较各国承受战争的能力。</p></div></div></section><section><h2>土地仍然重要，但土地不意味着胜利<a href="#土地仍然重要但土地不意味着胜利"><span>#</span></a></h2><p>消耗战也不意味着“不再争夺土地”。一处高地有利于炮兵观察，一座村庄可以成为防御支点，一处铁路枢纽则关系到增援能否及时到达。夺取这些地方，既可能让己方的下一次进攻更容易，也可能迫使敌人在防守和反攻时付出更大代价。</p><p>夺取阵地可以增加敌军反攻时的损失，消耗敌军也可能为之后的突破创造条件。</p><p>1916 年的许多将领，仍然期待突破防线，恢复机动作战。变化在于，他们越来越愿意接受在突破之前先打一场可能持续数月的战斗。</p><p>不过，这套思路面临一个危险的难题。<strong>双方对自己的损失掌握得比较清楚，却很难判断敌人还能支撑多久。</strong></p><p>战俘的口述、空中侦察、情报报告，以及敌军抵抗的强度，都可以提供线索。但这些信息不足以让指挥官随时掌握对方的物资储备、预备队规模，以及政府继续战争的决心。</p><p>于是，“敌人快要被耗尽了”既可能是一次判断，也可能只是一厢情愿。</p><hr /></section></section>
<section><h1>法尔肯海因的设想<a href="#法尔肯海因的设想"><span>#</span></a></h1><p>1915 年东线的大胜，并没有让德国摆脱两线作战的窘境。</p><p>俄国虽然失去了大片领土，却仍有军队能够继续作战。而在西线，英法军队的规模还在扩大，弹药供应也在增加；在海上，英国继续凭借舰队优势对德国实施封锁。</p><p>对德国而言，拖延下去只会越来越不利。</p><p>但继续向俄国内地推进，意味着补给线更长，维持占领也更费力；若想从海上直接击败英国，德国舰队的实力又不够，而扩大潜艇战还可能引发外交危机。</p><p>德国总参谋长 <strong>埃里希·冯·法尔肯海因（Erich von Falkenhayn）</strong> 因而把希望重新放在对法国的进攻。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Erich-von-Falkenhayn%2C-1861-1922-LCCN2002714158--ff-0119a7aaba52.7lksd4l2q2.webp" alt="德国总参谋长埃里希·冯·法尔肯海因" /><figcaption>德国总参谋长埃里希·冯·法尔肯海因</figcaption></figure><p></p><p>法尔肯海因在马恩河战役后接替毛奇。此时，德国速胜的希望已经落空。他需要解决的，是接下来怎样打，才能迫使敌方接受谈判。</p><p>在他看来，法国人口较少，又已经承受了西线长期作战的沉重压力。如果法国军队无力继续支撑，英国依靠大陆盟友作战的基础，也会随之动摇。</p><section><h2>寻找战线的突出位置<a href="#寻找战线的突出位置"><span>#</span></a></h2><p>凡尔登位于默兹河两岸，是法国东北部的重要要塞地区。1914 年战线稳定以后，凡尔登成了一个朝德军方向伸出的突出部。这就意味着德军可以利用周边铁路运输兵力和物资，形成包围态势，依托已有阵地准备进攻，并在选定地段集中大量重炮，同时威胁法军在这一地区的交通线路。</p><p>这并不意味着凡尔登已经被完全包围。但从德军角度看，攻击这里，似乎有机会在相对有限的战场上形成局部优势。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Verdun-and-Vincinity---Map--ff-0bc7d1f06cea.2dphq4s2td.webp" alt="1916 年凡尔登、默兹河两岸与周边要塞战区示意图" /><figcaption>1916 年凡尔登、默兹河两岸与周边要塞战区示意图</figcaption></figure><p></p><p>凡尔登既是法国边境防御体系的一部分，也承载着此前战争留下的历史记忆，可以说是法国的一处国家象征。对于德国的威胁，法国自然不会坐视不理。</p><p>德军进攻越猛烈，法国就越需要证明自己能够守住凡尔登；法国越强调坚守，政府与军队就越难接受放弃这里引发的政治后果。</p><p><strong>随着战斗持续，凡尔登的军事价值与象征意义，开始相互强化。</strong></p></section><section><h2>“让法国流尽鲜血”<a href="#让法国流尽鲜血"><span>#</span></a></h2><p>关于凡尔登战役，最著名的一种解释是：法尔肯海因准备利用法国不肯放弃这里的心理，诱使法军不断投入军队，再用重炮造成大量杀伤，逐步“让法国流尽鲜血”。</p><p>这种解释可以追溯到他战后公布的所谓“圣诞备忘录”。按照这份文本的说法，法国无论是为守住目标付出惨重代价，还是放弃目标、承受士气上的打击，都可能因此走向失败。</p><p>然而，这份据称写于 1915 年末的备忘录，至今未发现可供核实的“原件”。</p><p>因此，历史学界一直在讨论：法尔肯海因是否从一开始就把大量杀伤法军作为主要目标，还是在夺取凡尔登的希望受挫后，才用消耗战略来解释自己的行动？</p><p>法尔肯海因确实希望削弱法国继续战争的能力，但夺取重要阵地、造成政治冲击与消耗法军，很可能都是这套构想的一部分。</p><p>无论最初打算如何，德军都必须先向前推进，并占领足以威胁凡尔登的阵地，才能迫使法国按照预期投入更多部队。</p><hr /></section></section>
<section><h1>凡尔登战役：名副其实的绞肉机<a href="#凡尔登战役名副其实的绞肉机"><span>#</span></a></h1><p>1916 年 2 月 21 日，德军开始猛烈炮击凡尔登地区。</p><p>这一天，炮击从上午 7 时 15 分开始。德军希望先用密集火力摧毁法军阵地，再让步兵穿过被轰开的缺口。但炮弹要破坏的，不只是最前面的一道战壕。树林、交通壕、观察点、炮兵阵地和通向后方的道路，都在轰击范围内。</p><p>树木被炸断，壕沟被掀翻，电话线被切断。一支部队即使没有在第一轮轰击中被消灭，也可能突然失去与两翼和上级的联系，不知道敌人已经推进到哪里，更不知道增援什么时候能到。</p><p>在 <strong>科尔树林（Bois des Caures）</strong>，埃米尔·德里昂（Émile Driant）指挥的法国轻步兵仍然抵抗。炮击没有把守军全部摧毁，德军步兵进入林地后，还要清除一个个残存的抵抗点。</p><p>2 月 22 日，德里昂在撤退中阵亡，树林最终失守。</p><p>持续轰击之后，步兵向默兹河右岸推进。法军前沿阵地受到严重破坏，一些村庄和阵地相继失守。德军最初几天的攻势取得了明显进展。</p><p>2 月 25 日，<strong>杜奥蒙要塞（Fort Douaumont）</strong> 落入德军手中。</p><p>这一座占据重要位置的大型要塞，却被德军轻松攻克了下来。</p><p>此前，法国为了防止部分战区的火力空虚，将部分要塞的火炮和守备部队调往其他战区。杜奥蒙要塞因此守备薄弱，一支很小的德军部队趁乱进入要塞，以极少的兵力夺取了这处重要据点。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Douaumontfort--ff-4757a464a789.13mkjta3gk.webp" alt="1916 年遭受炮火破坏的杜奥蒙要塞航空照片" /><figcaption>1916 年遭受炮火破坏的杜奥蒙要塞航空照片</figcaption></figure><p></p><p>深埋地下的混凝土工事能够为士兵提供掩蔽，要塞又能控制周边地形，仍是双方反复争夺的支点。</p><p>对法国而言，杜奥蒙要塞的失守让局势变得更加紧迫。德军已经不只是攻击外围阵地，而是在动摇凡尔登防御体系中最重要的部分。</p><section><h2>炮火连天的战场<a href="#炮火连天的战场"><span>#</span></a></h2><p>凡尔登后来被称为“绞肉机”，并不只是因为双方不断发动死亡冲锋。</p><p>对许多士兵来说，最漫长的时间，是留在阵地上等待下一轮炮击。</p><p>连续轰炸会把挖好的工事填平，摧毁道路、沟渠与村庄。有些部队只能利用相邻的弹坑组织防守，再趁炮火稍弱时修补掩体。</p><p>每一处弹坑里都可能趴着几名士兵，联络兵必须在炮火中寻找他们，军官也要想办法确认，哪些地段还在己方手中，哪些已经被敌人占领。</p><p>法国军官 <strong>夏尔·德尔韦（Charles Delvert）</strong> 在 5 月的日记里，记录过沃要塞附近的景象：树林只剩树桩，山坡几乎没有草，积水的弹坑连成一片；简陋的指挥所，不过是在弹坑上盖了几根木梁和泥土。</p><p>他的记录里，炮声日夜不停，无法正常睡眠。撤下来的部队与准备接替他们的士兵，要在夜色中冒着炮弹轰炸穿过这些山坡。</p><p>这就使“还能坚持多久”变成一个非常具体的问题。不只是子弹是否够用，还有多久没有睡过一个完整的觉；不只是阵地能否守住，还有饮水和食物能否送到。</p><p>后方仓库里有粮食，不等于前沿士兵就能吃上饭。运送补给的人员要背着食品、水壶和装备，在弹坑间寻找通路。“通路”如果真的容易通过，也就意味着容易成为敌方炮兵预先瞄准的目标。</p><p>伤员的处境更加艰难。从弹坑到救护站的路，同样处在炮火下。能够在负伤后自行后撤，与负伤后只能留在原地等待担架，是两种完全不同的命运。搬运伤员的人，也会在途中成为伤员。</p><p><strong>消耗并不只发生在进攻的那几分钟里。守住阵地、修补工事、运送补给、抬走伤员，都在继续消耗部队的体力和意志。</strong></p></section><section><h2>贝当与“神圣之路”<a href="#贝当与神圣之路"><span>#</span></a></h2><p>危机中，法国将防守凡尔登的任务交给 <strong>菲利普·贝当（Philippe Pétain）</strong>。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Photo-of-Philippe-P%C3%A9tain--ff-ae430be3f13f.2a5vsez037.webp" alt="不晚于 1916 年拍摄的菲利普·贝当肖像" /><figcaption>不晚于 1916 年拍摄的菲利普·贝当肖像</figcaption></figure><p></p><p>贝当不仅重视火力准备，也关注士兵能够承受多大的压力。他不认为，仅凭进攻精神或一道坚守命令，就能让部队在重炮轰击之下长期维持。</p><p>在凡尔登，首先要能够确保前线持续得到兵员和物资补充。从巴勒迪克通往凡尔登的道路，后来被称为 <strong>“神圣之路”（Voie sacrée）</strong>。卡车沿着这条交通干线运送兵员、弹药和物资，伤员则向后方撤离。</p><p>车辆要统一调度，损坏的路面要不断铺石修补，故障车辆也必须及时清走，以免堵塞道路。为了让车队持续运转，道路维护和运输安排都要随时跟上前线的需要。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/At-the-Battle-of-Verdun---French-reserves-and-ammunition-are-brought-in-by-motorcades--ff-e6d2df6d1d31.8ae1x58lpa.webp" alt="1916 年描绘法军车队向凡尔登运送增援与弹药的插画" /><figcaption>1916 年描绘法军车队向凡尔登运送增援与弹药的插画</figcaption></figure><p></p><p>与此同时，当地的窄轨铁路，以及后来部分恢复通车的其他铁路，也承担了补给任务。</p><p>部队轮换也是其中相当重要的一部分。法军把许多师团先后送入凡尔登，再将遭受损失、疲惫不堪的部队撤下来补充整顿，这可以在相当程度上缓解同一批士兵的作战压力，避免部队被消耗殆尽。</p><p>轮换使法军在前沿部队遭受重创以后，仍能调入新部队恢复防线。</p></section><section><h2>深陷泥沼的德军<a href="#深陷泥沼的德军"><span>#</span></a></h2><p>最初几天推进顺利，但随后德军也遇到了难题。</p><p>只攻击默兹河右岸，推进部队会受到左岸法军炮兵的威胁。德军于是把攻势扩大到左岸，继续争夺 <strong>“死人山”（Mort-Homme）</strong> 与 <strong>304 高地</strong>。</p><div><div><div></div><div>Note</div></div><div><p><strong>“304 高地”的数字</strong></p><p>“304”取自地形图上以米为单位标注的海拔高度。战史中的“某某高地”常用这种方式辨认地图上的地形点。</p></div></div><p>这些高地关系到战场观察、炮兵阵地和附近阵地的安全，它们后来也成为凡尔登战役的象征。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/West-bank-of-the-Meuse%2C-Verdun%2C-1916--ff-a117a4ae2590.1ht0aoiedn.webp" alt="1916 年凡尔登默兹河左岸战区示意图，1921 年刊于《泰晤士报战争史》" /><figcaption>1916 年凡尔登默兹河左岸战区示意图，1921 年刊于《泰晤士报战争史》</figcaption></figure><p></p><p>死人山与 304 高地控制着周围的观察和射击位置。德军每夺取一处阵地，还须压制邻近高地上的侧射火力，留下兵力防备反攻，并修复遭到炮击的补给道路，才能维持战果。</p><p>原本希望控制规模的一场进攻，就这样需要不断投入更多兵力和物资。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/French-155L-sur-affut-truck-in-action-near-Verdun%2C-March-1916-%283%29--ff-e7ac3dc85a51.3gp710nwng.webp" alt="1916 年 3 月 18 日，凡尔登附近作战中的法国 155 毫米长身管火炮" /><figcaption>1916 年 3 月 18 日，凡尔登附近作战中的法国 155 毫米长身管火炮</figcaption></figure><p></p><p>法军也拥有大炮。德军步兵同样要穿过被炮火破坏的地面，守住缺乏完整掩蔽的阵地，再承受法国的炮击与反攻。</p><p>原本设想的是让法军在德军炮火下逐渐耗尽，实际打起来，双方都在进攻、坚守与反攻中不断付出惨重代价。</p></section><section><h2>摇摇欲坠的要塞<a href="#摇摇欲坠的要塞"><span>#</span></a></h2><p>杜奥蒙要塞被一小队德军迅速夺取，沃要塞却经历了另一种战斗。</p><p>6 月初，德军逼近要塞，控制了它的顶部，并从防御设施向内部渗透。法国指挥官 <strong>西尔万·雷纳尔（Sylvain Raynal）</strong> 和守军退入内部，在狭窄的通道里设置障碍，继续抵抗。</p><p>这时，战场已经不再是能让一整个营展开的山坡。一段走廊、一道转角、一处通向其他房间的入口，都可能成为争夺目标。守军用沙石封住通道，双方在近距离交火、投掷手榴弹；德军还使用喷火器与毒气，试图迫使法军退出掩蔽。</p><p>混凝土保护了守军，也把他们困在一个越来越难以维持的空间里。</p><p>烟雾、爆炸、火焰、毒气，伤员和病患挤在一起。外面的炮声仍然在响，但里面的人已经很难分辨，这些炮火究竟是友军的救援，还是又一轮打在要塞上的轰击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/M-134-8-le-fort-de-Vaux-mars-1916--ff-3d4b2a931a72.9ddr814fmy.webp" alt="1916 年 3 月，沃要塞内部的法军士兵" /><figcaption>1916 年 3 月，沃要塞内部的法军士兵</figcaption></figure><p></p><p>电话线路遭到破坏后，守军不得不依赖信鸽、灯光信号和冒险穿越火线的联络人员向后方求援。</p><p>6 月 4 日，雷纳尔放出了最后一只信鸽 <strong>“勇敢号”（Vaillant）</strong>。求援信息说明，守军仍在抵抗，但正受到猛烈攻击，急需解围，也急需恢复与外界的联系。</p><p>信鸽送出了消息，却不能把援军和饮水带回来。外围的法军曾尝试接近要塞，却未能打通救援通路。内部的饮水供应则越来越紧张，每一名守军士兵都逼近了极限。</p><p>到了 6 月 7 日早晨，雷纳尔最终率守军投降。</p></section><section><h2>艰难的最后几公里<a href="#艰难的最后几公里"><span>#</span></a></h2><p>沃要塞失守以后，德军距离凡尔登又近了一步。</p><p>但要真正接近城市，仍要越过 <strong>弗勒里村（Fleury-devant-Douaumont）</strong> 及附近高地，并压倒 <strong>苏维尔要塞（Fort de Souville）</strong> 周围的防御。</p><p>这几处地名，在地图上并不遥远，却连着一道又一道必须争夺的阵地。</p><p>6 月 23 日，德军发动猛烈进攻，夺取了弗勒里大部，并在附近取得进展。法军防线一度面临严重危机。</p><p>此时，“凡尔登快要失守了”并不是毫无根据的担忧。德军确实逼近了关键地段，法国也确实需要继续投入力量，才能把前线稳住。</p><p>但德军的进攻却也不是如此轻松。进攻部队同样被炮火打散，在泥泞的地面上前进。最先到达目标附近的人，可能已经与后续部队脱节；前面取得进展，后方火炮、弹药和兵力却未必能够及时跟上。</p><p>7 月 11 至 12 日，德军再次尝试向苏维尔方向推进。少量士兵一度抵达要塞顶部，最终仍被法军反击赶下。</p><p>凡尔登没有陷落。</p><p>这几天也与索姆河攻势紧紧相连。英法已在 7 月 1 日发动新的攻势，德国无法只看着苏维尔附近还差几公里，就不断把预备队送进同一片战场。</p><p>弗勒里的战斗此后仍然持续，到 8 月，法军才最终夺回村庄。几经易手的村庄，已经几乎无法从地面上辨认出原来的样子。</p><p>或者说，双方争夺的是一个“阵地”，而不是一座“村庄”。</p><p>到了这时，德国面对的问题已经不只是“下一次攻击能不能成功”。</p><p>如果此时停止进攻，便无法向国内交代此前付出的牺牲，以及这场战役未能实现的期待。如果继续，又凭什么认为下一轮战斗会比上一轮更有利？</p><p>当然，不能把德军的所有决策都归结为不甘心让此前的牺牲白费。法军的行动与前线阵地的安全，仍然影响着德军的选择。</p><p>可以确认的是，当一场战役关系到战略目标和国家声望时，究竟打到什么程度才可以停下来，往往会变得越来越难以判断。</p></section><section><h2>秋季反攻<a href="#秋季反攻"><span>#</span></a></h2><p>德军的进攻逐渐受限，凡尔登的局势开始出现转机。</p><p>到了秋天，法军开始把此前积累的兵力、火力和协同经验用于反攻。贝当已经升任更高一级的指挥职务，凡尔登的具体作战由尼维尔等人组织。</p><p>这一次，重炮先对德军阵地和要塞进行集中轰击，步兵再跟随向前移动的炮火推进。</p><p>10 月 24 日，法军收复杜奥蒙要塞。11 月初，德军撤出沃要塞，法军重新进入。12 月的进攻，又进一步改善了法军在凡尔登的局势。</p><p>年初失去的两座著名要塞，终于重新回到法国手中。</p><p>但要塞回来了，死者却无法复生，被摧毁的部队也不能只靠重新占领一处建筑就恢复。</p><p>法军能够依靠轮换稳住防线，也使大量部队先后见证了凡尔登的残酷。从各地来到这里的士兵，再把这场战役的记忆带回不同城镇和家庭。</p><p>因此，凡尔登后来成为法国对一战最深刻的记忆之一，既因为它守住了一个重要地点，也因为太多人亲自感受过守住这里的代价。</p><p>而要理解德国为什么逐渐难以继续集中力量打凡尔登，还需要回到 1916 年的其他战场。</p><p>法国的盟友会采取行动，而德国自己的盟友也有各自的计划。</p><p>1916 年春夏之交，凡尔登还在激战，北海则爆发了另一场希望改变力量平衡的会战。</p><hr /></section></section>
<section><h1>日德兰海战：击沉了军舰不代表能解除封锁<a href="#日德兰海战击沉了军舰不代表能解除封锁"><span>#</span></a></h1><p>陆上作战需要源源不断的弹药与粮食。海上作战，则关系到各国能否从海外获得这些物资。</p><p>在 1915 年，英国凭借海军优势对德军的海上通道实施封锁，德国则试图使用潜艇进行反击。</p><p>但潜艇并不是德国唯一的选择。</p><p>德国还拥有耗费巨资建立的 <strong>公海舰队（High Seas Fleet）</strong>。面对实力更强的英国 <strong>大舰队（Grand Fleet）</strong>，德军虽然很难直接投入全部主力决战，却也不甘心一直龟缩在港内，把海上主动权完全交给英国。</p><p>1916 年，公海舰队司令 <strong>赖因哈德·舍尔（Reinhard Scheer）</strong> 希望主动出击，引出一部分英国舰艇，趁英国主力尚未全部赶到，制造局部优势将其击败。</p><p>这种设想与凡尔登有相似之处：</p><p><strong>既然整体实力不如对手，就设法在局部集中优势，迫使对方付出更大代价。</strong></p><p>英国大舰队司令 <strong>约翰·杰利科（John Jellicoe）</strong> 则需要兼顾两件事：寻找打击德国舰队的机会，同时保存英国主力，因为英国的海上控制依赖这支舰队。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/HMS-Iron-Duke-%281912%29--ff-aeae2bc26717.3uvmrvw7j3.webp" alt="约 1914—1915 年的英国战列舰“铁公爵”号，后来担任杰利科的旗舰" /><figcaption>约 1914—1915 年的英国战列舰“铁公爵”号，后来担任杰利科的旗舰</figcaption></figure><p></p><p>对德国而言，没有取得决定性胜利，舰队仍可能撤回基地，等待下一次行动；对英国而言，如果主力舰队在一场冒险中遭到灾难性损失，不仅会使封锁线被迫解除，也会影响海上贸易和英国本土安全。</p><section><h2>互有伤亡的遭遇战<a href="#互有伤亡的遭遇战"><span>#</span></a></h2><p>1916 年 5 月 31 日，双方在丹麦日德兰半岛以西的北海相遇。</p><p>最先交战的，是英国大卫·贝蒂和德国弗朗茨·希佩尔各自指挥的战列巡洋舰部队。</p><p>战列巡洋舰拥有重型火炮，航速也较高，承担侦察、追击和诱敌等任务。它们能够迅速接近敌军，但能否承受敌方重炮攻击，还要看各型军舰的防护设计、弹药处理方式，以及实际如何作战。</p><p>贝蒂的部队向南追逐德国舰队，随后发现舍尔的主力也在附近，便转向北方，把德军引向杰利科正在赶来的大舰队。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Map-of-the-Battle-of-Jutland%2C-1916--ff-776226cf2507.9o0l16jnse.svg" alt="1916 年日德兰海战中英德舰队主要航迹示意图" /><figcaption>1916 年日德兰海战中英德舰队主要航迹示意图</figcaption></figure><p></p><p>德军想引出一部分英国舰艇，英军也想把德国舰队引到自己的主力面前。</p><p>双方都想抓住战机，却都无法完全掌握对手的位置。</p><p>硝烟和雾气遮挡视线，炮火扰乱判断，通信也并不畅通，实际交战远比地图上标出的航迹复杂。旗语与无线电报告可能延误，仅凭舰桥上的观察，很难掌握几十公里外其他舰艇的情况。</p><p>交战中，英国多艘战列巡洋舰发生灾难性爆炸。</p><p>“玛丽女王”号等军舰的爆炸，常被后世归因于英国舰艇装甲不足。但装甲只是部分原因。炮弹命中后是否引燃发射药，火焰是否沿弹药通道蔓延到舰体内部，也取决于舰上的弹药处理方式和防火措施。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/HMSLion-besideHMSQueenMaryblowingup1916--ff-9c0798d80558.9gxd5qxich.webp" alt="1916 年 5 月 31 日，“狮”号受炮击与“玛丽女王”号爆炸的两张照片合成图" /><figcaption>1916 年 5 月 31 日，“狮”号受炮击与“玛丽女王”号爆炸的两张照片合成图</figcaption></figure><p></p><p>傍晚，杰利科将主力舰队展开为战斗队形，德国舰队一度陷入不利位置。</p><p>舍尔通过转向、鱼雷攻击与后卫行动脱离了危险。但战斗仍然持续，双方驱逐舰与其他舰艇在难以辨认敌我的环境中交火。</p><p>德国主力最终得以返回基地，英国没有取得一场像特拉法加那样摧毁敌方主力的胜利，德国也没有把英国大舰队打到无法维持海上优势。</p></section><section><h2>战术结果和战略结果<a href="#战术结果和战略结果"><span>#</span></a></h2><p>按击沉舰艇和人员损失计算，德国有理由宣称取得了战术上的优势。</p><p>英国损失 14 艘军舰，德国损失 11 艘；英国死亡人数超过六千，德国约为两千五百。</p><p>但这些数字还没有回答另一个问题：战役以后，谁还能控制海上交通，谁还能继续实施封锁？</p><p>答案仍然是英国。</p>

<table><thead><tr><th>比较层面</th><th>日德兰的结果</th><th>对战争的实际影响</th></tr></thead><tbody><tr><td>舰艇与人员损失</td><td>英国损失更重</td><td>德国可以宣称战术战果</td></tr><tr><td>主力舰队的保存</td><td>双方主力均未被摧毁</td><td>没有出现海军决战意义上的彻底胜负</td></tr><tr><td>北海力量对比</td><td>英国继续保持优势</td><td>英国能够维持封锁</td></tr><tr><td>德国的战略目标</td><td>没能打破英国海上控制</td><td>封锁压力仍在，潜艇战更受重视</td></tr></tbody></table><p>公海舰队此后仍可以出海行动，也仍能牵制英国兵力。它的军事价值并没有因这场海战而消失。</p><p>日德兰的炮声刚刚停息，欧洲东部又出现了一场同盟国无法忽视的危机。</p><hr /></section></section>
<section><h1>勃鲁西洛夫攻势：俄军也想快速结束战争<a href="#勃鲁西洛夫攻势俄军也想快速结束战争"><span>#</span></a></h1><p>1915 年的俄军大撤退，很容易给人一种俄国已经被打垮，接下来只能勉强守住战线的感觉。</p><p>但到了 1916 年，情况发生了变化。</p><p>俄国的军工生产和武器供应有所改善，部队也经过了重新整顿。此前的严重问题尚未全部解决，但俄军已经开始恢复进攻能力。</p><p>协约国也开始加强各条战线之间的协调。</p><p>在 1915 年末的尚蒂伊会议及相关协商中，协约国提出，下一年要在几条战线发动相互配合的攻势。目的就是让德国与奥匈多线受压，无法及时把部队从相对平静的战线调往局势危急的战线。</p><p>这个想法看似简单，执行起来却很困难。各国军队的装备和指挥体系不同，各条战线的地形、天气也不一样。协约国又没有统一总司令部能够随时调动所有盟国的军队。</p><p>法国在凡尔登遭到攻击以后，俄国曾于 3 月在纳罗奇湖地区发动攻势，却损失惨重，未能取得预期成果。泥泞的地面、步炮协同不佳，以及德军的抵抗，都使这次支援法国的尝试迅速受挫。</p><p>到了 5 月，奥匈又向意大利特伦蒂诺方向进攻，意大利开始寻求盟友支援。</p><p>俄国就在这样的多方压力下，着手准备并发动了新一轮攻势。</p><section><h2>勃鲁西洛夫的改变<a href="#勃鲁西洛夫的改变"><span>#</span></a></h2><p>俄军西南方面军司令 <strong>阿列克谢·勃鲁西洛夫（Aleksei Brusilov）</strong> 认为，过去一些攻势准备时间很长，主攻地段又过于明显，往往给了敌人集中预备队的机会。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D0%BB-%D0%90.%D0%90.-%D0%91%D1%80%D1%83%D1%81%D0%B8%D0%BB%D0%BE%D0%B2%2C-1916--ff-92721ad39708.77ecm9crx3.webp" alt="1916 年俄国西南方面军司令阿列克谢·勃鲁西洛夫" /><figcaption>1916 年俄国西南方面军司令阿列克谢·勃鲁西洛夫</figcaption></figure><p></p><p>他在此前作战经验的基础上，对进攻准备作出了更细致的安排。</p><p>部队通过提前侦察摸清敌方阵地；工兵将出发阵地和交通壕向前延伸，尽量缩短步兵穿越无人区、暴露在敌军火力下的时间；炮兵则针对铁丝网、火力点和通信设施进行轰击，并配合步兵的推进。</p><p>更重要的是，西南方面军的多个集团军同时准备攻击。俄军并没有在几百公里的战线上平均分配兵力，各部队仍然选定了重点突破地段。但几处攻势同时展开，奥匈军队就更难判断，该把有限的预备队集中到哪里。</p><p>这样一来，就不必把大量士兵集中在一处敌人早有防备的突破口，反复发动冲锋。</p><p>1916 年 6 月 4 日，俄军发起攻势。</p><p>在卢茨克等地，奥匈防线遭到突破，大批部队被俘或后撤。俄军在沃里尼亚、加利西亚与布科维纳方向取得进展。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/EasternFront1916b--ff-c4a4021ccf5e.73uqojjp55.webp" alt="1916 年勃鲁西洛夫攻势与东线南部战区示意图" /><figcaption>1916 年勃鲁西洛夫攻势与东线南部战区示意图</figcaption></figure><p></p><p>奥匈并非完全没有防御准备。奥匈军队依托着战壕和坚固阵地，却过于相信这些工事能够挡住进攻；部分精锐部队与装备又已调往意大利方向。面对俄军的多点进攻和较为紧密的步炮协同，奥匈指挥官来不及有效调动预备队应对。</p></section><section><h2>关键突破仍不足以决定胜利<a href="#关键突破仍不足以决定胜利"><span>#</span></a></h2><p>奥匈遭受的重创，很快影响了其他战线。</p><p>奥匈需要从意大利方向调兵，德国也必须向东线派出增援。这就限制了德国继续向凡尔登投入兵力和物资的余地，也给了意大利喘息的机会。</p><p>但俄军各方面军之间的配合，却没有那么顺利。北面的其他方面军未能及时发动有力攻势以配合勃鲁西洛夫。原先计划由各方面军共同进攻、分散敌军兵力，到后来，主要进攻任务却落在了勃鲁西洛夫身上。</p><p>德国增援抵达后，同盟国开始稳住防线。俄军后续进攻又越来越集中在科韦利一带。这里虽然是重要的铁路枢纽，但河流、沼泽和不断加强的敌军防御，都增加了进攻的难度。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/From-the-fighting-of-the-Russian-offensive-in-the-Buchach-area%2C-1916--ff-2a7cb8fbe226.4cloggxl3s.webp" alt="以 1916 年布恰奇地区俄军攻势为题材的战争插画" /><figcaption>以 1916 年布恰奇地区俄军攻势为题材的战争插画</figcaption></figure><p></p><p>随着攻势集中到科韦利，同盟国军队更容易判断俄军的主攻方向，并作出防备，俄军逐渐丧失了最初多点进攻带来的优势。</p><p>俄军要继续扩大战果，还需要补充炮弹、调动预备队，并修复交通线。</p><p>所以，战役出现了熟悉的转变：</p><p><strong>防线已经突破，但后续的补给、增援与指挥配合，却没能及时跟上。</strong></p><p>奥匈军队受到严重削弱，此后更加依赖德国的援助与指挥协调；俄军虽然扭转了此前的颓势，也为这场胜利付出了惨重代价。</p><p>8 月，罗马尼亚加入协约国。俄军的成功增强了罗马尼亚对战局的信心，而领土诉求与外交谈判，也推动了它的参战决定。</p><p>然而，罗马尼亚随后遭到同盟国反击，到年底，首都布加勒斯特被攻陷。协约国虽然得到一个新盟友，也很快需要承担支援新战线的负担。</p><div><div><div></div><div>Note</div></div><div><p>俄国当时使用儒略历。本文把战役日期统一写为公历，因此勃鲁西洛夫攻势的 6 月 4 日，与一些俄文资料中的 5 月 22 日，指的是同一天。</p></div></div><p>东线战果证明了俄军仍然能够改变局势，但德国与奥匈仍然具有继续战争的能力。</p><p>要让同盟国真正难以兼顾各条战线，还需要在西线发动另一场大规模攻势。</p><hr /></section></section>
<section><h1>索姆河战役：流干了血的战场<a href="#索姆河战役流干了血的战场"><span>#</span></a></h1><p>索姆河攻势，并不是法国在凡尔登遇险后才临时提出的计划。</p><p>它原本就是协约国 1916 年联合行动的一部分。英法准备在法国北部两军战线交界处展开攻势，以便相互配合，共同打击德军。</p><p>只是凡尔登改变了计划的规模、兵力分配与紧迫程度。法国原本准备在索姆河投入更多兵力，却被迫将大量部队调往默兹河。英国因此从支援法国的角色，逐渐转为承担主要进攻任务的一方。</p><p>对英国远征军总司令 <strong>道格拉斯·黑格（Douglas Haig）</strong> 而言，这既是机会，也是难题。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Douglas-Haig--ff-3d0cd66405d5.86ufzffj0o.webp" alt="1917 年拍摄的英国远征军总司令道格拉斯·黑格肖像" /><figcaption>1917 年拍摄的英国远征军总司令道格拉斯·黑格肖像</figcaption></figure><p></p><p>1914 年，英国派往欧洲大陆的还是一支规模有限的职业远征军。此时，这支军队已大幅扩充，包含大量新兵。1914—1915 年招募的志愿兵开始参加大规模作战，其中不少人来自同一城镇、行业或社群，组成了后来常被称为“伙伴营”的部队。</p><p>这样的招募方式有助于吸引志愿兵，也能增强部队的凝聚力。但一个营遭受重创时，伤亡就可能集中在几个社区，让许多相识的家庭同时失去亲人。</p><p>而兵员数量迅速增加，并不意味着经验丰富的军官、士官和参谋也能及时补足，更不意味着各兵种的协同能力会同步提高。</p><p>黑格知道军队还需要准备。但如果继续等待，凡尔登的法军就要持续承受德军的进攻压力。</p><p>英法最终定于 7 月 1 日，在索姆河发动大规模步兵进攻。</p><p>英军的进攻重点位于索姆河以北，法军则在相邻地段及河流南侧展开攻击。从蒂耶普瓦尔、拉布瓦塞勒到弗莱尔、佩罗讷，这片战场上的村庄、高地与道路，共同构成了德军防御的骨架。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Battle-of-the-Somme-1916-map--ff-da3c7d8d7f8f.2rvxh00dlr.webp" alt="1916 年索姆河战役主要攻击阶段与战线变化示意图" /><figcaption>1916 年索姆河战役主要攻击阶段与战线变化示意图</figcaption></figure><p></p><section><h2>互有胜负的拉锯<a href="#互有胜负的拉锯"><span>#</span></a></h2><p>6 月 24 日，英军开始大规模炮兵准备。</p><p>在接下来的七天里，大量炮弹落向德军阵地。指挥官希望切断铁丝网、破坏工事、压制火力，提前为步兵清理进攻路线。</p><p>与 1915 年相比，英军已经拥有更多火炮与弹药，但这并不意味着炮击就能达到预期效果。</p><p>德军在这里经营阵地已久，一些掩体深挖在地下的白垩岩层中。榴霰弹可以杀伤暴露在外的步兵，却难以摧毁深处的掩体，后者需要足够的重型高爆弹。炮弹质量、引信性能、炮兵观察，以及火力如何分配，也都影响着炮击效果。</p><p>与此同时，炮兵还要在较宽的战线上，对多种目标进行轰击。进攻范围越大，每个地段分到的有效火力就可能越不足。</p><p>这使得炮击未能按照预期完全摧毁德军阵地的铁丝网等工事，也没能使德军守军大规模减员。</p><p>这里还有一个计划层面的矛盾。</p><p>负责主要进攻的第四集团军司令亨利·罗林森，更倾向于分阶段夺取有限目标；黑格则仍希望在突破防线后，直接向敌军纵深推进。</p><p>究竟是稳步夺取有限目标，还是争取大规模突破，作战安排中始终没能充分协调这两种思路。</p><p>于是，尽管炮弹打了很多，计划也制定得很细，步兵面前的障碍和敌军火力，却并没有全部被清除。</p></section><section><h2>索姆河的首战<a href="#索姆河的首战"><span>#</span></a></h2><p>在北端的 <strong>贡梅库尔（Gommecourt）</strong>，英国军官 <strong>爱德华·利文（Edward G. D. Liveing）</strong> 留下了一份自己的参战记述。</p><p>进攻前一夜，他坐在一个汽油罐上，夹在士官和士兵之间，等着天亮。炮弹不断落到附近，睡眠被爆炸和呼叫担架员的声音打断。部队还没有离开出发阵地，就已经有人受伤。</p><p>早晨到来时，天气却显得很好。阳光渐渐暖起来，眼前仍然是夏天的草地和天空。</p><p>再过一会儿，士兵便要离开掩蔽，穿过眼前的草地，走向德军阵地。</p><p>而在博蒙阿梅尔附近，正在进行另一项准备。</p><p>英军工兵此前已经在地下掘进，把大量炸药放到德军据点下方。7 月 1 日上午 7 时 20 分，<strong>霍桑岭（Hawthorn Ridge）</strong> 下的装药爆炸，整片土石被掀向空中。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Hawthorn-Ridge-Redoubt-mine-%281-July-1916%29-1--ff-5d3faf496ef2.8vnpjg321n.webp" alt="1916 年 7 月 1 日上午 7 时 20 分，霍桑岭地下装药爆炸的瞬间" /><figcaption>1916 年 7 月 1 日上午 7 时 20 分，霍桑岭地下装药爆炸的瞬间</figcaption></figure><p></p><p>土石冲上天空的瞬间，被摄影师欧内斯特·布鲁克斯（Ernest Brooks）记录下来，成为索姆河最著名的影像之一。</p><p>但这并不意味着附近所有守军都已经清空。爆破只能改变一个地段，并不能压制整条战线的敌军火力。</p><p>这处爆破比步兵的冲锋提前了十分钟。等待攻击的英军看见了爆炸，附近的德军也知道，进攻已经迫近。</p><p>接下来，仍然要看双方谁能更快利用这个变化：进攻方能否及时赶到预定目标，防守方能否及时重新占据射击位置。</p><p>英军于 7 月 1 日上午 7 时 30 分发起了攻击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Tyneside-Irish-Brigade-advancing-1-July-1916--ff-fc6d4e757be3.mv8xe9mn.webp" alt="1916 年 7 月 1 日，向拉布瓦塞勒方向前进的泰恩赛德爱尔兰旅支援连" /><figcaption>1916 年 7 月 1 日，向拉布瓦塞勒方向前进的泰恩赛德爱尔兰旅支援连</figcaption></figure><p></p><p>当炮火弹幕打往更远处，幸存的德军便从掩体里出来，将机枪重新架到射击位置。</p><p>进攻部队穿越无人区，却被未完全损毁的铁丝网和德军重新组织起来的火力压制。前面的士兵倒下以后，后续部队又可能因通信中断、不清楚前方情况，继续向前推进。</p><p>各部队的出发位置、队形与行动方法有所不同，有些部队提前进入无人区，试图缩短炮火停止后的接近时间。</p><p>当天英国军队的损失通常统计为 <strong>57,470 人，其中 19,240 人阵亡</strong>。</p><p>这是英国陆军历史上伤亡最严重的一天。</p></section><section><h2>纽芬兰团的灾难<a href="#纽芬兰团的灾难"><span>#</span></a></h2><p>在 <strong>博蒙阿梅尔（Beaumont-Hamel）</strong>，纽芬兰团的遭遇，让首日的伤亡数字有了更具体的含义。</p><div><div><div></div><div>Note</div></div><div><p><strong>纽芬兰当时的身份</strong></p><p>1916 年的纽芬兰是英国自治领，直到 1949 年才加入加拿大。该团在博蒙阿梅尔隶属英军第 29 师。</p></div></div><p>约在上午 9 时 15 分，这支部队从圣约翰支援壕出发。前沿交通已经因先前攻击的伤亡而拥堵，他们不得不从较后的阵地向前走两百多米，才能到达己方前线。</p><p>敌方阵地还在更远处。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Newfoundland-soldiers-1916--ff-47c4d537dae1.7zr83ztdm8.webp" alt="1916 年 7 月 1 日进攻前，圣约翰支援壕中的纽芬兰士兵" /><figcaption>1916 年 7 月 1 日进攻前，圣约翰支援壕中的纽芬兰士兵</figcaption></figure><p></p><p>出发以后，他们迅速暴露在了德军火力下，在极短时间内遭受毁灭性损失。</p><p>大约八百人参加了当天早晨的进攻，次日点名时，只有 <strong>68 人</strong> 应点。</p><p>这个数字不等于其余人全部阵亡。无法应点的人里，还有负伤和失踪者。但对一个刚刚出发的作战单位来说，原有的队伍已经几乎无法继续作为完整部队行动。</p><p>这种灾难的消息，很快又会从战场传回部队征兵的地区。</p><p>纽芬兰的许多家庭，仅仅因为一次进攻而支离破碎。</p><p>然而，索姆河战场上各个地段的情况并不相同。</p><p>在英军进攻区域南部的一些地段，以及法军进攻区域，部队取得了明显进展。法军的炮兵配置、协同经验和进攻目标安排，与北部一些英军部队有所不同。</p><p>法军第六集团军吸取了 1915 年攻势的经验，对火力集中、攻击目标与步兵使用作出了调整。炮兵要先完成较明确的破坏任务，步兵再攻击能够得到有效支援的地段，而不是仅凭炮声猛烈，就认定整个防御体系已经崩溃。</p><p>但这样的进展，并不足以让整个联合攻势立刻向前展开。</p><p>法军若继续深入，仍然需要两翼的配合；火炮要向前移动，预备队要能赶上，新阵地也要得到补给。河流、桥梁与两军作战分界，又限制了兵力和装备的调动。</p><p>因此，索姆河的困难也在于：<strong>英法可以共同决定发动进攻，却仍然要在战斗中不断协调谁先前进、谁提供炮火、谁来守住两军之间的空隙。</strong></p><p>一个地段打得较好，还不足以让整个战场恢复机动。</p></section><section><h2>为什么不在第一天以后便停止进攻？<a href="#为什么不在第一天以后便停止进攻"><span>#</span></a></h2><p>从后世视角看，第一天的损失如此严重，停止进攻似乎是最明显的选择。</p><p>但黑格与法国指挥官还要考虑：此轮攻势是否足以解除德军对凡尔登的威胁。</p><p>如果协约国迅速停止攻势，德国可能重新调配预备队，让法国继续承受压力。英国与法国之间的合作，也会受到考验。</p><p>何况，在已经取得进展的地段，仍有继续扩大战果的机会。当时的指挥官，也无法预知整个夏天打下来，双方最终会付出多大的代价。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Cheshire-Regiment-trench-Somme-1916--ff-a98356270a2f.9o0l16jnr1.webp" alt="1916 年 7 月，索姆河奥维莱附近战壕中的英国柴郡团士兵" /><figcaption>1916 年 7 月，索姆河奥维莱附近战壕中的英国柴郡团士兵</figcaption></figure><p></p><p>此后的战斗，逐渐从寻求一次大规模突破，变成对村庄、树林与高地的反复争夺。</p><p>7 月 14 日，英军对德军第二道阵地的一次黎明突击取得较好效果。部队在夜间提前接近，炮兵准备更集中，攻击条件也更有利。</p><p>但即使夺取了前方阵地，英军想继续向纵深推进，仍会遇到通信不畅、预备队调动困难，以及德军增援阻击等问题。</p><p>波齐耶尔、德尔维尔森林、吉耶蒙等地，随后成为新的激战区域。澳大利亚、南非、加拿大、新西兰等部队，也先后投入这一阶段的作战。</p></section><section><h2>波齐耶尔战役：夺取阵地只是开始<a href="#波齐耶尔战役夺取阵地只是开始"><span>#</span></a></h2><p>村庄能提供掩蔽，树林能隐藏部队，高地则有利于观察周围阵地。控制了它们，下一轮进攻就可能更容易；失去它们，附近的防线又可能受到威胁。</p><p>于是，双方都很难在对方刚刚占领以后，就接受失去阵地的事实。</p><p><strong>波齐耶尔（Pozières）</strong> 位于重要高地上，澳大利亚部队从 7 月 23 日起投入这里的战斗。</p><p>夺取村庄后，部队仍然需要继续争夺附近阵地，并承受德军猛烈炮击。第一师撤下后，第二师和第四师先后接替；这些部队又陆续再次进入同一片战场。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Pozieres-plateau-28-August-1916--ff-0d48a3c3260a.13mkjta3hw.webp" alt="1916 年 8 月 28 日，从波齐耶尔北侧高地眺望德军第二道阵地" /><figcaption>1916 年 8 月 28 日，从波齐耶尔北侧高地眺望德军第二道阵地</figcaption></figure><p></p><p>经过约六周的战斗，澳大利亚军队在波齐耶尔及相关作战中损失约 <strong>23,000 人</strong>，包括阵亡、负伤和被俘。</p><p>这些损失在连续攻击、守住阵地、承受炮击和轮换中逐步累积。</p><p>索姆河战场上的部队来自英国本土，也来自帝国各地。这既显示了英国能够动员多大范围的兵员，也意味着战场上的伤亡会波及不同地区的社会与家庭。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/British-wounded-Bernafay-Wood-19-July-1916--ff-49e381a000c0.8okho0gwl7.webp" alt="1916 年 7 月 19 日，贝尔纳费森林附近走向救护站的伤员" /><figcaption>1916 年 7 月 19 日，贝尔纳费森林附近走向救护站的伤员</figcaption></figure><p></p><p>更多伤员需要担架。要把他们送到后方，必须先找到人，再穿过被打坏的战壕与道路，送到前沿救护站，之后才有机会继续转往更远处的医疗设施。</p><p>利文的参战记述中，受伤后的经历占了很大篇幅：从战场退出、在拥挤的救护地点等待、接受包扎，然后继续后送。离开战斗，也没有让他立刻摆脱恐惧。</p><p>对军队来说，医疗与撤离也直接影响承受能力。</p><p>伤员得到及时救治，部分人还有可能康复归队；但即使能够归队，也往往要经过很长时间。前线缺下来的位置，却需要尽快有人补上。</p><p>与此同时，战术也在变化。</p><p><strong>徐进弹幕（creeping barrage）</strong> 得到了更广泛的运用：炮火逐步向前移动，步兵尽量紧随炮火幕推进。同时，侦察敌方炮兵、据点与交通线，再用火力加以压制，也变得越来越重要。</p><p>这种步炮协同方法在索姆河之前已有尝试，此时又在实战中得到进一步改进。</p><p>如果步兵离炮火幕太远，守军就有时间恢复射击；如果跟得太近，又可能被己方炮火击中。炮兵计划的精确程度、士兵训练与地形，都决定它能否发挥作用。</p><div><div><div></div><div>Note</div></div><div><p><strong>寻找隐藏的敌军炮位</strong></p><p>闪光观测由不同观察点记录炮口闪光方向，再交会判断炮位；声测则利用沿线传声器记录炮声。英军在 1916 年改进声测设备，减少背景噪声干扰，使侦测隐藏炮位的工作逐步更可靠。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/Vickers-machine-gun-crew-with-gas-masks--ff-3c1dfa2611e2.3uvmrvw7k6.webp" alt="1916 年 7 月，索姆河奥维莱附近佩戴防毒面具的英军维克斯机枪组" /><figcaption>1916 年 7 月，索姆河奥维莱附近佩戴防毒面具的英军维克斯机枪组</figcaption></figure><p></p></section><section><h2>即使夺下阵地以后，也仍然难以掌握战场<a href="#即使夺下阵地以后也仍然难以掌握战场"><span>#</span></a></h2><p>改进了炮击和步兵行动，下一步仍然要解决通信。</p><p>炮火幕可以按照事先计划移动，前线实际发生的情况，却未必能及时传回炮兵和司令部。</p><p>一支部队到达目标，可能需要增援；另一支部队被机枪压制，则可能需要重新集中炮火。两者都要把消息送出去，而电话线很容易被炮弹切断。</p><p>9 月，英军终于夺取此前久攻不下的蒂耶普瓦尔。参与作战的 <strong>弗朗西斯·麦克斯韦（Francis Maxwell）</strong> 在 9 月 28 日的信里，仍然详细描述了这种困难。</p><p>他谈到，电话线很快就会被炸断；灯光信号传得慢，还可能暴露位置；联络兵白天要冒着火力穿越战场，夜里又容易在满是弹坑的地面迷路。</p><p>指挥官在后方没有收到消息，可能不知道部队究竟还在进攻、已经受阻，还是已经进入敌方阵地。前方士兵没有收到命令，也可能无法判断，是继续前进，还是原地等候。</p><p>空中侦察与观察气球有助于发现目标、校正炮击，却不能代替每一处步兵阵地与后方之间的联络。而当炮火与烟尘遮住地面，观察也会受到限制。</p><p>因此，所谓“及时投入预备队”，并不只取决于有没有预备队。还要知道该把它们送到哪里，能否及时赶到，以及抵达时前方局势是否已经改变。</p></section><section><h2>德军守住防线的代价<a href="#德军守住防线的代价"><span>#</span></a></h2><p>德军同样积累了经验。</p><p>从德军一侧看，索姆河也远不是一场只要躲在地下，就可以轻易打赢的战役。</p><p>7 月 1 日许多地段的防御成功，并不意味着以后几个月仍然能用同样的代价守住。英法会调整攻击方法，也会持续投入火炮与部队攻势。</p><p>而德军必须不断修复阵地、补充守军，并决定哪些地方值得立即反攻。</p><p>向前反击，可以夺回有利位置，却可能重新进入英法已经覆盖的炮火范围；暂时后退，可以保存一部分兵力，但也可能使附近阵地暴露。</p><p>因此，“德军防线没有崩溃”，并不等于德国没有受到消耗。防线仍在，守住它的部队却可能已经失去大量经验丰富的骨干，需要不断换入新兵。</p><p>这也是凡尔登与索姆河相互牵动的地方。</p><p>德国在凡尔登希望法国不断补充守军；到了索姆河，德军自己也要承担维持防线的同一套负担。法国还在坚守凡尔登，德国也还在坚守索姆河，双方却越来越难把“守住了”当成“还没有付出太多代价”。</p></section><section><h2>泥泞的战场<a href="#泥泞的战场"><span>#</span></a></h2><p>战斗从夏天持续到冬天，而夏天被炮火打碎的道路和土地，到了秋冬积雨以后，就变成更加难以通行的泥地。运送炮弹、移动火炮、修复阵地和抬运伤员，都变得更费力。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/THE-BATTLE-OF-THE-SOMME%2C-JULY-NOVEMBER-1916-Q80279--ff-aee3c5c0d83b.102ym3h0s6.webp" alt="1916 年 11 月 6 日，穿过索姆河战区泥泞道路的英法车队" /><figcaption>1916 年 11 月 6 日，穿过索姆河战区泥泞道路的英法车队</figcaption></figure><p></p><p>车队需要向前运送物资，车辆却要在泥泞中艰难行进。前线一轮炮击所需的弹药，要靠这些通道不断补充。</p><p>炮兵能否继续支援，不只取决于仓库里还有多少炮弹。弹药要能送到，炮位要能维持，观察和通信也要跟得上。</p><p>步兵如果取得进展，而重炮和补给车辆无法及时前移，便无法有效开展新的攻势，甚至会无法守住已取得的战果。</p><p>然而，战斗并不会因为道路泥泞就停止。只是每前进一点、每守住一处阵地，所需要的人力与时间，又进一步增加了。</p><p>这便使得索姆河战场进一步加剧了德军承受的压力，也帮助法国减轻了凡尔登的负担。即便如此，协约国仍未有效突破德军防线，恢复大规模机动推进。</p><p>在战斗进入秋冬的泥泞之前，英国已经尝试用一种新型武器打开局面。时间回到 9 月。</p><hr /></section></section>
<section><h1>履带压过铁丝网<a href="#履带压过铁丝网"><span>#</span></a></h1><p>1916 年 9 月 15 日，英军在 <strong>弗莱尔—库尔瑟莱特（Flers–Courcelette）</strong> 战场首次使用坦克。</p><p>这一天，一种新型装甲车辆出现在战场上：车身呈菱形，履带环绕车体两侧，火炮或机枪装在侧面向外突出的舱内。</p><p>它就是英国的 <strong>Mark I 坦克</strong>。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/261001-WWI-4-verdun-somme-attrition/British-Mark-I-male-tank-Somme-25-September-1916--ff-148b1e5ee7e5.pg4sy1skt.webp" alt="1916 年 9 月 25 日，索姆河蒂耶普瓦尔附近的英国 Mark I 雄型坦克" /><figcaption>1916 年 9 月 25 日，索姆河蒂耶普瓦尔附近的英国 Mark I 雄型坦克</figcaption></figure><p></p><p>这张照片拍摄于十天后的索姆河战场。车体尾部还能看见早期型号使用的辅助转向装置，它与后世更紧凑、更可靠的坦克有很大不同。</p><p>坦克的设计，直接针对步兵深入敌军阵地的难题。</p><p>履带帮助车辆摧毁铁丝网、越过壕沟和崎岖地面；装甲在一定程度上保护乘员免受步枪与机枪火力杀伤；车载武器则用来摧毁阻碍步兵推进的火力点。</p><p>相比步兵徒步穿越无人区，这确实变得高效与安全许多。</p><p>但 1916 年的坦克，还远未成熟。</p><section><h2>能挡住外部攻击，却挡不住内部故障<a href="#能挡住外部攻击却挡不住内部故障"><span>#</span></a></h2><p>英军原本为首次行动安排了 49 辆坦克。</p><p>真正抵达出发位置、参加攻击，并能持续发挥作用的车辆却少得多。</p><p>早期坦克速度缓慢，发动机与传动系统不够可靠，车内又有高温、噪音和废气。乘员需要在恶劣环境里驾驶、观察、射击并处理故障。</p><p>履带能帮助坦克越过一些障碍，但车辆也可能陷进深弹坑或泥地。装甲能抵挡部分轻武器射击，却仍然受到大口径火炮的威胁。</p><p>因此，坦克让士兵有机会在装甲保护下接近敌人，却又带来了维修技术、燃料供应、乘员训练与行进路线选择等新问题。</p></section><section><h2>步坦炮协同作战<a href="#步坦炮协同作战"><span>#</span></a></h2><p>更重要的，是如何把坦克放进原有的作战体系。</p><p>步兵要知道坦克的位置和攻击目标；炮兵要避免误伤坦克，又不能为了给它们留出通道，使火力幕出现空隙，让敌军有机会恢复射击；如果坦克迟到或损坏，步兵也不能一直等下去。</p><p>在部分地段，坦克确实帮助部队夺取了据点。这种从未见过的钢铁怪兽，也给守军造成了心理冲击。</p><p>但少量车辆分散在不同攻击地段，既不足以独自打开缺口，也没有能力在突破后高速深入敌军后方。</p><p>不过，如果等到坦克数量更多、性能更可靠时再投入，眼前的战役就仍要继续消耗下去；如果尽早使用，或许可以在初期给德军制造不小的压力，增加突破的机会，还能积累实战经验。</p><p>所以黑格坚持在这一时候便投入坦克作战。</p><p>后来，对这一选择一直存在争论：只投入少量坦克，是否过早暴露了新武器，削弱了今后大规模使用时的震慑性？还是说，要完善这种武器，本来就需要先经过实战试用？</p><p>索姆河战役并没有在坦克登场以后结束。</p><p>战斗一直持续到秋冬，天气恶化，地面越来越泥泞，进攻也越来越困难。11 月 18 日，这一年的主要攻势结束。</p><p>协约国在部分地段推进了十余公里，但没有实现预期的战略突破。英国及其帝国各地参战部队的总损失约为 42 万人，法军约为 20 万人。对于德军的损失，不同估计相差较大，但通常都达到数十万人。</p><hr /></section></section>
<section><h1>1916 年结束了，可战争仍然没有结束<a href="#1916-年结束了可战争仍然没有结束"><span>#</span></a></h1><p>没有一个主要参战国在这一年退出战争。</p>

<table><thead><tr><th>战场</th><th>当时希望实现的目标</th><th>到年底仍然存在的问题</th></tr></thead><tbody><tr><td>凡尔登</td><td>威胁重要阵地，削弱法国承受力</td><td>德军也遭到重创，法国没有退出战争</td></tr><tr><td>索姆河</td><td>减轻盟友压力，削弱德军并寻求突破</td><td>德军受压，但防线没有发生战略性崩溃</td></tr><tr><td>日德兰</td><td>改变海上力量对比</td><td>英国封锁继续，德国主力舰队仍存在</td></tr><tr><td>勃鲁西洛夫攻势</td><td>突破奥匈防线，分散同盟国资源</td><td>奥匈更加依赖德国，俄军也付出巨大代价</td></tr><tr><td>坦克首次参战</td><td>帮助步兵穿越障碍、压制据点</td><td>数量、可靠性与协同尚不足以决定战局</td></tr></tbody></table><section><h2>悄然改变的战场<a href="#悄然改变的战场"><span>#</span></a></h2><p>凡尔登战役中，双方总损失约为七十万人；索姆河战役的总损失则超过百万人。</p><p>德国能够继续守住战线，却越来越需要节约兵力、重建预备队。奥匈仍然参战，却更难独立支撑战局。俄国打出一次重要胜利，也没有解决战争给运输、财政和社会带来的长期负担。</p><p>协约国方面，英国军队积累了大规模作战的经验，法国也守住了凡尔登，但这些成果都是以沉重代价换来的。</p><p><strong>消耗战改变的，往往首先是战线背后的军队与国家，而不是地图上的战线位置。</strong></p><p>1916 年 8 月，法尔肯海因被撤换。兴登堡与鲁登道夫接管德国最高军事领导权。</p><p>凡尔登、索姆河、东线危机和罗马尼亚参战共同影响了这一变化。新的领导层开始推动进一步动员，也准备在西线修建更有利于节约兵力的防御体系。</p><p>后来被称为“兴登堡防线”的工事，正是在这样的压力下逐步形成。德军会在 1917 年撤向其中部分阵地，缩短战线。</p><p>1916 年的巨大消耗，也已经迫使德国改变下一年的作战安排。</p></section><section><h2>继续作战的判断<a href="#继续作战的判断"><span>#</span></a></h2><p>协约国希望加强多线配合，迫使德国无法兼顾所有战场；德国则看到法俄与英国新军的沉重损失，仍期待对方先失去承受能力。工业生产、持续征兵与盟友援助使双方能够继续作战，也使指挥官难以判断敌军何时才会耗尽。</p><p>战争目标同样妨碍妥协。法国要收回被占领土，德国希望保住此前优势，各国还须考虑战后安全与国内政治。即使有人试探和谈，对可以接受的条件仍有很大分歧。1916 年结束时，双方都已经知道战争代价巨大，却仍相信再坚持一轮可能改变力量对比。</p><hr /></section></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考战争史专业百科、凡尔登纪念馆、英国国家陆军博物馆、格林尼治皇家博物馆及相关原始文本与历史影像。涉及“圣诞备忘录”时，区分战后刊载文本与能够独立核实的战时文件；战役损失数字采用约数或常见统计，并保留口径差异。</p><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>William Philpott / 1914-1918 Online — Warfare 1914–1918</strong><br />
用于理解工业化消耗战、步炮协同、战术学习与后勤限制之间的关系。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/warfare_1914-1918" target="_blank">专业百科原文</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Western Front</strong><br />
主要支持索姆河攻势原有计划、凡尔登造成的兵力调整，以及黑格与罗林森对作战目标的不同理解。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-western_front-2015-11-11.pdf" target="_blank">专业百科 PDF</a></p>
</li>
<li>
<p><strong>German Historical Institute / German History in Documents and Images — Erich von Falkenhayn’s “Christmas Memorandum”</strong><br />
收录法尔肯海因战后出版的相关文本；本文将其作为自我解释材料，未把原件存疑的文本直接当成已核实战时命令。<br />
<a href="https://germanhistorydocs.org/en/wilhelmine-germany-and-the-first-world-war-1890-1918/erich-von-falkenhayns-christmas-memorandum-december-1915" target="_blank">文本与出版出处</a></p>
</li>
<li>
<p><strong>法国国防部门 — Verdun, symbole d’une guerre d’usure</strong><br />
介绍凡尔登的消耗战性质，并指出所谓“圣诞备忘录”原件未被找到的问题。<br />
<a href="https://archives.defense.gouv.fr/actualites/articles/verdun-2016-verdun-symbole-d-une-guerre-d-usure.html" target="_blank">法国国防部门历史页面</a></p>
</li>
<li>
<p><strong>Mémorial de Verdun — La bataille de Verdun</strong><br />
支持凡尔登主要阶段、要塞得失、法军轮换、秋季反攻与损失规模。<br />
<a href="https://memorial-verdun.fr/fr/ressources/la-bataille-de-verdun" target="_blank">凡尔登纪念馆战役介绍</a></p>
</li>
<li>
<p><strong>法国国防部门 / Chemins de mémoire — Béveaux National Cemetery</strong><br />
支持凡尔登两岸作战的扩展，以及神圣之路的交通与补给作用。<br />
<a href="https://www.cheminsdememoire.gouv.fr/en/beveaux-national-cemetery" target="_blank">战场背景与纪念资料</a></p>
</li>
<li>
<p><strong>法国国防部门 / Chemins de mémoire — Les chemins de la mémoire，第 14 号专题资料</strong><br />
用于区分公路运输、道路维护、窄轨铁路与其他铁路在凡尔登补给中的不同作用。<br />
<a href="https://www.cheminsdememoire.gouv.fr/sites/default/files/editeur/MC14.pdf" target="_blank">神圣之路专题 PDF</a></p>
</li>
<li>
<p><strong>National Army Museum — 1916: Year of attrition</strong><br />
支持凡尔登与索姆河的战略关联、后续攻击和德军转向加强防御的背景。<br />
<a href="https://www.nam.ac.uk/explore/1916-year-attrition" target="_blank">英国国家陆军博物馆原文</a></p>
</li>
<li>
<p><strong>National Army Museum — Battle of the Somme</strong><br />
支持英法兵力调整、炮击效果、7 月 1 日结果差异与整场战役的概况。<br />
<a href="https://www.nam.ac.uk/explore/battle-somme" target="_blank">索姆河战役介绍</a></p>
</li>
<li>
<p><strong>William Philpott / 1914-1918 Online — Somme, Battles of</strong><br />
用于理解索姆河从最初突破设想转入长期战斗的过程，以及夏秋攻势的阶段变化。<br />
<a href="https://encyclopedia.1914-1918-online.net/wp-content/uploads/2023/01/ams/958994/pdf/1914-1918-Online-somme_battles_of-2014-11-01.pdf" target="_blank">专业百科 PDF</a></p>
</li>
<li>
<p><strong>National Army Museum — 1914: To arms</strong><br />
支持英国新军的扩张、“伙伴营”的招募基础及集中伤亡对社区的影响。<br />
<a href="https://www.nam.ac.uk/explore/to-arms-1914" target="_blank">新军与招募背景</a></p>
</li>
<li>
<p><strong>Government of Jersey — Commemoration of the Battle of the Somme, 1 July 2016</strong><br />
用于核对索姆河首日常见统计中的总损失与阵亡人数，避免将两者混用。<br />
<a href="https://www.gov.je/SiteCollectionDocuments/Government%20and%20administration/S%20Bailiffs%20Somme%20Commemoration%201%20July%202016%2020160713%20DS.pdf" target="_blank">纪念讲话原文 PDF</a></p>
</li>
<li>
<p><strong>National Army Museum — Attack of the tanks</strong><br />
支持 1916 年 9 月坦克首次参战、49 辆计划投入车辆与实际作战效能的差别，以及坦克和步炮协同的问题。<br />
<a href="https://www.nam.ac.uk/explore/attack-tanks" target="_blank">坦克首次作战介绍</a></p>
</li>
<li>
<p><strong>Andrew Choong Han Lin / Royal Museums Greenwich — Who won the Battle of Jutland?</strong><br />
支持日德兰海战主要阶段、双方舰艇损失，以及战术战果和持续海上控制之间的区别。<br />
<a href="https://www.rmg.co.uk/stories/maritime-history/curatorial/who-won-battle-jutland" target="_blank">格林尼治皇家博物馆原文</a></p>
</li>
<li>
<p><strong>National Museum of the Royal New Zealand Navy — Battle of Jutland</strong><br />
用于核对舰队接触、主力展开及夜间行动的基本过程。<br />
<a href="https://navymuseum.co.nz/explore/by-themes/world-war-one/battle-of-jutland/" target="_blank">新西兰皇家海军国家博物馆原文</a></p>
</li>
<li>
<p><strong>Orkney Museums — World War I: Battle of Jutland</strong><br />
支持英国战列巡洋舰爆炸与发射药、防火和弹药处理之间的关系。<br />
<a href="https://www.orkneymuseums.co.uk/world-war-i-battle-of-jutland/" target="_blank">奥克尼博物馆战役资料</a></p>
</li>
<li>
<p><strong>Timothy C. Dowling / 1914-1918 Online — Eastern Front</strong><br />
支持俄军攻击准备、奥匈防线危机、德国增援与后续追击受限的原因。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-eastern_front-2014-10-08.pdf" target="_blank">专业百科 PDF</a></p>
</li>
<li>
<p><strong>John W. Steinberg / 1914-1918 Online — Warfare 1914–1918 (Russian Empire)</strong><br />
支持俄军经历大撤退后的恢复，以及勃鲁西洛夫攻势对盟友与俄军自身的影响。<br />
<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-warfare_1914-1918_russian_empire-2015-10-27.pdf" target="_blank">专业百科 PDF</a></p>
</li>
<li>
<p><strong>Bruce W. Menning — Brusilov Offensive</strong><br />
支持俄军方面军之间的协调困难、科韦利方向的后续战斗与进攻成本。<br />
<a href="https://www.encyclopedia.com/history/encyclopedias-almanacs-transcripts-and-maps/brusilov-offensive" target="_blank">《现代欧洲百科》条目</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Italian Front；Civilian and Military Power (Germany)</strong><br />
分别支持奥匈在特伦蒂诺方向的行动与东线牵动，以及罗马尼亚参战后德国最高军事领导层的变化。<br />
<a href="https://encyclopedia.1914-1918-online.net/wp-content/uploads/2023/01/ams/959256/pdf/1914-1918-Online-italian_front-2014-10-08.pdf" target="_blank">意大利战线</a>、<a href="https://encyclopedia.1914-1918-online.net/pdf/1914-1918-Online-civilian_and_military_power_germany-2020-03-10.pdf" target="_blank">德国军政关系</a></p>
</li>
<li>
<p><strong>Henry Bordeaux — The Last Days of Fort Vaux, March 9–June 7, 1916</strong><br />
战时记述，英译本载有夏尔·德尔韦的日记，以及沃要塞的求援、通道战斗、缺水与投降过程。其爱国宣传和文学化叙述与可核实的战斗细节分别使用。<br />
<a href="https://www.gutenberg.org/cache/epub/48532/pg48532-images.html" target="_blank">公有领域英译本全文</a></p>
</li>
<li>
<p><strong>Mémorial de Verdun — Sur les pas des combattants de Verdun；Visiter le Fort de Vaux</strong><br />
支持炮击下的阵地生活、口渴、疲惫与轮换，以及沃要塞守军的通信和外部救援。<br />
<a href="https://memorial-verdun.fr/files/0a54ccad/transcriptions_mooc1bdef.pdf" target="_blank">纪念馆课程文字记录 PDF</a>、<a href="https://memorial-verdun.fr/en/memorial-et-forts/visiter-le-fort-de-vaux" target="_blank">沃要塞历史介绍</a></p>
</li>
<li>
<p><strong>John Masefield — The Old Front Line（1917）</strong><br />
提供索姆河战区地形、德军掩体与交通、霍桑岭和博蒙阿梅尔的观察材料；战时作者关于最终胜负的宣传判断不作为本文结论。<br />
<a href="https://www.gutenberg.org/cache/epub/20616/pg20616-images.html" target="_blank">公有领域全文</a></p>
</li>
<li>
<p><strong>Edward G. D. Liveing — Attack: An Infantry Subaltern’s Impression of July 1st, 1916（1918）</strong><br />
贡梅库尔方向参战军官的亲历记述，用于进攻前夜的等待、首日战斗和受伤后撤过程。本文未将其个人经历扩写成所有部队的统一经历。<br />
<a href="https://www.gutenberg.org/cache/epub/28145/pg28145-images.html" target="_blank">公有领域全文</a></p>
</li>
<li>
<p><strong>Veterans Affairs Canada — Battle of Beaumont-Hamel</strong><br />
支持纽芬兰团的出发位置、攻击时间、伤亡规模和次日 68 人应点的统计含义。<br />
<a href="https://veterans.gc.ca/en/remembrance/military-history/first-world-war/battle-beaumont-hamel" target="_blank">加拿大退伍军人事务部历史介绍</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Pozières</strong><br />
支持澳大利亚各师在波齐耶尔方向的轮换、德军炮击以及约六周作战中 23,000 人损失的统计范围。<br />
<a href="https://www.awm.gov.au/learn/schools/resources/1916/pozieres" target="_blank">澳大利亚战争纪念馆战役资料</a></p>
</li>
<li>
<p><strong>Elizabeth Greenhalgh / Australian War Memorial — The French at the Somme, 1916</strong><br />
支持法军第六集团军的作战安排、首日地段差异，以及联合进攻中的兵力、火炮、地形和协同限制。<br />
<a href="https://wm.awm.gov.au/read/somme" target="_blank">法军在索姆河的作战研究</a></p>
</li>
<li>
<p><strong>National Army Museum — Lieutenant-Colonel Francis Maxwell</strong><br />
收录麦克斯韦 1916 年 9 月的书信与作战材料，用于蒂耶普瓦尔的战术改进和电话、灯光、联络兵的通信困难。<br />
<a href="https://ww1.nam.ac.uk/stories/lieutenant-colonel-francis-maxwell/" target="_blank">英国国家陆军博物馆士兵经历资料</a></p>
</li>
<li>
<p><strong>Chris Baker / The Long, Long Trail — Evacuation of casualties from the Somme 1916</strong><br />
根据英国国家档案馆所藏医疗列车战时日记整理，支持前沿救护、伤员集中站与后送列车之间的联系。<br />
<a href="https://www.longlongtrail.co.uk/evacuation-of-casualties-from-the-somme-1916/" target="_blank">医疗撤离与档案出处</a></p>
</li>
<li>
<p><strong>Académie française — cote</strong><br />
词典的地形学释义说明以海拔标高辨认地形点的用法，并以“cote 304”为例。<br />
<a href="https://www.dictionnaire-academie.fr/article/A9C4444" target="_blank">法兰西学院词典：cote</a></p>
</li>
<li>
<p><strong>Canadian Heritage — Newfoundland and Labrador’s provincial symbols</strong><br />
历史部分介绍纽芬兰的自治领身份及 1949 年加入加拿大的过程。<br />
<a href="https://www.canada.ca/en/canadian-heritage/services/provincial-territorial-symbols-canada/newfoundland-and-labrador.html" target="_blank">加拿大文化遗产部历史资料</a></p>
</li>
<li>
<p><strong>Australian War Memorial — Intelligence on the Somme</strong>
介绍索姆河战役期间的闪光观测、声测定位，以及 1916 年传声器改进对过滤背景噪声的作用。
<a href="https://wm.awm.gov.au/read/intelligence-somme" target="_blank">澳大利亚战争纪念馆：索姆河战场情报</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>1916 年凡尔登 304 高地战壕中的法国第 87 步兵团士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:French%20soldiers%20of%20the%2087th%20Regiment%20shelter%20in%20their%20trenches%20at%20Hill%20304%20at%20Verdun.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者姓名不详；英国国家陆军博物馆（National Army Museum），NAM. 2007-03-7-3。Commons 文件页标注为公有领域。</p>
</li>
<li>
<p><strong>1916 年 9 月，索姆河战场上借助轨道搬运 15 英寸榴弹炮炮弹的士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:15inchHowitzerShellEnglebelmerWoodSeptember1916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：John Warwick Brooke；帝国战争博物馆（IWM），Q 4515。Commons 文件页标注为公有领域。</p>
</li>
<li>
<p><strong>德国总参谋长埃里希·冯·法尔肯海因</strong>：<a href="https://commons.wikimedia.org/wiki/File:Erich%20von%20Falkenhayn%2C%201861-1922%20LCCN2002714158.jpg" target="_blank">Wikimedia Commons 原图页</a>。Bain News Service 历史照片，George Grantham Bain Collection / 美国国会图书馆，LCCN 2002714158。馆藏标注无已知出版限制，Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1916 年凡尔登、默兹河两岸与周边要塞战区示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Verdun%20and%20Vincinity%20-%20Map.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国军事学院（西点军校）历史系制作的战役教学地图。美国陆军职务作品，Commons 标注为美国公有领域。原文件名中的 Vincinity 为托管文件的拼写。</p>
</li>
<li>
<p><strong>1916 年遭受炮火破坏的杜奥蒙要塞航空照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Douaumontfort.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详，原资料经法语维基百科及 Commons 收录。Commons 文件页标注为公有领域；原馆藏机构未明确标注。</p>
</li>
<li>
<p><strong>不晚于 1916 年拍摄的菲利普·贝当肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Photo%20of%20Philippe%20P%C3%A9tain.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Underwood &amp; Underwood；刊载于《The Outlook》，扫描版本来自 HathiTrust 数字馆藏。Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>1916 年描绘法军车队向凡尔登运送增援与弹药的战争插画</strong>：<a href="https://commons.wikimedia.org/wiki/File:At%20the%20Battle%20of%20Verdun%20-%20French%20reserves%20and%20ammunition%20are%20brought%20in%20by%20motorcades.png" target="_blank">Wikimedia Commons 原图页</a>。作者未署名；刊载于《Der Krieg 1914/19 in Wort und Bild》第 76 期，数字版本由奥地利上奥地利州立图书馆提供。Commons 依据匿名作品公开超过 70 年标注为公有领域。此图为插画。</p>
</li>
<li>
<p><strong>1916 年凡尔登默兹河左岸战区示意图，1921 年刊于《泰晤士报战争史》</strong>：<a href="https://commons.wikimedia.org/wiki/File:West%20bank%20of%20the%20Meuse%2C%20Verdun%2C%201916.png" target="_blank">Wikimedia Commons 原图页</a>。制图者为《The Times History of the War 1914–1921》工作人员，1921 年出版。Commons 标注为美国公有领域及英国作者不详作品公有领域。此图为战后绘制的战区示意图。</p>
</li>
<li>
<p><strong>1916 年 3 月 18 日，凡尔登附近作战中的法国 155 毫米长身管火炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:French%20155L%20sur%20affut%20truck%20in%20action%20near%20Verdun%2C%20March%201916%20%283%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Paul Dubray；La contemporaine，Fonds des Albums Valois。文件页采用 <a href="https://www.etalab.gouv.fr/licence-ouverte-open-licence" target="_blank">Licence Ouverte 1.0 开放许可</a>。</p>
</li>
<li>
<p><strong>1916 年 3 月，沃要塞内部的法军士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:M%20134%208%20le%20fort%20de%20Vaux%20mars%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详；刊载于《Le Miroir》第 134 期第 8 页，经 G. Garitan 扫描上传。Commons 标注为公有领域。拍摄月份依原刊说明，为 3 月，并非 6 月围攻期间。</p>
</li>
<li>
<p><strong>约 1914—1915 年的英国战列舰“铁公爵”号，后来担任杰利科的旗舰</strong>：<a href="https://commons.wikimedia.org/wiki/File:HMS%20Iron%20Duke%20%281912%29.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者未明确标注；经 Cyber Heritage 舰艇历史资料与 Commons 收录，Morven 为上传者。Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1916 年日德兰海战中英德舰队主要航迹示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Map%20of%20the%20Battle%20of%20Jutland%2C%201916.svg" target="_blank">Wikimedia Commons 原图页</a>。SVG 制作者：Grandiose；根据西点军校历史系公有领域地图 Jutland1916.jpg 重绘，并补充相关航迹与地理信息。<a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a> 授权。</p>
</li>
<li>
<p><strong>1916 年 5 月 31 日，“狮”号受炮击与“玛丽女王”号爆炸的两张照片合成图</strong>：<a href="https://commons.wikimedia.org/wiki/File:HMSLion%20besideHMSQueenMaryblowingup1916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详，照片摄于英国驱逐舰 HMS Lydiard；收入《The Fighting at Jutland》。托管版本由两张照片合成，不能理解为同一画面的单次曝光。Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>1916 年俄国西南方面军司令阿列克谢·勃鲁西洛夫</strong>：<a href="https://commons.wikimedia.org/wiki/File:%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D0%BB%20%D0%90.%D0%90.%20%D0%91%D1%80%D1%83%D1%81%D0%B8%D0%BB%D0%BE%D0%B2%2C%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者不详；刊载于俄文《战争纪事》（Летопись войны）1916 年第 84 期，第 1339 页。扫描：Владимир Васильев。Commons 标注为俄国及美国公有领域。</p>
</li>
<li>
<p><strong>1916 年勃鲁西洛夫攻势与东线南部战区示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:EasternFront1916b.jpg" target="_blank">Wikimedia Commons 原图页</a>。美国军事学院（西点军校）军事艺术与工程部门制作。美国联邦政府职务作品，Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>以 1916 年布恰奇地区俄军攻势为题材的战争插画</strong>：<a href="https://commons.wikimedia.org/wiki/File:From%20the%20fighting%20of%20the%20Russian%20offensive%20in%20the%20Buchach%20area%2C%201916.png" target="_blank">Wikimedia Commons 原图页</a>。作者：Ladislaus Tuszyński；刊载于《Illustrierte Geschichte des Weltkrieges 1914–1918》第五卷（1917），数字版本由上奥地利州立图书馆提供。作者于 1943 年去世，Commons 标注为公有领域。此图为战争题材插画，不能作为战术细节的直接记录。</p>
</li>
<li>
<p><strong>1917 年拍摄的英国远征军总司令道格拉斯·黑格肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Douglas%20Haig.jpg" target="_blank">Wikimedia Commons 原图页</a>。原摄影者未明确标注；美国国会图书馆，LCCN 91727740。当前文件从该馆完整照片中裁出肖像。Commons 标注为公有领域。</p>
</li>
<li>
<p><strong>1916 年索姆河战役主要攻击阶段与战线变化示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Battle%20of%20the%20Somme%201916%20map.png" target="_blank">Wikimedia Commons 原图页</a>。制作者：Gsl；基于 T. Dodson Stamps 与 Vincent J. Esposito 编辑的《A Short Military History of World War I — Atlas》（1950）地图绘制。制作者将本图释入公有领域。</p>
</li>
<li>
<p><strong>1916 年 7 月 1 日上午 7 时 20 分，霍桑岭地下装药爆炸的瞬间</strong>：<a href="https://commons.wikimedia.org/wiki/File:Hawthorn%20Ridge%20Redoubt%20mine%20%281%20July%201916%29%201.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Ernest Brooks；帝国战争博物馆（IWM），Q 754。英国官方历史照片，Commons 标注为英国政府版权到期的公有领域作品。托管版本曾裁剪并调整图像。</p>
</li>
<li>
<p><strong>1916 年 7 月 1 日，向拉布瓦塞勒方向前进的泰恩赛德爱尔兰旅支援连</strong>：<a href="https://commons.wikimedia.org/wiki/File:Tyneside%20Irish%20Brigade%20advancing%201%20July%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：英国皇家工兵第 1 印刷连人员，姓名不详；IWM，Q 53。英国官方历史照片，Commons 归入英国政府作品；当前版本曾经裁剪并调整对比度。</p>
</li>
<li>
<p><strong>1916 年 7 月 1 日进攻前，圣约翰支援壕中的纽芬兰士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:Newfoundland%20soldiers%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者未明确标注；纽芬兰与拉布拉多省档案馆，PANL NA-3105，圣约翰斯。Commons 标注为公有领域。照片拍摄于进攻前。</p>
</li>
<li>
<p><strong>1916 年 7 月，索姆河奥维莱附近战壕中的英国柴郡团士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:Cheshire%20Regiment%20trench%20Somme%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：John Warwick Brooke；IWM，Q 3990。Commons 标注为英国政府版权到期的公有领域作品；托管版本曾调整对比度及锐度。</p>
</li>
<li>
<p><strong>1916 年 8 月 28 日，从波齐耶尔北侧高地眺望德军第二道阵地</strong>：<a href="https://commons.wikimedia.org/wiki/File:Pozieres%20plateau%2028%20August%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：英国官方摄影人员，姓名未明确标注；澳大利亚战争纪念馆（AWM），EZ0099。Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1916 年 7 月 19 日，贝尔纳费森林附近走向救护站的伤员</strong>：<a href="https://commons.wikimedia.org/wiki/File:British%20wounded%20Bernafay%20Wood%2019%20July%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Ernest Brooks；IWM，Q 800。照片中包括英军伤员及帮助他们的德国俘虏。Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1916 年 7 月，索姆河奥维莱附近佩戴防毒面具的英军维克斯机枪组</strong>：<a href="https://commons.wikimedia.org/wiki/File:Vickers%20machine%20gun%20crew%20with%20gas%20masks.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：John Warwick Brooke；IWM，Q 3995。Commons 标注为英国政府版权到期的公有领域作品。</p>
</li>
<li>
<p><strong>1916 年 11 月 6 日，穿过索姆河战区泥泞道路的英法车队</strong>：<a href="https://commons.wikimedia.org/wiki/File:THE%20BATTLE%20OF%20THE%20SOMME%2C%20JULY-NOVEMBER%201916%20Q80279.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：法国官方摄影人员，姓名未明确标注；IWM，Q 80279，French First World War Official Exchange Collection。Commons 将该历史照片及扫描版本标注为公有领域。</p>
</li>
<li>
<p><strong>1916 年 9 月 25 日，索姆河蒂耶普瓦尔附近的英国 Mark I 雄型坦克</strong>：<a href="https://commons.wikimedia.org/wiki/File:British%20Mark%20I%20male%20tank%20Somme%2025%20September%201916.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影：Ernest Brooks；IWM，Q 2486。Commons 标注为英国政府版权到期的公有领域作品。拍摄日期为 9 月 25 日，并非首次参战的 9 月 15 日；早期上传版本曾做清理、明暗与锐度调整。</p>
</li>
</ol><div><div><div></div><div>Note</div></div><div><p>Wikimedia Commons 上的历史图片可能来自不同国家、不同年代与不同馆藏，其版权状态并不完全一致。本文在配图来源中按照文件页当前标注说明作者、馆藏与授权信息；若用于转载、商业发布或二次创作，仍应以对应 Commons 文件页及原馆藏机构的最新授权说明为准。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/260926-wwi-3-trenches-gas-submarines-gallipoli/</id>
      <title type="text">WWI-3 堑壕里外的战争：机枪、毒气、大炮、潜艇</title>
      <published>2026-09-26T00:00:00.000Z</published>
      <updated>2026-09-26T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/260926-wwi-3-trenches-gas-submarines-gallipoli/"/>
      <summary type="text">1914 年的机动作战冻结成贯穿西线的壕沟后，1915 年的列强开始寻找突破僵局的方法：更深的战壕、更密集的炮火、机枪、毒气与潜艇，以及绕开西线的达达尼尔远征。本文从战壕体系与攻击方式讲起，分析“一战刻板印象”是怎样形成的。</summary>
      <content type="html"><![CDATA[
<p>1915 年春天，如果一名士兵重新回到几个月前自己曾经经过的西线，他很可能已经认不出眼前的战场。</p>
<p>1914 年夏末，德法两军还在比利时和法国北部进行大规模机动。军团沿着道路前进，铁路不断把后备兵力送向前方，各国总参谋部仍在寻找一次能够改变整个战局的决定性会战。</p>
<p>到了第二年，战线却像被冻结了一样。</p>
<p>从北海岸边到瑞士边境，一条由战壕、铁丝网、掩体、炮兵阵地和交通壕组成的防御体系横亘在欧洲西部。过去几个月里为了躲避炮火临时挖出的土沟，正在变成一套能够容纳数十万士兵长期生活和作战的地下世界。</p>
<p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Front-Line-Trench%2C-Near-St.-Yves%2C-Ploegsteert-Wood%2C-May-1915-Art.IWMART4785--ff-877a36fd4509.54yjy7bxvl.webp" alt="描绘 1915 年 5 月比利时普洛赫斯泰尔特森林附近前沿战壕的画作" /><figcaption>描绘 1915 年 5 月比利时普洛赫斯泰尔特森林附近前沿战壕的画作</figcaption></figure><p></p>
<p>后世对第一次世界大战最典型的视觉印象——泥泞的壕沟、横亘在无人区上的铁丝网、不断落下的炮弹、机枪火力与防毒面具——大多是在这样的环境中逐渐定型的。</p>
<p>各国仍在寻找打破僵局的办法。在西线，各个国家都在尝试用更密集的炮火摧毁对手的防御；德国试图让潜艇避开英国优势海军，从海上切断协约国贸易与补给线；英国和法国则把目光投向达达尼尔海峡，希望从另一个方向击败奥斯曼帝国。在东线，德奥军队甚至真的重新发动了大规模突破，把俄军逐出了波兰。</p>
<hr />
<section><h1>从临时壕沟到地下世界：1915 年的西线是怎么形成的<a href="#从临时壕沟到地下世界1915-年的西线是怎么形成的"><span>#</span></a></h1><p>1914 年秋天，战壕刚刚形成规模时，各国军队并不打算打一场持续数年的阵地战。</p><p>马恩河战役以后，德国军队向北撤退并重新构筑防线；英法联军试图不断从北面绕过德军侧翼，德军也进行同样的尝试。</p><p>结果就是上一回所说的“奔向大海”。</p><p>当双方的战线一路延伸到北海以后，西线几乎已经没有一个可以像战争初期那样自由绕过去的开放侧翼。想要向前推进，就必须正面穿过对方已经占据的阵地。</p><p>而在现代火炮、步枪和机枪面前，挖进土里恰恰是士兵能够采取的最直接、最有效的自我保护方式。</p><p>浅壕随后加深、加固，后方也增设阵地，以便第一线失守后继续防御，逐渐形成了多层工事。</p><section><h2>一条战壕并不只是一条沟<a href="#一条战壕并不只是一条沟"><span>#</span></a></h2><p>战壕体系的不同部分承担着不同任务：</p>

<table><thead><tr><th>组成</th><th>位置或关系</th><th>主要作用</th></tr></thead><tbody><tr><td>前沿阵地</td><td>最靠近敌军</td><td>直接承担守备与交战</td></tr><tr><td>支援战壕、预备战壕</td><td>位于前沿阵地之后</td><td>提供纵深，并容纳后备兵力</td></tr><tr><td>交通壕</td><td>连接前沿与后方阵地</td><td>向前输送弹药、食物、饮水和增援，同时后送伤员</td></tr></tbody></table><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Trench-construction-diagram-1914--ff-6edbe6ecdea5.7q34cy647.svg" alt="1914 年英军野战筑垒手册中的战壕结构示意图" /><figcaption>1914 年英军野战筑垒手册中的战壕结构示意图</figcaption></figure><p></p><p>战壕本身也很少笔直延伸。如果一条壕沟修成很长的直线，一发落入壕中的炮弹、一次爆炸，甚至一名闯入阵地的敌军士兵，都可能让危险沿着壕沟迅速向两侧扩散。因此，许多前线阵地会采用折线、横墙或分段结构，把冲击和射界限制在较小范围内。</p><p>壕沟前方修有胸墙，后方堆筑防护土层；较为重要的地段还会出现地下掩体、指挥所和弹药储存处。</p><p>纵深、障碍、交通与后备兵力互相支撑，使攻击者夺下前沿战壕后仍须面对后续防御。</p></section><section><h2>“无人区”并不真的无人<a href="#无人区并不真的无人"><span>#</span></a></h2><p>两军最前方的阵地之间，是后来成为一战象征之一的 <strong>无人区（No Man’s Land）</strong>。</p><p>它的宽度因地形和战线而异，有些地段只有几十米，有些则要宽得多。</p><p>这里往往散布着弹坑、泥浆、被炮火打碎的树木、废弃装备以及铁丝网。夜间，巡逻队会爬出战壕侦察敌军阵地，工兵会修补或重新铺设铁丝网，士兵还可能被派去监听地下掘进、回收伤员，或者寻找敌方巡逻队。</p><p>“无人区”是双方都难以稳定控制、却必须反复进入的危险地带。</p><div><div><div></div><div>Note</div></div><div><p>多数部队会在前沿、支援阵地、预备阵地和后方之间轮换。轮换期间仍要进行训练、维修和搬运，并不意味着完全停止军务。</p></div></div><p>到了 1915 年，西线从北海到瑞士已经形成一条大体连续的正面战场。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Northern-part-of-Western-front-1915-16--ff-deb56beb01e4.3d5l3asl0k.webp" alt="1915—1916 年西线北部主要战区示意图" /><figcaption>1915—1916 年西线北部主要战区示意图</figcaption></figure><p></p><p>这条线并不是绝对静止的。双方仍然会发动进攻，也会在局部地区推进几百米、几公里，甚至夺下重要高地和村庄。</p><p>但是<strong>一次局部突破后，再也不能轻松地把战果扩大成能够重新恢复大规模机动作战的战略突破。</strong></p><hr /></section></section>
<section><h1>现代火力瞬间让进攻态势灰飞烟灭<a href="#现代火力瞬间让进攻态势灰飞烟灭"><span>#</span></a></h1><p>战壕当然不是坚不可摧的。只需要一门足够大的火炮，就足以把胸墙炸开，高爆弹能够摧毁掩体，步兵如果集中足够兵力，也可能冲入一条前沿阵地。</p><p>真正让进攻变得如此棘手的，是攻击者必须在极短时间内连续解决一整串问题。</p><p>进攻开始以前，炮兵通常会轰击敌方阵地，切断铁丝网、摧毁火力点、压制敌军炮兵并迫使守军躲入掩体。</p><p>等到炮火停止或向前延伸以后，步兵再从己方战壕爬出，穿越无人区。</p><p>炮弹未必能够彻底切断铁丝网；被炮火压制的机枪组可以在轰击结束后重新回到射击位置；炮击留下的大量弹坑又会让进攻部队更难维持队形。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Royal-Munster-Fusiliers-on-the-Western-Front--ff-f464c163e857.6writ3vasm.webp" alt="1915 年费斯蒂贝尔战场上，皇家明斯特燧发枪团军官操作机枪" /><figcaption>1915 年费斯蒂贝尔战场上，皇家明斯特燧发枪团军官操作机枪</figcaption></figure><p></p><p>机枪与步枪、铁丝网、炮兵和纵深阵地配合，共同把无人区变成了火力覆盖区。</p><p>攻击者必须在暴露状态下向前移动，而防御者却可以躲在阵地里等待他们靠近。</p><p>即使第一波部队真的冲入敌军战壕，另一个问题也会立刻出现。</p><section><h2>占领第一条战壕之后怎么办？<a href="#占领第一条战壕之后怎么办"><span>#</span></a></h2><p>1915 年 3 月，英军在 <strong>新沙佩勒战役（Battle of Neuve Chapelle）</strong> 中发动了一次准备较为充分的集中攻势。</p><p>短时间炮击取得了明显效果，英军一度突破德军前沿，并证明经过细致侦察、火炮覆盖和步兵协同以后，战壕并非无法攻破。</p><p>然而，很快便遇到了问题。电话线被炮火切断，传令兵需要穿越被破坏的地形，前线部队难以及时让后方知道自己究竟推进到了哪里。</p><p>预备队还要从后方沿着狭窄道路和交通壕向前移动。与此同时，炮兵又无法随时掌握最前方步兵已经推进到什么位置，原本精确制定的炮火计划很快就可能与实际战况脱节。</p><p>第一道阵地被占领以后，守军后方的机枪、炮兵和预备队仍在准备下一轮防守。防御者可以沿相对熟悉的道路调兵，甚至通过更后方的铁路把更多部队送向受到威胁的战区。</p><p>于是，一场进攻中出现了一个非常奇怪的速度差：</p><p><strong>越成功的进攻者，越要向被炮火摧毁、道路陌生的危险地带前进；越后退的防御者，反而越接近自己熟悉的阵地、预备阵地，甚至是提前为来袭的敌军布置好的陷阱。</strong></p><p>突破第一条壕沟所需要的力量，和把突破扩大几十公里所需要的力量，完全不是同一个数量级的问题。</p><p>1915 年春秋两季，法国在阿图瓦和香槟发动大规模攻势；英国也在奥贝尔岭、费斯蒂贝尔和洛斯等地继续进攻。</p><p>这些行动在不同地段、不同时间取得过局部成功，也不断积累新的战术经验，却始终无法把西线重新变回 1914 年那种可以自由机动的战场。</p></section><section><h2>重炮反而让战争陷入胶着<a href="#重炮反而让战争陷入胶着"><span>#</span></a></h2><p>各国投入更多火炮与炮弹，延长轰击时间，调来更重的榴弹炮，也尝试让炮火更贴近步兵推进。</p><p>一场攻势不再只是“把更多步兵送上去”，而需要先积累大量炮弹、修建炮兵阵地、布设铁路和道路、准备通信线路，再把数以千计的火炮和数十万士兵集中到有限战区。</p><p>大规模炮击又会破坏道路和地形，让后续部队更难通过；准备时间越长，越可能让守军做好防备；弹药的巨大消耗，也在考验后方军工与运输能力。</p><p>1915 年英国爆发的“炮弹危机”就是这种变化的一个表现：战争消耗弹药的速度远远超过了工厂的生产速度。</p><div><div><div></div><div>Note</div></div><div><p><strong>1915 年的军需部</strong></p><p>英国在 1915 年成立军需部，由劳合·乔治负责。随后的《军需法》赋予政府更强的军工厂管理权，包括限制受管制工厂的工人自由离职，并要求劳资争议交由强制仲裁。</p></div></div><p>如果炮火不足以解决问题，那么有没有其他方法，可以让守军无法在战壕里停留？</p><p>1915 年 4 月，德军在伊普尔给出了一个举世震惊的答案。</p><hr /></section></section>
<section><h1>把空气变成武器：第二次伊普尔战役<a href="#把空气变成武器第二次伊普尔战役"><span>#</span></a></h1><p>1915 年 4 月 22 日傍晚，比利时伊普尔北面的战线上出现了一道颜色异常的气云。</p><p>德军在前沿阵地释放大量氯气，让气体随风飘向协约国阵地。</p><p>氯气进入呼吸道后会对肺部造成严重损伤，对于此前从未面对如此大规模化学攻击的士兵来说，真正可怕的不只是毒性本身，还在于他们几乎没有成熟的防护设备，甚至不知道这些绿色的气体究竟是什么。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/NYTMap2ndBattleOfYpres1915--ff-1792f000b0ae.5molmsdbhh.webp" alt="1915 年第二次伊普尔战役初期战线与德军毒气攻击区域示意图" /><figcaption>1915 年第二次伊普尔战役初期战线与德军毒气攻击区域示意图</figcaption></figure><p></p><p>法军部分阵地出现混乱，毒气迅速在战线上撕开了一道缺口。</p><p>这就是 <strong>第二次伊普尔战役（Second Battle of Ypres）</strong>，它通常被视为人类战争史上第一次大规模致死性毒气攻击，也是化学武器开始真正进入现代战场的重要节点。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/The-Second-Battle-of-Ypres--ff-6d2af51b60fa.1zj1z9hj02.webp" alt="加拿大官方战争艺术中的第二次伊普尔战役场景" /><figcaption>加拿大官方战争艺术中的第二次伊普尔战役场景</figcaption></figure><p></p><p>德军最初的攻击确实造成了一个意料之外的突破机会，可这次行动本来并不是一场已经准备好大批预备队、随时向纵深推进的战略总攻，造成的效果甚至超过了部分德军指挥官原先的预期。</p><p>结果是，前线出现了缺口，德军却没有足够快地把这一局部成功转化为决定性突破。</p><p>协约国则在最初混乱以后调动部队重新建立防线。</p><p>战斗继续了一个多月，德军取得了部分地面成果，却没有因此打开通向海峡港口的道路。</p><section><h2>从湿布到防毒面具<a href="#从湿布到防毒面具"><span>#</span></a></h2><p>化学战最初的优势，很大程度来自突然性。一旦士兵知道自己面对的是什么，便会迅速寻找防护办法。最早的应急措施十分简陋。士兵使用湿布等材料覆盖口鼻，希望减轻氯气的影响；随后，各国都开始配发经过化学处理的防护用品，再发展出更可靠的防毒面具和滤毒装置。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/French-soldiers-wearing-gas-masks-1915--ff-c6e8afae7912.1apsf8tzxw.webp" alt="1915 年佩戴早期防毒面具的法国士兵" /><figcaption>1915 年佩戴早期防毒面具的法国士兵</figcaption></figure><p></p><p>与此同时，化学武器本身也在升级。</p><p>英国也在 1915 年 9 月的洛斯战役中使用氯气；到同年末，更危险的光气也开始应用于战争。</p><p>后来还出现了芥子气等更难处理的化学武器。</p><p>防护技术削弱了毒气最初的突然性，促使交战国继续升级毒剂与投放方式。化学战造成大量伤亡与长期健康损害，仍未带来决定性突破。</p><div><div><div></div><div>Note</div></div><div><p>1899 年《海牙宣言》禁止以散布窒息性或有害气体为唯一目的的投射物，1907 年《海牙陆战法规和惯例章程》也禁止使用毒物或有毒武器。交战国围绕具体使用方式与条约适用范围，提出了不同的辩解与指责。</p></div></div><hr /></section></section>
<section><h1>海上僵局：封锁、U 艇与无限制潜艇战<a href="#海上僵局封锁u-艇与无限制潜艇战"><span>#</span></a></h1><p>如果说西线的问题是德军无法迅速击穿英法陆军的防线，那么海上的情况则恰好相反：</p><p><strong>德国很难在正面海战中抵挡英国的优势海军力量。</strong></p><p>英国皇家海军凭借更强大的主力舰队控制北海主要出入口，并实施远距离封锁，限制德国获取海外物资。</p><p>德国公海舰队虽然实力强大，却无法与英国主力进行一场可能决定整个舰队命运的全面决战。</p><p>德国转而使用潜艇，避开主力舰队，直接攻击海上运输。</p><div><div><div></div><div>Note</div></div><div><p><strong>U 艇的水面与水下动力</strong></p><p>当时多数远洋 U 艇在水面上使用柴油机，潜入水下后改用蓄电池驱动的电动机。柴油机需要空气，蓄电池的容量又有限，因此潜艇需要定期上浮航行和充电。</p></div></div><section><h2>撕毁传统海战规则的潜艇战<a href="#撕毁传统海战规则的潜艇战"><span>#</span></a></h2><p>传统海战需要截获商船时，军舰通常可以先拦截目标、检查船只和货物，再根据交战规则决定扣押、放行或击沉。</p><div><div><div></div><div>Note</div></div><div><p><strong>无限制潜艇战与商船攻击规则</strong></p><p>传统捕获规则通常要求军舰先查明商船身份，并在击沉前保障船上人员的安全。潜艇浮出水面执行这些程序时容易遭到攻击，也难以收容整船人员。无限制潜艇战绕开这些约束，使运输效率与中立国、平民的安全直接发生冲突；它并不意味着潜艇在全球任何海域都以同一规则攻击一切目标。</p></div></div><p>潜艇若上浮停在商船附近检查，就会失去隐蔽性，也容易受到武装商船、护航舰艇或其他力量攻击。</p><p><strong>潜艇最有效的战术，恰恰最难与传统的“先警告、再检查、再保障船员安全”的海上交战方式兼容。</strong></p><p>1915 年 2 月 4 日，德国宣布英国周边海域为战区，规定自 2 月 18 日起击沉其中的敌国商船，并表示未必能够保全船员和乘客。公告同时警告，中立船也可能因误认或海战中的其他情况受到攻击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/U-20-submarine-%28Germany%29---from%2C-U-Boote-Kiel-1914-%28cropped%29--ff-799412aa2371.4jowbwhhmt.webp" alt="第一次世界大战前夕的德国 U-20 潜艇" /><figcaption>第一次世界大战前夕的德国 U-20 潜艇</figcaption></figure><p></p><p>这种作战方式很快使德国与中立国之间的外交关系急剧恶化。</p><p>因为驶向英国的远洋轮船并不只搭载英国人，它们还可能运载美国和其他中立国公民，也可能同时装有普通商品、乘客行李以及被交战国认定为军事相关的货物。</p><p>于是，“能不能击沉一艘船”和“是否愿意承担击沉它所带来的外交代价”，成了两个完全不同的问题。</p></section><section><h2>卢西塔尼亚号事件<a href="#卢西塔尼亚号事件"><span>#</span></a></h2><p>1915 年 5 月 1 日，英国冠达邮轮公司的大型客轮 <strong>卢西塔尼亚号（RMS Lusitania）</strong> 从纽约驶向利物浦。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Last-photo-of-Lusitania-May-1-1915--ff-2ff3e3bb4701.3rc0u60vvd.webp" alt="1915 年 5 月 1 日离开纽约前的卢西塔尼亚号，这是其最后一批照片之一" /><figcaption>1915 年 5 月 1 日离开纽约前的卢西塔尼亚号，这是其最后一批照片之一</figcaption></figure><p></p><p>德国驻美国使馆此前已经公开警告，在英国周边战区航行的船只可能遭到攻击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Lusitania-warning--ff-c5c843244298.7axyjz3lne.webp" alt="1915 年 4 月美国报纸上与卢西塔尼亚号广告并列刊登的德国使馆航行警告" /><figcaption>1915 年 4 月美国报纸上与卢西塔尼亚号广告并列刊登的德国使馆航行警告</figcaption></figure><p></p><p>5 月 7 日，卢西塔尼亚号接近爱尔兰南部海岸时，被德国潜艇 <strong>U-20</strong> 发现。</p><p>U-20 发射的一枚鱼雷击中客轮，随后船体内部又发生第二次爆炸。不到二十分钟，卢西塔尼亚号沉没。</p><p>约 1,198 人遇难，其中包括 128 名美国公民。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Movements-of-RMS-Lusitania-and-SM-U-20--ff-1269c0300466.8dxnuuzfj7.svg" alt="U-20 与卢西塔尼亚号在 1915 年 5 月初的航行路线示意图" /><figcaption>U-20 与卢西塔尼亚号在 1915 年 5 月初的航行路线示意图</figcaption></figure><p></p><p>卢西塔尼亚号携带了包括弹药在内的战争相关物资，同时载有大量平民与中立国公民。</p><p>因此，争论很快不再只围绕“船上有什么货物”，而转向潜艇是否可以在不保障乘客安全的情况下直接击沉客轮，以及德国应如何对待中立国公民。</p><p>美国政府连续向德国提出强烈抗议。</p><div><div><div></div><div>Note</div></div><div><p>事件发生后，德国因美国压力一度收紧对大型客轮和部分商船的攻击限制。美国直到 1917 年才正式参战，德国当年重新实施无限制潜艇战是促成参战的因素之一。</p></div></div><p>德国如果过度克制，潜艇战就很难真正切断英国贸易；如果彻底放开攻击范围，又可能把中立强国推向敌方阵营。</p><hr /></section></section>
<section><h1>绕开西线：押上协约国希望的达达尼尔海峡<a href="#绕开西线押上协约国希望的达达尼尔海峡"><span>#</span></a></h1><p>早在 1914 年末，奥斯曼帝国加入同盟国一方后，英法就已开始考虑攻击达达尼尔海峡。</p><p>奥斯曼控制着连接爱琴海、马尔马拉海和黑海的 <strong>达达尼尔海峡与博斯普鲁斯海峡</strong>。这意味着俄国通过黑海进入地中海的传统海上通道受到封锁。</p><p>与此同时，西线已经陷入僵局。</p><p>于是，英国和法国的部分决策者开始思考：</p><p>如果无法在法国北部正面突破德国，是否可以攻击同盟国中相对薄弱的一环？</p><p>如果协约国舰队能够强行通过达达尼尔，威胁奥斯曼首都君士坦丁堡，不仅可能迫使奥斯曼帝国退出战争，还可能重新打开与俄国的海上联系，并影响仍在观望的巴尔干国家。</p><p>这个设想听起来极具诱惑力。</p><p>问题在于，战略目标和现实成功之间，隔着一条狭长、布满水雷、岸炮和坚固阵地的海峡。</p><section><h2>丘吉尔与“从海上打开海峡”的设想<a href="#丘吉尔与从海上打开海峡的设想"><span>#</span></a></h2><p>当时担任英国第一海军大臣的 <strong>温斯顿·丘吉尔</strong> 是达达尼尔行动最积极的支持者之一。</p><div><div><div></div><div>Note</div></div><div><p><strong>第一海军大臣与第一海务大臣</strong></p><p>第一海军大臣（First Lord of the Admiralty）是主管海军事务的内阁职务。海军的专业首长则是第一海务大臣（First Sea Lord），1915 年达达尼尔行动初期由海军上将约翰·费舍尔担任。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Churchill-Admiralty-Uniform-1914--ff-8b038c4dc334.46h6n53c7.webp" alt="1914 年身穿海军部礼服的英国第一海军大臣温斯顿·丘吉尔" /><figcaption>1914 年身穿海军部礼服的英国第一海军大臣温斯顿·丘吉尔</figcaption></figure><p></p><p>丘吉尔希望利用英国强大的海军力量，在不从西线抽调过多陆军的情况下打破局势。</p><p>这项行动经过英国政府和战争委员会讨论，得到其他高级决策者不同程度的支持，但各方高估了海军单独突破海峡的能力。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Graphic-map-of-the-Dardanelles--ff-18493db33dfa.13mkjt7uiv.webp" alt="1915 年达达尼尔海峡、加里波利半岛及协约国登陆区域地图" /><figcaption>1915 年达达尼尔海峡、加里波利半岛及协约国登陆区域地图</figcaption></figure><p></p><p>达达尼尔海峡并不是一条军舰开足马力就可以通过的航道。</p><p>奥斯曼军队在海峡两岸修筑了大量炮台，并在水道内布设多层水雷。</p><p>如果没有压制住炮台，扫雷舰就难以工作；如果不清除掉水雷，大型战舰便无法放心前进。</p><p>1915 年 3 月 18 日，英法舰队发动了一次大规模海上突击。</p><p>结果，多艘战舰触雷或遭受重创，法国战列舰“布韦”号以及英国战列舰“无阻”号、“海洋”号最终沉没。</p><p>单纯依靠舰队突破海峡的尝试就此受挫。</p><p>协约国随后转向一个更复杂、也更危险的方案：</p><p><strong>既然岸上的炮台和阵地阻止舰队前进，那就先派陆军登陆，把它们夺下来。</strong></p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/HMS-Irresistible-abandoned-18-March-1915--ff-d75d73a87ed2.5q87ki6e6i.webp" alt="1915 年 3 月 18 日达达尼尔海战中遭重创并被放弃的英国战列舰“无阻”号" /><figcaption>1915 年 3 月 18 日达达尼尔海战中遭重创并被放弃的英国战列舰“无阻”号</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Landing-of-Australian-troops-at-ANZAC-Cove%2C-25-April-1915-slnsw--ff-2b7a390fe4bf.1ow8642aty.webp" alt="1915 年 4 月 25 日登陆加里波利半岛安扎克湾的澳大利亚军队" /><figcaption>1915 年 4 月 25 日登陆加里波利半岛安扎克湾的澳大利亚军队</figcaption></figure></div></section><section><h2>加里波利战役：从海面打到陆上<a href="#加里波利战役从海面打到陆上"><span>#</span></a></h2><p>4 月 25 日，协约国军队在加里波利半岛多个地点登陆。</p><p>其中既有英国、法国部队，也有后来与加里波利记忆紧密相连的澳大利亚和新西兰军团——ANZAC。</p><p>但从登陆一开始，战场实际情况就与纸面计划出现了巨大落差。</p><p>半岛本身狭长，地形则多山丘、山脊和峡谷。</p><p>登陆部队离开海滩以后，并不是进入一片能够迅速展开的大平原，而要在陌生、崎岖的地形上向高地推进。</p><p>奥斯曼守军则可以利用制高点观察敌情并实施阻击。</p><p>在安扎克登陆区，奥斯曼第 19 师师长 <strong>穆斯塔法·凯末尔</strong> 在关键时刻主动把部队投入受到威胁的高地，成功阻止了协约国继续向内陆推进。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Turkish-trenches-at-Gallipoli--ff-ccc249409134.9rk6ywahkz.webp" alt="1915 年加里波利战壕中的奥斯曼军官与士兵，前排中央人物为穆斯塔法·凯末尔" /><figcaption>1915 年加里波利战壕中的奥斯曼军官与士兵，前排中央人物为穆斯塔法·凯末尔</figcaption></figure><p></p><p>凯末尔后来成为土耳其共和国的创建者，因此加里波利战役在此后的土耳其国家记忆中占据了极为重要的位置。</p><p>奥斯曼军队原本就已经部署了多支守军，德军将领利曼·冯·桑德斯负责整体防御体系；不同登陆区能取得的战果还受到地形、情报、指挥、登陆位置和协约国推进速度共同影响。</p></section><section><h2>似曾相识的战壕<a href="#似曾相识的战壕"><span>#</span></a></h2><p>协约国原本发动达达尼尔远征，就是希望绕开法国北部的战壕僵局。</p><p>可一旦最初登陆没有迅速取得成效，战壕战便开始在加里波利复刻：</p><p>双方挖掘战壕，铺设铁丝网，用机枪和炮兵控制狭窄地形。</p><p>一些战壕之间的距离甚至非常近。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/60-pounder-Cape-Helles-June-1915--ff-d6781fbd4456.3uvmrvtyn0.webp" alt="1915 年 6 月，加里波利赫勒斯角附近的英军 60 磅炮阵地" /><figcaption>1915 年 6 月，加里波利赫勒斯角附近的英军 60 磅炮阵地</figcaption></figure><p></p><p>这里的环境甚至比法国北部更加恶劣。</p><p>夏季炎热、饮水不足、尸体难以及时处理，疾病不断蔓延；到了冬季，暴雨和严寒又带来新的伤亡。</p><p>协约国尝试在 8 月再次发动大规模攻势，并在苏夫拉湾实施新的登陆战，希望夺取萨里拜尔山脊等关键高地，重新打破僵局，但这也失败了。</p><p>到年底，协约国已经很难找到继续留在半岛上的理由。</p><p>1915 年 12 月至 1916 年 1 月，部队开始分阶段撤离。</p><p>颇具讽刺意味的是，撤退反而成为整场行动中组织最成功的阶段之一：协约国成功骗过了奥斯曼军队，在没有被察觉的情况下撤走了大量人员。</p><p>但战略目标已经彻底落空。奥斯曼帝国没有被迫退出战争，达达尼尔海峡也没有被协约国打开。</p><p>兵力投入不足也限制了行动的成功空间。协约国试图绕开西线，却在加里波利陷入了另一场阵地战。</p><hr /></section></section>
<section><h1>东线重新动了起来：戈尔利采—塔尔努夫攻势与俄军大撤退<a href="#东线重新动了起来戈尔利采塔尔努夫攻势与俄军大撤退"><span>#</span></a></h1><p>从波罗的海到喀尔巴阡山，东线绵延更长，军队密度相对较低，铁路网也没有西欧那么密集。</p><p>这使双方更难构筑出一条像西线那样近乎连续、又拥有巨大纵深的防御带。</p><p>虽然依然存在着战壕和机枪，也有着相似的阵地战，但更大的地理空间意味着：</p><p><strong>只要攻击者能够在某个地段集中足够优势，并让防守一方来不及填补缺口，便有可能迫使对方大规模后退和重新机动。</strong></p><p>1915 年，德国决定把注意力转向东方，并与奥匈帝国一起发动一系列进攻。</p><p>其中最重要的转折点之一，就是 <strong>戈尔利采—塔尔努夫攻势（Gorlice–Tarnów Offensive）</strong>。</p><section><h2>一次真正撕开缺口的突破<a href="#一次真正撕开缺口的突破"><span>#</span></a></h2><p>1915 年 5 月初，德国和奥匈军队在加利西亚西部集中大量火炮和兵力，向俄军阵地发动进攻。</p><p>负责主要突击任务的德军第十一集团军由奥古斯特·冯·马肯森指挥。</p><p>与西线的许多防线相比，这里的俄军防线更浅，兵力和炮兵准备也更加薄弱。</p><p>俄罗斯军队此时还面临严重的武器和炮弹供应问题。</p><p>集中炮击以后，同盟国步兵迅速突破俄军阵地。</p><p>这一次，防守一方没有能够在几公里后方立刻构筑出一套与西线相同密度的第二、第三道完整防线。</p><p>缺口开始扩大。</p><p>俄军为了避免更多部队被包围，不断向东撤退。德奥军队重新占领加利西亚，并继续向俄属波兰推进。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Russian-retreat-1915-es--ff-5de9390cb407.8l0vqalkyx.svg" alt="1915 年俄军从加利西亚和波兰大规模后撤路线示意图" /><figcaption>1915 年俄军从加利西亚和波兰大规模后撤路线示意图</figcaption></figure><p></p><p>这一连串行动最终发展成通常所说的 <strong>俄军大撤退（Great Retreat）</strong>。</p><p>8 月 5 日，德军进入华沙。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Ewakuacja-wojsk-rosyjskich-z-Warszawy-1915--ff-a350a7bbcfc2.45igl196pd.webp" alt="1915 年俄军撤离华沙时的场景" /><figcaption>1915 年俄军撤离华沙时的场景</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260926-WWI-3-trenches-gas-submarines-gallipoli/Bundesarchiv-Bild-183-R42025%2C-Warschau%2C-Einmarsch-deutscher-Kavallerie--ff-2fa1b998cf7f.8hh9sksiam.webp" alt="1915 年 8 月 5 日德军骑兵进入华沙" /><figcaption>1915 年 8 月 5 日德军骑兵进入华沙</figcaption></figure></div><p>从地图上看，这是同盟国一次极为显著的胜利。</p><p>俄国在几个月里失去了大片领土，被迫向东撤退数百公里。</p><p>俄军在一些地区破坏铁路、桥梁和其他可能被敌军利用的设施，同时组织居民、工业设备和物资向内地迁移。</p><p>这样的行动虽然是军事上常见的“不给敌人留下资源”的考虑，却也给当地社会造成了巨大灾难。</p><p>然而，这同样不意味着“战争即将结束”。</p></section><section><h2>俄军不断后退，但没有退出战争<a href="#俄军不断后退但没有退出战争"><span>#</span></a></h2><p>俄军遭受严重损失，却没有被整体包围和消灭。</p><p>相反，大撤退本身的一部分目的，就是通过主动放弃难以防守的突出部，避免军队在波兰遭受夹击。</p><p>当战线缩短并向俄国内地移动以后，俄军仍然保持了大规模作战能力。</p><p>同盟国则面临另一个熟悉的问题：部队每向前推进一步，补给线就变得更长。</p><p>铁路需要修复，弹药和粮食要从更远的后方送来。</p><p>俄国巨大的纵深开始发挥与法国纵深“截然不同”的作用：</p><p>西线上，法国利用密集铁路和纵深战壕吸收突破态势；</p><p>东线上，俄罗斯则可以在承受巨大领土损失后，用空间换取保存军队的机会。</p><p>俄国失去大片领土，却保存了继续作战的军队。同盟国取得突破，仍未能迫使俄国退出战争。</p><hr /></section></section>
<section><h1>在 1915 年，没有“神奇武器”，只有一场越来越泥泞的战争<a href="#在-1915-年没有神奇武器只有一场越来越泥泞的战争"><span>#</span></a></h1><p>1915 年，各国尝试的破局方式受到不同限制：</p>

<table><thead><tr><th>破局方式</th><th>想解决的问题</th><th>实际遇到的限制</th></tr></thead><tbody><tr><td>重炮与火力集中</td><td>摧毁战壕和铁丝网，为步兵打开缺口</td><td>炮击同样会破坏地形；突破后通信、补给和后续推进仍然困难</td></tr><tr><td>毒气</td><td>迫使守军离开阵地，突然撕开防线</td><td>防护手段迅速出现，而且毒气本身无法解决“突破之后怎么办”</td></tr><tr><td>潜艇战</td><td>绕开英国主力舰队，直接打击海上贸易与补给</td><td>军事效率越高，越容易带来对中立国的外交风险</td></tr><tr><td>达达尼尔远征</td><td>绕开西线，从外围迫使奥斯曼帝国退出战争</td><td>海军突破受阻后转入登陆作战，最终又陷入另一场阵地战</td></tr><tr><td>东线大规模突破</td><td>恢复机动作战并削弱俄国</td><td>战线虽然向东移动数百公里，俄军主力仍得以保存并继续作战</td></tr></tbody></table><p>多次破局未果后，让敌人比自己更早耗尽兵力、物资与政治承受力，逐渐成为交战国的重要战略选择。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考 1914-1918 Online、International Committee of the Red Cross（ICRC）、Imperial War Museums（IWM）以及 Wikimedia Commons 等机构与资料库提供的战争史研究、国际法文本和历史影像资料。</p><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>1914-1918 Online — Warfare 1914-1918</strong><br />
介绍第一次世界大战不同战线的作战形态，以及西线阵地战、东线机动作战和战术适应之间的差异。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/warfare-1914-1918/" target="_blank">https://encyclopedia.1914-1918-online.net/article/warfare-1914-1918/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Western Front</strong><br />
梳理 1915 年西线主要攻势、阵地战发展以及协约国和德国对突破问题的不同应对。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/western-front/" target="_blank">https://encyclopedia.1914-1918-online.net/article/western-front/</a></p>
</li>
<li>
<p><strong>Imperial War Museums — Trench Warfare</strong><br />
介绍第一次世界大战战壕体系的构成、战壕生活与西线长期阵地战的基本特征。<br />
<a href="https://www.iwm.org.uk/history/10-photos-of-life-in-the-trenches" target="_blank">https://www.iwm.org.uk/history/10-photos-of-life-in-the-trenches</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Ypres, Battles of</strong><br />
介绍第二次伊普尔战役、德军毒气攻击及其在战役和西线战争中的位置。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/ypres-battles-of/" target="_blank">https://encyclopedia.1914-1918-online.net/article/ypres-battles-of/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Gas Warfare</strong><br />
介绍第一次世界大战化学武器的发展、氯气和光气的使用、防护装备演变及各交战国的化学战实践。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/gas-warfare/" target="_blank">https://encyclopedia.1914-1918-online.net/article/gas-warfare/</a></p>
</li>
<li>
<p><strong>International Committee of the Red Cross — Hague Declaration concerning Asphyxiating Gases, 1899</strong><br />
1899 年海牙会议关于禁止以散布窒息性或有害气体为唯一目的之投射物的相关文本。<br />
<a href="https://ihl-databases.icrc.org/en/ihl-treaties/hague-decl-2-1899" target="_blank">https://ihl-databases.icrc.org/en/ihl-treaties/hague-decl-2-1899</a></p>
</li>
<li>
<p><strong>International Committee of the Red Cross — Regulations concerning the Laws and Customs of War on Land, Article 23, 1907</strong><br />
1907 年海牙陆战法规中关于禁止使用毒物或有毒武器等条款。<br />
<a href="https://ihl-databases.icrc.org/en/ihl-treaties/hague-conv-iv-1907/regulations-art-23" target="_blank">https://ihl-databases.icrc.org/en/ihl-treaties/hague-conv-iv-1907/regulations-art-23</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Atlantic U-boat Campaign</strong><br />
介绍德国潜艇战、英国海上封锁，以及 1915 年以后潜艇作战在军事效果与外交压力之间的矛盾。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/atlantic-u-boat-campaign/" target="_blank">https://encyclopedia.1914-1918-online.net/article/atlantic-u-boat-campaign/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Lusitania, Sinking of</strong><br />
介绍卢西塔尼亚号沉没经过、伤亡、船上货物争议以及事件对美德关系的影响。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/lusitania-sinking-of/" target="_blank">https://encyclopedia.1914-1918-online.net/article/lusitania-sinking-of/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Gallipoli, Campaign and Battle of</strong><br />
介绍达达尼尔海上行动、加里波利登陆、陆上战役及最终撤退。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/gallipoli-campaign-and-battle-of/" target="_blank">https://encyclopedia.1914-1918-online.net/article/gallipoli-campaign-and-battle-of/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Churchill, Winston Leonard Spencer</strong><br />
用于了解丘吉尔在达达尼尔计划形成过程中的作用，以及把整个行动简单归因于个人决策的局限。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/churchill-winston-leonard-spencer/" target="_blank">https://encyclopedia.1914-1918-online.net/article/churchill-winston-leonard-spencer/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Kemal, Mustafa (Atatürk)</strong><br />
介绍穆斯塔法·凯末尔在加里波利战役中的指挥经历，以及其战时经历与后来历史记忆之间的关系。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/kemal-mustafa-ataturk/" target="_blank">https://encyclopedia.1914-1918-online.net/article/kemal-mustafa-ataturk/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Offensive, Gorlice-Tarnów</strong><br />
介绍 1915 年戈尔利采—塔尔努夫攻势、德奥炮兵集中、俄军防御状况以及突破对东线局势的影响。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/offensive-gorlice-tarnow/" target="_blank">https://encyclopedia.1914-1918-online.net/article/offensive-gorlice-tarnow/</a></p>
</li>
<li>
<p><strong>1914-1918 Online — Eastern Front</strong><br />
用于理解东线战场更大的空间、较低的兵力密度、铁路条件及 1915 年俄军大撤退的整体背景。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/eastern-front/" target="_blank">https://encyclopedia.1914-1918-online.net/article/eastern-front/</a></p>
</li>
<li>
<p><strong>U.S. Naval History and Heritage Command — Anatomy of a U-boat</strong><br />
展览资料中的 U 艇结构说明，介绍水面柴油动力、水下蓄电池动力与上浮充电的关系。<br />
<a href="https://www.history.navy.mil/content/dam/nhhc/browse-by-topic/War%20and%20Conflict/world-war-i/panels_7-12.pdf" target="_blank">第一次世界大战展览资料</a></p>
</li>
<li>
<p><strong>Churchill Archive — Churchill’s First World War</strong><br />
关于丘吉尔的第一海军大臣职务，以及第一海务大臣费舍尔在达达尼尔行动中的作用。<br />
<a href="https://www.churchillarchive.com/churchill-first-world-war" target="_blank">Churchill’s First World War</a></p>
</li>
<li>
<p><strong>UK Parliament — Munitions of War Act 1915</strong>
介绍 1915 年军需部及《军需法》对军工厂管理、工人离职与劳资争议处理的规定。
<a href="https://www.parliament.uk/about/living-heritage/transformingsociety/parliament-and-the-first-world-war/legislation-and-acts-of-war/kdjgh/" target="_blank">英国议会：1915 年《军需法》</a></p>
</li>
<li>
<p><strong>U.S. Department of State, Office of the Historian — 德国政府关于海上战区的说明备忘录（1915 年 2 月 4 日）</strong><br />
同期外交文件，用于核对德国公告的攻击对象、实施日期及对中立船只的警告。<br />
<a href="https://history.state.gov/historicaldocuments/frus1915Supp/d130" target="_blank">德国驻美使馆转交文本</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>描绘 1915 年 5 月比利时普洛赫斯泰尔特森林附近前沿战壕的画作</strong><br />
作者：Geoffrey Rose；Imperial War Museums，ART 4785。作品创作于 1915 年，Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Front_Line_Trench,_Near_St._Yves,_Ploegsteert_Wood,_May_1915_Art.IWMART4785.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Front_Line_Trench,_Near_St._Yves,_Ploegsteert_Wood,_May_1915_Art.IWMART4785.jpg</a></p>
</li>
<li>
<p><strong>1914 年英军野战筑垒手册中的战壕结构示意图</strong><br />
出自 E. J. Solano《Field Entrenchments: Spadework for Riflemen》（1914）。Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Trench_construction_diagram_1914.svg" target="_blank">https://commons.wikimedia.org/wiki/File:Trench_construction_diagram_1914.svg</a></p>
</li>
<li>
<p><strong>1915—1916 年西线北部主要战区示意图</strong><br />
来源：United States Military Academy, Department of History。美国政府作品，Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Northern_part_of_Western_front_1915-16.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Northern_part_of_Western_front_1915-16.jpg</a></p>
</li>
<li>
<p><strong>1915 年费斯蒂贝尔战场上的皇家明斯特燧发枪团机枪组</strong><br />
摄影：G. Sealey King 中尉；帝国战争博物馆（IWM），Q 113850。Commons 文件页标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Royal_Munster_Fusiliers_on_the_Western_Front.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Royal_Munster_Fusiliers_on_the_Western_Front.jpg</a></p>
</li>
<li>
<p><strong>1915 年第二次伊普尔战役初期战线与毒气攻击区域示意图</strong><br />
来源：《New York Times Current History》，1915 年 6 月。Wikimedia Commons 标注为美国公有领域作品。<br />
<a href="https://commons.wikimedia.org/wiki/File:NYTMap2ndBattleOfYpres1915.png" target="_blank">https://commons.wikimedia.org/wiki/File:NYTMap2ndBattleOfYpres1915.png</a></p>
</li>
<li>
<p><strong>《第二次伊普尔战役》战争画作</strong><br />
作者：Richard Jack，1917；Canadian War Museum / Library and Archives Canada。Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:The_Second_Battle_of_Ypres.png" target="_blank">https://commons.wikimedia.org/wiki/File:The_Second_Battle_of_Ypres.png</a></p>
</li>
<li>
<p><strong>1915 年佩戴早期防毒面具的法国士兵</strong><br />
照片刊载于《Chicago Tribune》，1915 年 6 月 23 日。Wikimedia Commons 标注为美国公有领域作品。<br />
<a href="https://commons.wikimedia.org/wiki/File:French_soldiers_wearing_gas_masks_1915.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:French_soldiers_wearing_gas_masks_1915.jpg</a></p>
</li>
<li>
<p><strong>第一次世界大战前夕的德国 U-20 潜艇</strong><br />
1914 年基尔港德国潜艇照片，George Grantham Bain Collection / Library of Congress。Wikimedia Commons 标注无已知版权限制。<br />
<a href="https://commons.wikimedia.org/wiki/File:U_20_submarine_(Germany)_-_from,_U-Boote_Kiel_1914_(cropped).jpg" target="_blank">https://commons.wikimedia.org/wiki/File:U_20_submarine_(Germany)_-_from,_U-Boote_Kiel_1914_(cropped).jpg</a></p>
</li>
<li>
<p><strong>1915 年 5 月 1 日离开纽约前的卢西塔尼亚号</strong><br />
作者：G. R. Rance；Imperial War Museums，Q 48349。Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Last_photo_of_Lusitania_May_1_1915.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Last_photo_of_Lusitania_May_1_1915.jpg</a></p>
</li>
<li>
<p><strong>与卢西塔尼亚号广告并列刊登的德国使馆航行警告</strong><br />
1915 年 4 月美国报纸广告资料，作者不详。Wikimedia Commons 标注为美国公有领域作品。<br />
<a href="https://commons.wikimedia.org/wiki/File:Lusitania_warning.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Lusitania_warning.jpg</a></p>
</li>
<li>
<p><strong>U-20 与卢西塔尼亚号航行路线示意图</strong><br />
作者：Fangz；根据 Bailey 与 Ryan 的《The Lusitania Disaster》及英国海军第 40 室记录整理航线。以 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。<br />
<a href="https://commons.wikimedia.org/wiki/File:Movements_of_RMS_Lusitania_and_SM_U-20.svg" target="_blank">https://commons.wikimedia.org/wiki/File:Movements_of_RMS_Lusitania_and_SM_U-20.svg</a></p>
</li>
<li>
<p><strong>1914 年身穿海军部礼服的温斯顿·丘吉尔</strong><br />
摄影者不详，拍摄日期为 1914 年 9 月 16 日；Commons 文件页所列来源为 CBS News 图像，标注为美国公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Churchill_Admiralty_Uniform_1914.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Churchill_Admiralty_Uniform_1914.jpg</a></p>
</li>
<li>
<p><strong>1915 年达达尼尔海峡、加里波利半岛及登陆区域地图</strong><br />
制图：G. F. Morrell，1915；新南威尔士州立图书馆，馆藏记录 b3001973。Commons 标注原作为澳大利亚公有领域作品，数字复制件采用 <a href="https://creativecommons.org/licenses/by-sa/3.0/au/" target="_blank">CC BY-SA 3.0 Australia</a> 授权。<br />
<a href="https://commons.wikimedia.org/wiki/File:Graphic_map_of_the_Dardanelles.JPG" target="_blank">https://commons.wikimedia.org/wiki/File:Graphic_map_of_the_Dardanelles.JPG</a></p>
</li>
<li>
<p><strong>1915 年 3 月 18 日遭重创并被放弃的英国战列舰“无阻”号</strong><br />
英国皇家海军历史照片，Library of Congress / Wikimedia Commons 收录；因年代及政府作品属性，Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:HMS_Irresistible_abandoned_18_March_1915.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:HMS_Irresistible_abandoned_18_March_1915.jpg</a></p>
</li>
<li>
<p><strong>1915 年 4 月 25 日登陆安扎克湾的澳大利亚军队</strong><br />
记录澳大利亚第 4 营登陆场景，State Library of New South Wales 馆藏。Wikimedia Commons 标注为澳大利亚公有领域作品。<br />
<a href="https://commons.wikimedia.org/wiki/File:Landing_of_Australian_troops_at_ANZAC_Cove,_25_April_1915_slnsw.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Landing_of_Australian_troops_at_ANZAC_Cove,_25_April_1915_slnsw.jpg</a></p>
</li>
<li>
<p><strong>1915 年加里波利战壕中的奥斯曼军队与穆斯塔法·凯末尔</strong><br />
摄影者不详，奥斯曼帝国时期历史照片。Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Turkish_trenches_at_Gallipoli.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Turkish_trenches_at_Gallipoli.jpg</a></p>
</li>
<li>
<p><strong>1915 年 6 月加里波利赫勒斯角附近的英军 60 磅炮阵地</strong><br />
摄影：Ernest Brooks；英国官方战争摄影资料。Wikimedia Commons 标注为英国政府公有领域作品。<br />
<a href="https://commons.wikimedia.org/wiki/File:60_pounder_Cape_Helles_June_1915.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:60_pounder_Cape_Helles_June_1915.jpg</a></p>
</li>
<li>
<p><strong>1915 年俄军大撤退路线示意图</strong><br />
制图：RedTony；基于 Douglas Wilson Johnson 1916 年《The Great Russian Retreat》中的公有领域资料重制，托管版本经 Rowanwindwhistler 调整标注与版面。以 <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a> 授权。<br />
<a href="https://commons.wikimedia.org/wiki/File:Russian_retreat_1915-es.svg" target="_blank">https://commons.wikimedia.org/wiki/File:Russian_retreat_1915-es.svg</a></p>
</li>
<li>
<p><strong>1915 年俄军撤离华沙</strong><br />
摄影者不详，华沙历史明信片与照片收藏。Wikimedia Commons 标注为公有领域。<br />
<a href="https://commons.wikimedia.org/wiki/File:Ewakuacja_wojsk_rosyjskich_z_Warszawy_1915.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Ewakuacja_wojsk_rosyjskich_z_Warszawy_1915.jpg</a></p>
</li>
<li>
<p><strong>1915 年 8 月 5 日德军骑兵进入华沙</strong><br />
摄影：Alfred Kühlewindt；德国联邦档案馆（Bundesarchiv），Bild 183-R42025。Commons 文件页标注为公有领域，并保留 <a href="https://creativecommons.org/licenses/by-sa/3.0/de/" target="_blank">CC BY-SA 3.0 Germany</a> 授权；托管版本曾裁去边框。<br />
<a href="https://commons.wikimedia.org/wiki/File:Bundesarchiv_Bild_183-R42025,_Warschau,_Einmarsch_deutscher_Kavallerie.jpg" target="_blank">https://commons.wikimedia.org/wiki/File:Bundesarchiv_Bild_183-R42025,_Warschau,_Einmarsch_deutscher_Kavallerie.jpg</a></p>
</li>
</ol><div><div><div></div><div>Note</div></div><div><p>Wikimedia Commons 上的历史图片可能来自不同国家、不同年代与不同馆藏，其版权状态并不完全一致。本文在配图来源中按照文件页当前标注说明作者、馆藏与授权信息；若用于转载、商业发布或二次创作，仍应以对应 Commons 文件页及原馆藏机构的最新授权说明为准。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/260925-wwi-2-schlieffen-to-marne/</id>
      <title type="text">WWI-2 速胜计划破产：从德国入侵比利时到马恩河战役</title>
      <published>2026-09-25T00:00:00.000Z</published>
      <updated>2026-09-25T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/260925-wwi-2-schlieffen-to-marne/"/>
      <summary type="text">1914 年夏天，各国军队都试图用进攻在数周或数月内结束战争，却很快发现现代火力、百万大军、铁路调度与漫长补给线并不服从纸面计划。从德国入侵比利时、法国第十七号计划与蒙斯战役，到马恩河反击、“奔向大海”和西线战壕形成，探究一场被设想为速胜战的战争为什么变成了惨烈的消耗战。</summary>
      <content type="html"><![CDATA[
<p>1914 年 8 月，德军进入比利时，法军向阿尔萨斯—洛林方向发动进攻，英军则渡过英吉利海峡。</p>
<p>在七月危机中，外交官和政治家还在争分夺秒地准备着最后通牒、动员令与宣战书；如今，数百万士兵已经被运往边境。</p>
<p>开战时，主要参战国对战争最初阶段的设想大致如下：</p>

<table><thead><tr><th>国家</th><th>开战初期的主要战略设想</th></tr></thead><tbody><tr><td>德国</td><td>迅速击败法国，再把主力转向东方迎战俄罗斯</td></tr><tr><td>法国</td><td>主动进攻阿尔萨斯—洛林，并在德军主力暴露后寻找决战机会</td></tr><tr><td>俄罗斯</td><td>尽快向东普鲁士施压，以减轻法国承受的压力</td></tr><tr><td>英国</td><td>派遣规模不大的远征军赴法，与法军共同阻止德国西进</td></tr></tbody></table>
<p>几乎所有大国都拥有自己的战争计划。这些计划虽然各不相同，却都建立在一个危险的共同前提之上：<strong>只要依赖战争初期的快速机动与一次或数次决定性会战，仍有可能在敌人充分发挥全部人口、工业和经济潜力以前便决定胜负。</strong></p>
<p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Mobilisation-08-1914--ff-01ae7b3692f5.lwiv9udx8.webp" alt="1914 年 8 月 3 日，巴黎东站准备出发的法国动员士兵" /><figcaption>1914 年 8 月 3 日，巴黎东站准备出发的法国动员士兵</figcaption></figure><p></p>
<p>到 1914 年秋天，八月地图上向前伸展的进攻箭头，逐渐变成一条延伸至北海的战线。士兵挖掘越来越深的壕沟，守住一片高地或一条道路也要付出巨大代价。</p>
<p>各国都遭受重创，却没有一方彻底崩溃。原本以星期和月份计算的战争，开始以年计算。</p>
<hr />
<section><h1>纸面上的速决战：德法两国的豪赌<a href="#纸面上的速决战德法两国的豪赌"><span>#</span></a></h1><p>各国为动员、运输和开战行动准备多年，但这些计划依赖对敌军部署、交通和推进速度的判断。几项前提同时落空，就可能打乱整个行动。</p><section><h2>德国“施里芬计划”<a href="#德国施里芬计划"><span>#</span></a></h2><p>德国最棘手的战略问题来自地理。法国位于西面，俄罗斯位于东面。如果两国同时参战，德国就必须面对两线战争。</p><p>德国总参谋部长期认为，俄罗斯国土辽阔、铁路网相对稀疏，完成全面动员所需的时间会比法国更长。因此，德国有可能利用这一时间差，把大部分兵力首先投入西线：</p><p><strong>先击败法国，再通过铁路把部队调往东线迎战俄罗斯。</strong></p><p>在阿尔弗雷德·冯·施里芬担任总参谋长期间，这种思路逐渐发展为一套强调右翼大规模迂回的作战构想。</p><p>法国东北部与德国直接接壤的地区拥有较严密的要塞体系，如果德军只从阿尔萨斯—洛林正面推进，就可能被拖入正面消耗。</p><p>因此，德国计划把最强大的右翼通过卢森堡和比利时向西北展开，绕过法国主要边境防御体系，再向南、向东旋转，威胁巴黎和法军主力后方。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Schlieffen-Plan--ff-363e5f4ef4b5.9gxd5sj6dk.svg" alt="1914 年德国施里芬—毛奇作战构想与法国第十七号计划示意图" /><figcaption>1914 年德国施里芬—毛奇作战构想与法国第十七号计划示意图</figcaption></figure><p></p><p>后世通常把这套作战构想概括为“施里芬计划”。施里芬退休以后，继任总参谋长赫尔穆特·冯·毛奇继续调整部署，1914 年实际执行的是多年规划不断演变后的方案。</p><div><div><div></div><div>Note</div></div><div><p>历史学界对施里芬设想的右翼规模、是否从巴黎以西包抄，以及毛奇调整对战局的影响，仍有不同解释。</p></div></div><p>这套方案不仅要求部队在战场上取胜，还要求整个战争机器几乎都按照预定节奏运转。它依赖的关键前提，可以概括为：</p>

<table><thead><tr><th>依赖环节</th><th>必须成立的前提</th></tr></thead><tbody><tr><td>比利时方向</td><td>德军能够尽快突破比利时，不被要塞、交通破坏和持续抵抗长期拖住</td></tr><tr><td>运输与补给</td><td>庞大军队能够按期向前推进，补给也能跟上不断向西、向南延伸的部队</td></tr><tr><td>法军反应</td><td>法国主力大体按照德国预想的方式部署和行动</td></tr><tr><td>东线时间窗</td><td>俄罗斯不能过早在东方形成足以迫使德国分兵的严重威胁</td></tr><tr><td>指挥协调</td><td>各支德国集团军能够在数百公里宽的战线上保持联系并协调推进</td></tr></tbody></table></section><section><h2>法国“第十七号计划”<a href="#法国第十七号计划"><span>#</span></a></h2><p>法国同样有自己的战争计划。</p><p>1871 年普法战争失败以后，阿尔萨斯—洛林被德国吞并，这片地区在法国政治与军事记忆中始终具有重要意义。</p><p>到了战前，法国总参谋部制定了著名的 <strong>第十七号计划（Plan XVII）</strong>。</p><p>第十七号计划首先是一套动员和集中部署方案。它强调法军要保留主动权，并准备根据德国军队的部署从法国东北部发起进攻。</p><p>法国军方确实高度重视“进攻精神”，相信训练、士气和主动进攻能够克服敌军防御，也并不是完全没想过德国可能经过比利时。</p><p>法国最高统帅部低估了德国投入比利时方向的兵力规模，也没有准确判断德国右翼究竟会展开多远。</p><p>法军总司令约瑟夫·霞飞认为，如果德国把过多兵力投入北方，德军在阿尔萨斯—洛林和阿登方向就会变弱，法国可以通过进攻德军中央取得突破。</p><p>单从纸面推演来看，这种判断并非毫无逻辑。</p><p>现实却很快证明：德法双方都在试图通过自己的进攻打乱对手。</p><p>因此，战争刚开始时，并不是一方进攻、另一方防守，而是两个庞大的军事体系同时向前推进，尝试进攻对方的弱翼。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Helmuth-Johannes-Ludwig-von-Moltke-1914--ff-1c930e141fed.1766hkou52.webp" alt="1914 年的德国总参谋长赫尔穆特·冯·毛奇" /><figcaption>1914 年的德国总参谋长赫尔穆特·冯·毛奇</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Joseph-Joffre--ff-df84ea2233ac.6poaxpx2aw.webp" alt="一战初期的法国陆军总司令约瑟夫·霞飞" /><figcaption>一战初期的法国陆军总司令约瑟夫·霞飞</figcaption></figure></div><hr /></section></section>
<section><h1>比利时不是通透的走廊：穿越中立国的代价<a href="#比利时不是通透的走廊穿越中立国的代价"><span>#</span></a></h1><p>在德国的战争计划中，需要穿过比利时，这样可以绕开法国东北部的防御，争取从开阔的北方进入法国腹地，为右翼提供足够的展开空间。</p><p>但对比利时而言，这片国土当然不可能只是任由外国军队通行的“军事走廊”。</p><section><h2>比利时拒绝“借道”<a href="#比利时拒绝借道"><span>#</span></a></h2><p>1839 年的伦敦条约确认了比利时的独立与中立。</p><p>它不能只被理解成英国参战时援引的一纸法律文件：对比利时而言，中立是独立国家赖以存在的国际安排。</p><p>德国保证战后会尊重比利时领土，但这无法解决眼前的问题。一旦外国大军获准穿越，比利时自己就无法决定战争如何在本国进行，也很难继续向其他国家主张其中立地位。</p><p>因此，比利时政府拒绝了德国要求军队自由通行的最后通牒，并决定抵抗。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Albert-I.%2C-King-of-the-Belgians%2C-c.-1914--ff-9a4ee0e09b66.5xbffzggmu.webp" alt="约 1914 年的比利时国王阿尔贝一世" /><figcaption>约 1914 年的比利时国王阿尔贝一世</figcaption></figure><p></p><p>抵抗并非因为比利时相信自己能够单独战胜德军，而是要保住主权、履行本国法律责任，同时为期待中的外援争取时间。</p><p>比利时军队规模和武器配备都无法与德军相比；其野战部队的任务，是迟滞入侵而不是在边境打一场毁灭自己的决战。</p><p>这种有限目标，后来解释了它为何退向安特卫普，而非在列日附近与德国主力耗尽全部兵力。</p><p>可对德国总参谋部来说，绕经比利时的决定，不是一个能轻易在开战后改变的选项。</p><p>数十万人的动员与列车调度已经依预定方向展开；临时改为沿法德边境正面进攻，要重新进行军队调度和部署，这种变故在速战速决的计划下是不可接受的。</p><p>于是，德军于 1914 年 8 月 4 日强行越过比利时边境。</p></section><section><h2>列日战役：德军可以突破，但比利时也可以选择抵抗<a href="#列日战役德军可以突破但比利时也可以选择抵抗"><span>#</span></a></h2><p>列日及周边要塞首先成为战场，这里控制着默兹河谷和通向比利时腹地的重要铁路、道路，是德国右翼推进必须处理的障碍。</p><p>德国最初希望迅速夺取这一地区，可比利时野战军和要塞守军的抵抗却让德军发现：</p><p>穿越一个中立国家，绝不等同于在一张空白地图上行军。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Belgian-troops-block-a-road-in-Li%C3%A8ge---L%C3%BCttich-against-the-German-advance-1914--ff-d67103c02f81.9ddr82q3k4.webp" alt="1914 年列日附近阻断道路、准备抵抗德军推进的比利时军队" /><figcaption>1914 年列日附近阻断道路、准备抵抗德军推进的比利时军队</figcaption></figure><p></p><p>德军最终依靠重炮攻克了列日外围堡垒。这场抵抗并没有让德国计划立刻破产，德军主力仍然继续向西推进。</p><p>但它带来了几项真实影响。</p><p>首先，德国必须投入兵力、炮兵和时间清除要塞与交通障碍；</p><p>其次，比利时军队并没有在列日失守后消失，而是继续以安特卫普等地为依托牵制德国部队；</p><p>更重要的是，德国必须在敌对区域、被破坏的交通线和越来越漫长的补给线上维持后方秩序。</p><p>战争计划中的箭头可以连续移动，现实中的军队却需要吃饭、睡觉、补充弹药，还需要修复桥梁、管理道路，并留下部队守卫铁路和占领区。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Li%C3%A8ge%2C-le-pont-des-Arches-d%C3%A9truit-par-les-Belges-%C3%A0-lapproche-des-Allemands---btv1b6931567w--ff-271e35402655.2rvxh2wbz4.webp" alt="1914 年 8 月被比利时守军炸毁后的列日桥梁" /><figcaption>1914 年 8 月被比利时守军炸毁后的列日桥梁</figcaption></figure><p></p><p>这些消耗单独来看都不足以阻止德军推进，却会随着战线不断向前延伸而层层累积。</p></section><section><h2>战场上的怨恨催生了暴力<a href="#战场上的怨恨催生了暴力"><span>#</span></a></h2><p>德国军队进入比利时后，还发生了大规模的针对平民的报复行动。</p><p>德军普遍担心遭到所谓“自由射手”的袭击。这种恐惧部分来自 1870—1871 年普法战争的经验，也来自开战初期混乱环境中的传言、误击和友军火力。</p><p>在许多地区，德军把真实或想象中的平民抵抗解释为有组织的非法袭击，随后实施处决和集体屠杀，并焚毁了大量房屋。</p><p>鲁汶等地遭到严重破坏，大量比利时平民逃离家园，这些事件后来被协约国广泛宣传。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Belgian-refugees-1914--ff-01b9c729dfea.7q34em2xy.webp" alt="1914 年逃离战区的比利时难民" /><figcaption>1914 年逃离战区的比利时难民</figcaption></figure><p></p><div><div><div></div><div>Note</div></div><div><p>一战初期有关比利时暴行的宣传材料确实包含夸张和未经证实的故事，因此阅读当时宣传品时需要区分具体证据。但现代研究已经确认，德军在 1914 年入侵比利时和法国北部期间确实杀害了数千名平民，并在多个城镇实施了大规模报复和破坏。</p></div></div><hr /></section></section>
<section><h1>边境战役与蒙斯战役：双方都没能等到预期的“决战”<a href="#边境战役与蒙斯战役双方都没能等到预期的决战"><span>#</span></a></h1><p>就在德军右翼穿过比利时的时候，法国也发动了自己的攻势。</p><p>霞飞并不打算在巴黎等待德国人进攻过来。</p><p>按照法国方面的判断，如果德军将大量兵力投入比利时，那么德国在阿尔萨斯—洛林和阿登一线的正面防御就可能相对薄弱。</p><p>这也就代表着，法国可以主动进攻。</p><section><h2>边境战役：法国尝试“反将”德军<a href="#边境战役法国尝试反将德军"><span>#</span></a></h2><div><div><div></div><div>Note</div></div><div><p>“边境战役”通常是对 1914 年 8 月法国与比利时边境附近多场战斗的合称，涉及阿尔萨斯、洛林、阿登和桑布尔河一带。各处的指挥目标与战况并不完全相同，不能把它看成一场由单一命令决定的战役。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Battle-of-Frontiers---Map--ff-ff67f9fde24a.5fldref2w1.webp" alt="1914 年 8 月边境战役主要战线与进军方向示意图" /><figcaption>1914 年 8 月边境战役主要战线与进军方向示意图</figcaption></figure><p></p><p>8 月上中旬，法军向阿尔萨斯和洛林发动进攻。向这一地区进军，带有夺回失地的政治象征，也有试探和牵制德军的军事目的。最初的进攻取得了一部分进展。</p><p>但当法军进入德军准备好的防御区域后，感受到了现代火力的恐怖威力。</p><p>德军的机枪与野战炮部署在开阔地带，给法军造成了极高伤亡；进攻中的步兵很难在炮火下维持组织，更难及时获得准确情报。</p><p>与此同时，法国在更北面的阿登地区的攻势也接连受挫。这里的树林和复杂地形限制了视野，法国指挥官对德军兵力的预估也严重不足。</p><p>结果，多支法军在侦察不足、炮兵准备不充分的情况下迎面撞上德军主力。</p><p>1914 年 8 月下旬的“边境战役”给法国陆军造成了极其惨重的损失，尤其是在 8 月 22 日，被认为是法国军事史上伤亡最惨重的单日之一。</p><p>虽然法军在此战付出了巨大代价，却没有被一次失败彻底摧毁。</p></section><section><h2>蒙斯战役：英国远征军初次正面交战<a href="#蒙斯战役英国远征军初次正面交战"><span>#</span></a></h2><p>虽然英国在战前并未维持一支规模可与法德相比的陆军军队，1914 年被派往法国的英国远征军规模不算庞大，但也是一支训练有素的职业军人部队。</p><div><div><div></div><div>Note</div></div><div><p><strong>英国怎样招募士兵？</strong></p><p>1914 年英国仍依靠志愿募兵，正规军之外还有后备役与地方部队。基奇纳招募的战时新军需要先接受训练，直到 1915—1916 年才陆续参加大规模作战；英国在 1916 年引入征兵制。</p></div></div><p>8 月 23 日，英军在比利时蒙斯附近的运河线上与亚历山大·冯·克鲁克指挥的德国第一集团军交战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Battle-of-Mons--ff-39aca744be64.232nx0yijo.webp" alt="1914 年 8 月 23 日蒙斯战役兵力与战线示意图" /><figcaption>1914 年 8 月 23 日蒙斯战役兵力与战线示意图</figcaption></figure><p></p><p>蒙斯战役最容易被写成一个让人热血沸腾、却解释不了结果的故事：少量英军以步枪射速让德军误以为碰上大批机枪。</p><p>英军借阵地和密集的步枪射击给进攻的德军造成了显著损失，但这并不是一场“少量英军击败庞大德军”的决定性胜利。</p><p>侧翼的法军接连撤退后，英军继续坚守运河并不能单独守住比利时；德军只要从两侧推进，英军便可能被切断退路。于是，英国远征军指挥官约翰·弗伦奇最终也只得撤退。</p><p>但这并不等同于“溃败”，英国远征军南撤时，仍以不断的小规模阻击争取时间。</p><p>8 月 26 日的勒卡托战斗就是其中一环：部队停下来抗击追兵，避免退却演变成无序溃散。</p><p>这样的阻击有代价，但也切实减缓了德军的推进速度。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Retreat-from-Mons--ff-64c7ea750785.8s43lrvnd2.webp" alt="1914 年 8 月，从蒙斯撤退的英国远征军骑兵与马匹" /><figcaption>1914 年 8 月，从蒙斯撤退的英国远征军骑兵与马匹</figcaption></figure><p></p><p>法英军队虽损失惨重，却没有被合围消灭。</p><p>接下来十多天里，英法联军不断向法国腹地退却。</p><p>德军也已逐步开始向巴黎推进。</p></section><section><h2>悄然发生变化的局势<a href="#悄然发生变化的局势"><span>#</span></a></h2><p>霞飞面对的不是“现在进攻还是永远失败”的选择。</p><p>继续在已经失利的位置作战，可能让法军主力遭到包围；退得太快，又可能失去在巴黎与英军配合的机会。</p><p>他选择将各军向南撤至更有利的线位，利用法国的铁路网络把部队调向左翼，同时要求指挥官维持组织与通信。</p><p>总体上，法军还是保存了完整的主力。</p><p>8 月 29 日，法军第五集团军在吉斯一带反击。</p><p>反击不能立刻扭转整个战局，也没有把德军赶出法国，却使德军必须应付一个仍敢于进攻的对手。</p><p>它给法军后续撤退和调整争取了时间，更重要的是提醒德国：法军虽然从比利时边境后退，距离被彻底击溃仍很远。</p><p>霞飞随后继续更换部分高级指挥官，重排部队。</p><p>他对战前部署判断失误负有责任，但也在失败后展现了组织调整的能力。</p><p>英军与法军的关系也不是从一开始就严丝合缝。</p><p>英国远征军由约翰·弗伦奇指挥，保留独立指挥体系；连续退却后，他一度考虑让部队暂时脱离前线休整。</p><p>英国政府担心这会危及同法国的合作，便让陆军大臣基钦纳赴前线干预。后来英军重新参与反击，离不开这一层政治与军事协调。</p><p>英军总司令的顾虑可以理解：这支小规模军队经不起无休止的损失，也不能完全接受法国指挥部替它决定行动。</p><p>但英国若让部队离开法国左翼，便可能使巴黎附近的防线出现新的缺口，最后反过来危及英国的战略目标。</p><p>法英合作之所以经受住 8 月退却，不是因为两军天然配合默契，而是双方政府和将领仍认为共同作战比各自保存兵力更有利。</p><p>德军需要切断法军退路、迫其决战，却始终未能完成包围。继续追击使右翼补给线拉长，各集团军的联系也越来越难以维持；法国后退则缩短了本方补给线，获得了重新集结的空间。</p><hr /></section></section>
<section><h1>坦能堡战役：东线的一次辉煌胜利，却暴露了德国的严重误判<a href="#坦能堡战役东线的一次辉煌胜利却暴露了德国的严重误判"><span>#</span></a></h1><p>德国的西线计划之所以如此强调速度，还因为另一个关键判断：</p><p><strong>它假定俄国需要较长时间才能形成有效攻势。</strong></p><p>俄国领土辽阔，铁路密度远低于德国，完成全面动员和集中确实困难。</p><p>但“俄国完成全部动员较慢”，并不等于“俄国在最初几周完全无法行动”。</p><p>法国尤其希望俄国尽快从东方进攻，以迫使德国分散兵力。</p><p>俄罗斯也确实比德国的预期更早进入东普鲁士。</p><section><h2>俄军提前进入东普鲁士<a href="#俄军提前进入东普鲁士"><span>#</span></a></h2><p>俄国第一集团军由保罗·冯·伦南坎普指挥，从东面进入东普鲁士；第二集团军由亚历山大·萨姆索诺夫指挥，从南面推进。</p><p>8 月 20 日，德军在贡宾嫩战役中受挫，东普鲁士危在旦夕。</p><p>德国第八集团军指挥层发生更换，退休将领保罗·冯·兴登堡被召回，埃里希·鲁登道夫出任其参谋长。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Hindenburg-and-Ludendorff--ff-048925bf3ea5.46h6ot0br.webp" alt="保罗·冯·兴登堡与埃里希·鲁登道夫" /><figcaption>保罗·冯·兴登堡与埃里希·鲁登道夫</figcaption></figure><p></p><p>随后，德军利用铁路运输、较有利的内线位置，以及俄军两个集团军之间协调不畅的弱点，迅速把主力转向萨姆索诺夫指挥的第二集团军。</p><p>与此同时，俄军的通信保密也存在严重问题，一些无线电报没有得到充分加密，使德军得以掌握重要部署信息。</p><p>8 月 26 日至 30 日，俄国第二集团军在坦能堡附近遭到包围。</p><p>大量俄军被俘，萨姆索诺夫自杀。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Tannenberg-tysk-plan--ff-72dea4d39922.wjcof9m2x.webp" alt="1914 年 8 月坦能堡战役德军机动与包围计划示意图" /><figcaption>1914 年 8 月坦能堡战役德军机动与包围计划示意图</figcaption></figure><p></p><p>坦能堡是德国在一战早期最耀眼的军事胜利之一。</p><p>这场胜利也迅速使兴登堡与鲁登道夫成为德国公众眼中的英雄。</p><div><div><div></div><div>Note</div></div><div><p><strong>坦能堡与“雪耻”宣传</strong></p><p>1410 年，条顿骑士团曾在同名的坦能堡战役中败于波兰与立陶宛联军。1914 年德国宣传把这次胜利塑造成对旧日败绩的“雪耻”，赋予它跨越数百年的民族象征意义。</p></div></div><p>但是，如果把它放回整个 1914 年战争，实际上十分矛盾：</p><p><strong>俄国在战术上遭到灾难性失败，却在战略上证明自己能够比德国预测更早向东普鲁士施加压力。</strong></p><p>这迫使德国最高统帅部重新考虑来自东方的风险。</p></section><section><h2>不得不应对的两线作战<a href="#不得不应对的两线作战"><span>#</span></a></h2><p>马恩河战役前，毛奇从西线调出两个军及一个骑兵师向东增援。</p><div><div><div></div><div>Note</div></div><div><p><strong>“军”与“集团军”的层级</strong></p><p>本文的“军”对应 corps，“集团军”对应 army。常见的编制层级是“集团军—军—师”：集团军下辖多个军，军再下辖若干师；具体编制与人数因国家、时期和战况而异。</p></div></div><p>后来常见的一种解释是：</p><p>“正是这两个军被调走，德国右翼才在马恩河缺少决定胜负的兵力。”</p><p>然而，被调往东方的部队并没有赶上坦能堡的主要战斗，而西线失败也有更深层的原因：</p><ul>
<li>右翼军队长期行军后的疲劳；</li>
<li>补给线过长；</li>
<li>德军集团军之间出现空隙；</li>
<li>克鲁克改变推进方向；</li>
<li>法国成功组建新的第六集团军；</li>
<li>英法联军在撤退中保持了战斗力；</li>
</ul><p>以及德国最高统帅部越来越难以有效控制分散在广大正面的各个集团军。</p><p>东线压力迫使德国在击败法国以前就分兵应对，两线作战的资源争夺比预想更早出现。</p><hr /></section></section>
<section><h1>“胜利在望”的德军<a href="#胜利在望的德军"><span>#</span></a></h1><p>8 月下旬，德军在西线仍然不断前进，并看似取得了很多战果：</p><p>比利时大部分地区已经被突破，英法联军持续南撤。</p><p>法国政府甚至在 9 月初撤离巴黎，迁往波尔多。</p><p>从表面上看，德国离战略胜利似乎只剩最后一步。</p><p>但对于一支规模以百万计的军队而言，<strong>持续向前推进本身就面临着极高的风险。</strong></p><section><h2>军队在推进，补给却寸步难行<a href="#军队在推进补给却寸步难行"><span>#</span></a></h2><p>德军右翼每天都要进行长距离徒步行军。</p><p>士兵不仅要战斗，还要背负装备，在夏末高温和尘土中持续前进。</p><p>越往法国腹地深入，部队距离补给能够到达的铁路线终点越远。</p><p>国境外的铁路早已遭到守军毁坏，需要修复；</p><p>物资从列车卸下后，还要依靠马车沿公路继续运往前线；</p><p>马匹会疲劳，需要饲料；</p><p>道路也会被军队、炮车、伤员和补给车队堵塞。</p><p>铁路能够快速把军队送到战争起点，但一旦离开完善的铁路网，百万大军通往前线“最后几十公里”的补给仍高度依赖马匹和公路。这形成了现代战争初期一个非常典型的矛盾：</p><p><strong>军队的大范围战略机动可以依靠铁路达到工业时代的速度，但真正向前推进时却仍主要依靠人的双腿和马匹。</strong></p><p>德军推进得越快，部队与后方补给之间的距离也就越容易被拉长。</p></section><section><h2>克鲁克的大转向<a href="#克鲁克的大转向"><span>#</span></a></h2><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Alexander-von-Kluck-%28ca.-1914%29--ff-be716e84c975.b9p24f5nc.webp" alt="约 1914 年的德国第一集团军司令亚历山大·冯·克鲁克" /><figcaption>约 1914 年的德国第一集团军司令亚历山大·冯·克鲁克</figcaption></figure><p></p><p>原本的德国右翼计划进行大范围的包抄。</p><p>然而，战场情况不断变化。</p><p>克鲁克的第一集团军连续追击英军和法军，在作战中逐渐向东南方向偏转。</p><p>德军没有从巴黎以西完成巨大包围，而是从巴黎以东通过了。</p><p>这样做并不能简单解释为克鲁克“擅自破坏施里芬计划”。</p><p>他面对的是实际战场的局势：</p><p>英法部队正在后退，而中央的第二集团军需要支援，德军希望在继续追击的同时呼应第二集团军。</p><p>这一策略从逻辑上可以说通，问题是，这一方向变化把第一集团军的右翼逐渐暴露给巴黎方向新集结的法军。</p><p>同时，第一集团军与卡尔·冯·比洛的第二集团军之间出现了越来越明显的空隙。</p><p>德军仍在继续向前推进，但已经不再像一堵整齐南下的墙。</p></section><section><h2>霞飞没有把撤退视为彻底失败<a href="#霞飞没有把撤退视为彻底失败"><span>#</span></a></h2><p>法军在边境战役中遭受惨重伤亡后，霞飞认识到德国右翼规模远超预期。</p><p>于是，他一边命令各集团军继续有秩序地后撤，一边从东部抽调部队，通过铁路向巴黎以东和塞纳河方向重新集结。</p><p>由此，形成了法国第六集团军。</p><p>借助本土铁路，法国能够比主要依靠徒步追击的德军更快地调动部分兵力，在巴黎附近重新形成局部优势。</p><hr /></section></section>
<section><h1>第一次马恩河战役：英法联军创造了奇迹，“施里芬计划”破产<a href="#第一次马恩河战役英法联军创造了奇迹施里芬计划破产"><span>#</span></a></h1><p>9 月初，德国第一、第二集团军已经深入法国。</p><p>巴黎方向的法国第六集团军则位于德军右翼侧面。</p><p>霞飞判断，反击的时机已经成熟。</p><p>9 月 5 日，法国第六集团军向乌尔克河方向的德军第一集团军右翼发动攻击。</p><p>克鲁克被迫调兵应对。这样一来，第一集团军与第二集团军之间原本就存在的间隙进一步扩大。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Positions-of-the-armies-before-the-Battle-of-the-Marne%2C-September-1914--ff-abd62ecfc09e.9kgz3ic933.webp" alt="1914 年 9 月第一次马恩河战役前夕德法英主要部队位置" /><figcaption>1914 年 9 月第一次马恩河战役前夕德法英主要部队位置</figcaption></figure><p></p><section><h2>英军和法军插入德国集团军之间的空隙<a href="#英军和法军插入德国集团军之间的空隙"><span>#</span></a></h2><p>此前持续后撤的英国远征军也开始重新前进。</p><p>法国第五集团军与英军向德军第一、第二集团军之间的空隙施压。</p><p>这使德国指挥层不得不面对一个极为危险的局面：</p><p>如果协约国部队真正穿过这一缺口，两大集团军之间的联系便会被切断，整个德国右翼将会因无法协调而崩溃。</p><p>但德军此时并没有在所有战场都遭遇溃败。</p><p>相反，局部战斗十分激烈，部分德军仍然有能力继续进攻。</p><p>马恩河战役的决定性意义在于：</p><p><strong>德国最高统帅部无法再确认右翼各集团军是否还能保持有效联系并继续推进。</strong></p><p>德国总参谋部派出的参谋军官理查德·亨奇前往各集团军了解情况。在与相关指挥官沟通后，德军最终决定后撤。</p><p>从 9 月 9 日前后开始，德国第一、第二集团军逐步向北撤退。</p></section><section><h2>“巴黎出租车”<a href="#巴黎出租车"><span>#</span></a></h2><p>马恩河战役中最著名的故事之一，是法军调动了巴黎的出租车把士兵运往前线。</p><p>巴黎军政长官加利埃尼确实征用了出租车，将数千名士兵运送到第六集团军方向。</p><p>出租车被征用，成为巴黎保卫战的象征。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Galli%C3%A9ni--ff-2f1de9c11428.6m4p003zim.webp" alt="1914 年的巴黎军政长官约瑟夫·加利埃尼" /><figcaption>1914 年的巴黎军政长官约瑟夫·加利埃尼</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Taxi-de-la-Marne%2C-Mus%C3%A9e-de-lArm%C3%A9e-IMG-0987--ff-5ec8be2c0bb9.5xbfg0qqw3.webp" alt="法国陆军博物馆收藏的雷诺 AG-1“马恩河出租车”" /><figcaption>法国陆军博物馆收藏的雷诺 AG-1“马恩河出租车”</figcaption></figure></div><p>这批运输相对于整个会战的兵力规模十分有限。法军反击依靠的是仍在运转的整体动员与交通体系。</p><div><div><div></div><div>Note</div></div><div><p><strong>马恩河以前的军用汽车</strong></p><p>1897 年，法国军演已有汽车参与。加利埃尼在马达加斯加约从 1900 年起使用汽车，1903 年又组织了汽车运输服务，早于他在巴黎征用出租车运兵的行动。</p></div></div><p>德军退到埃纳河一线后重整防御，仍有继续作战的能力。但德国失去了 1914 年迅速击败法国、再集中力量对付俄国的关键机会，必须同时在两条战线分配资源。</p><hr /></section></section>
<section><h1>“奔向大海”的战壕：双方永远无法抵达的“侧翼”<a href="#奔向大海的战壕双方永远无法抵达的侧翼"><span>#</span></a></h1><p>德军从马恩河撤退后，在埃纳河以北占据较有利的高地。</p><p>英法联军继续追击，希望趁德军撤退时重新打开机动作战空间。</p><p>但当协约国部队向德军阵地发动进攻时，很快发现：</p><p>即使只是经过短暂构筑的防御阵地，也很难通过正面冲击而突破。</p><p>步兵在战场制造数不清的掩体，配合重重铁丝网与壕沟，再配合机枪和野战炮，构成了几乎坚不可摧的防线。</p><p>于是，原本应在马恩河之后继续展开的追击，在埃纳河一线停了下来。</p><p>最初的浅壕也逐渐变成了稳定的阵地。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/German-trenches-on-the-aisne--ff-2763e10a0735.1ow865q7pv.webp" alt="一战初期埃纳河战线的德军战壕" /><figcaption>一战初期埃纳河战线的德军战壕</figcaption></figure><p></p><section><h2>通过”侧翼”展开进攻<a href="#通过侧翼展开进攻"><span>#</span></a></h2><p>所有指挥官都清楚，正面攻击坚固阵地代价很高。</p><p>双方于是试图从北侧迂回，避开正面防御。</p><p>法国开始把部队向北移动，试图攻击德军右翼。</p><p>德国发现后，也把部队向北调动，希望反过来包抄法国。</p><p>一方继续向北迂回，另一方便向更北方调兵封堵，战线不断延长。</p><p>这一系列行动后来被称为 <strong>“奔向大海”（Race to the Sea）</strong>。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Race-to-the-Sea-1914--ff-04a7a4a72055.102ym52osg.webp" alt="1914 年秋季“奔向大海”期间西线不断向北延伸的示意图" /><figcaption>1914 年秋季“奔向大海”期间西线不断向北延伸的示意图</figcaption></figure><p></p><p>行动的目标是绕到对方暴露的北翼，双方却始终未能完成决定性的包抄。</p></section><section><h2>佛兰德斯：无法再前往更北方<a href="#佛兰德斯无法再前往更北方"><span>#</span></a></h2><p>到了 10 月，战场逐渐延伸到法国北部与比利时佛兰德斯。</p><p>这里不仅有“突破侧翼”的价值，还是重要的海峡港口。</p><p>如果德国控制敦刻尔克、加来等港口，将严重威胁英国与欧洲大陆之间的交通。</p><p>比利时军队则在伊瑟河一线继续抵抗。</p><p>10 月下旬，为阻止德军推进，比利时方面打开水闸，使伊瑟河附近低地被淹。</p><p>洪水与既有防御共同限制了德军的进一步行动。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Yser-inundations-and-western-approaches-to-Houthoulst-Forest%2C-1914--ff-c7e534b3782b.92qxexavio.webp" alt="1914 年伊瑟河地区人工淹没区与西部战线示意图" /><figcaption>1914 年伊瑟河地区人工淹没区与西部战线示意图</figcaption></figure><p></p><p>与此同时，更南面的伊普尔地区爆发激烈战斗。</p><p>德军希望突破协约国战线、夺取通往海峡的道路；</p><p>英法则不断投入预备队阻挡。</p><p>第一次伊普尔战役造成极其惨重的伤亡。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/First-Battle-of-Ypres%2C-October-November-1914%2C-Q-57283--ff-2b232068e31c.4clogij91o.webp" alt="1914 年 10 月，伊普尔布料厅前被英国远征军征用的民用卡车" /><figcaption>1914 年 10 月，伊普尔布料厅前被英国远征军征用的民用卡车</figcaption></figure><p></p><p>到 11 月，双方都已经很难再组织一次大规模机动作战。</p><p>从北海到瑞士边境，战线基本连接起来。</p><p>可供大规模迂回的“开放侧翼”消失了。</p><hr /></section></section>
<section><h1>工业化没有让战争”更快结束”，反而使战争“更难结束”<a href="#工业化没有让战争更快结束反而使战争更难结束"><span>#</span></a></h1><p>到了 1914 年冬天，欧洲军队面对着一个前所未有问题：</p><p><strong>怎样突破一条由大量步兵、机枪、野战炮、铁丝网和纵深交通线共同支撑的连续防线？</strong></p><p>这并不是说战前没有人意识到现代武器的威力。</p><p>日俄战争等冲突早已展示过机枪、速射炮和野战工事的杀伤能力。</p><p>一些军事思想家也曾预见未来大战可能漫长而惨烈。</p><p>英国战争大臣基奇纳甚至在战争刚开始时就判断，这场冲突可能持续数年，需要英国建立规模远大于原有远征军的新军。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/Herbert-Kitchener-1914--ff-f3c276937382.4ubq53kmoq.webp" alt="1914 年的英国战争大臣赫伯特·基奇纳" /><figcaption>1914 年的英国战争大臣赫伯特·基奇纳</figcaption></figure><p></p><p>德国军界内部同样有人怀疑“短期战争”能否实现。</p><p>因此，后来流行的“所有人都以为圣诞节前就能回家”更像是一种概括性的战争记忆，而不是所有政府和军队共享的精确判断。</p><p>长期战争的风险已有预见，各国却仍有强烈动机争取快速决胜。</p><section><h3>长期战争的经济负担<a href="#长期战争的经济负担"><span>#</span></a></h3><p>首先，没有哪个大陆强国真正愿意主动选择多年消耗。</p><p>现代大众军队需要几百万劳动人口离开农业、工厂和家庭，后方则须持续生产炮弹、枪械、铁路设备与军服。贸易、金融和粮食供应都会受到冲击，国家财政也要承担空前支出。</p><p>所以，各国即使知道长期战争“可能发生”，也有强烈动机避免让战争进入那个阶段。</p><p>速决设想也承载着避开这些经济与社会压力的愿望。</p></section><section><h3>战争目标仍要求进攻<a href="#战争目标仍要求进攻"><span>#</span></a></h3><p>防御可以阻止敌人前进，但单纯防御无法迫使对方投降。</p><p>如果战争的目标仍然是摧毁敌军、占领关键地区并迫使敌国接受条件，那么总有一方需要进攻。</p><p>于是，进攻战略并没有因为会导致惨重伤亡而消失。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260925-WWI-2-schlieffen-to-marne/_A-French-75-in-Action._--ff-ee084a2e2c18.2yz5ciiheg.webp" alt="第一次世界大战时期投入作战的法国 75 毫米野战炮" /><figcaption>第一次世界大战时期投入作战的法国 75 毫米野战炮</figcaption></figure><p></p><p>相反，在接下来的四年里，各国不断寻找新的方法突破防线：</p><p>加强炮击、使用毒气与爆破、投入坦克、发展步兵渗透战术，并改进步兵、炮兵和坦克之间的协同。</p><p>战壕战不是战争停止了，它是战争进入另一种更加工业化形态的标志。</p></section><section><h2>铁路与后备力量支撑了持续作战<a href="#铁路与后备力量支撑了持续作战"><span>#</span></a></h2><p>防御者可以借助后方仍在运转的铁路，将预备队和补给送到受威胁地段；进攻者则必须穿过战斗破坏的区域继续推进。双方调兵速度的差异，使局部突破更难扩大为决定性胜利。“奔向大海”中反复出现的封堵，就依赖这种能力。</p><p>1914 年的欧洲大国还能够动员前所未有的人力，以预备役人员、征召兵和新生产的武器弹药补充前线。一个集团军战败、数十万人伤亡，并不意味着整个国家随即失去战斗力；只要后方组织与补充能力仍在运转，一次会战失利就未必足以迫使其退出战争。</p><hr /></section><section><h2>没有任何一方愿意在 1914 年秋天便接受失败<a href="#没有任何一方愿意在-1914-年秋天便接受失败"><span>#</span></a></h2><p>德国虽然没有击败法国，却仍占领比利时和法国北部大片地区，也没有被打回本土；</p><p>法国保住了巴黎，更不可能在这时接受德国继续控制东北领土；</p><p>英国刚刚开始扩大军队规模；</p><p>俄罗斯虽然在东普鲁士遭遇失败，却仍能对奥匈和德国制造巨大压力；</p><p>奥匈帝国也没有能够解决塞尔维亚和俄罗斯问题。</p><p>对当时的主要参战国来说，战争还没有彻底输掉。</p><p>而只要各国仍相信继续作战可能改善自己的谈判地位，就没有足够理由接受妥协。</p><p>于是，军事僵局与政治目标开始互相强化。</p><p><strong>前线无法取得胜利，因此政府不断投入更多资源；而投入越多，政府就越难接受一种无法证明此前牺牲“值得”的和平。</strong></p></section></section>
<section><h1>速战计划并不是在马恩河战场突然终结的<a href="#速战计划并不是在马恩河战场突然终结的"><span>#</span></a></h1><p>马恩河经常被称为施里芬计划破产的标志。</p><p>但西线真正从速决战进入长期战争，并不是 9 月某一天的战果使然。</p><p>这个过程经历了几个连续阶段：</p>

<table><thead><tr><th>阶段</th><th>主要变化</th><th>对速决战的影响</th></tr></thead><tbody><tr><td>比利时抵抗</td><td>要塞、交通破坏与持续抵抗增加了德军的时间和后勤消耗</td><td>原定推进节奏开始承受额外压力</td></tr><tr><td>边境战役</td><td>双方进攻都付出远超预期的伤亡</td><td>“通过进攻迅速决胜”变得更难实现</td></tr><tr><td>英法大撤退</td><td>法英军队后撤，却没有被包围消灭或彻底瓦解</td><td>德军的战术胜利没有转化为战略性歼灭</td></tr><tr><td>东普鲁士战事</td><td>俄军比德国预期更早形成有效压力</td><td>东方战线无法长期等待，德国两线作战困境提前出现</td></tr><tr><td>第一次马恩河战役</td><td>德军右翼后撤，未能迅速击败法国</td><td>德国失去 1914 年通过西线速胜结束战争的关键机会</td></tr><tr><td>埃纳河战斗</td><td>德军依托高地和临时工事阻止协约国追击</td><td>正面突破经过构筑的防御阵地变得极为困难</td></tr><tr><td>“奔向大海”</td><td>双方不断向北延长战线并封堵彼此侧翼</td><td>可供大规模绕行的开放侧翼最终消失</td></tr></tbody></table><p>到 1914 年冬天，现代火力与连续阵地阻碍了推进，铁路和国家动员又使双方能够不断补充损失。各国仍有能力继续战斗，却都难以取得足以结束战争的胜利。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考第一次世界大战战前军事计划、1914 年西线与东线战役研究，以及相关公共历史与地图资料。文章重点放在“速决战为何在 1914 年秋天转化为长期消耗战”，因此参考资料也优先选择能够解释计划、组织、交通和战场适应过程的研究。</p><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>Hew Strachan — <em>The First World War, Volume I: To Arms</em></strong><br />
系统讨论第一次世界大战爆发、各国动员、1914 年初期战役与战争由机动作战转向长期战争的过程，是理解战争最初阶段的重要综合研究。</p>
</li>
<li>
<p><strong>Terence Zuber — <em>Inventing the Schlieffen Plan: German War Planning 1871–1914</em></strong><br />
从德国总参谋部战争规划史出发，对传统意义上的“施里芬计划”提出修正性解释。相关观点在学界存在争议，但对于理解“施里芬计划”并非一份简单固定蓝图具有重要参考价值。</p>
</li>
<li>
<p><strong>Robert T. Foley — <em>German Strategy and the Path to Verdun: Erich von Falkenhayn and the Development of Attrition, 1870–1916</em></strong><br />
讨论德国战略思想如何从战前速决设想逐渐面对长期战争现实，有助于理解 1914 年计划失败以后德国战略思维的变化。</p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Pre-war Military Planning (Germany)</strong><br />
介绍施里芬、毛奇时期德国两线战争规划、短期战争设想以及德国军界内部对战争持续时间的不同判断。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/pre-war-military-planning-germany/" target="_blank">https://encyclopedia.1914-1918-online.net/article/pre-war-military-planning-germany/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Pre-war Military Planning (France)</strong><br />
介绍普法战争后法国军事思想、第十七号计划、进攻观念和 1914 年法国战争准备。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/pre-war-military-planning-france/" target="_blank">https://encyclopedia.1914-1918-online.net/article/pre-war-military-planning-france/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Western Front</strong><br />
梳理 1914 年德国西进、第一次马恩河战役、“奔向大海”以及西线连续战线形成的过程。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/western-front/" target="_blank">https://encyclopedia.1914-1918-online.net/article/western-front/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Warfare 1914–1918</strong><br />
从战术、作战与组织角度解释现代火力、铁路、指挥通信和大规模军队如何共同塑造一战战场，并讨论埃纳河以后战壕战形成的原因。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/warfare_1914-1918/" target="_blank">https://encyclopedia.1914-1918-online.net/article/warfare_1914-1918/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Mons</strong><br />
介绍 1914 年 8 月 23—24 日蒙斯战役、英国远征军与德国第一集团军的交战，以及随后“大撤退”的过程。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/mons/" target="_blank">https://encyclopedia.1914-1918-online.net/article/mons/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Tannenberg, Battle of</strong><br />
介绍俄军提前进入东普鲁士、德国第八集团军的机动、坦能堡包围战及其在德国战争记忆中的象征意义。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/tannenberg-battle-of-1-1/" target="_blank">https://encyclopedia.1914-1918-online.net/article/tannenberg-battle-of-1-1/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Kitchener, Horatio Herbert Kitchener, Earl</strong><br />
用于说明并非所有军政人物都相信战争能够迅速结束。基奇纳在 1914 年已经预期战争可能持续多年，并立即推动扩建英国陆军。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/kitchener-horatio-herbert-kitchener-earl/" target="_blank">https://encyclopedia.1914-1918-online.net/article/kitchener-horatio-herbert-kitchener-earl/</a></p>
</li>
<li>
<p><strong>Encyclopaedia Britannica — First Battle of the Marne / Battle of Tannenberg / World War I</strong><br />
用于核对 1914 年主要战役时间、参战力量与总体战局。<br />
<a href="https://www.britannica.com/event/First-Battle-of-the-Marne" target="_blank">https://www.britannica.com/event/First-Battle-of-the-Marne</a><br />
<a href="https://www.britannica.com/event/Battle-of-Tannenberg-World-War-I-1914" target="_blank">https://www.britannica.com/event/Battle-of-Tannenberg-World-War-I-1914</a><br />
<a href="https://www.britannica.com/event/World-War-I" target="_blank">https://www.britannica.com/event/World-War-I</a></p>
</li>
<li>
<p><strong>National Army Museum — British Army organisation</strong><br />
关于师、军与集团军的编制层级。<br />
<a href="https://www.nam.ac.uk/explore/army-organisation" target="_blank">British Army organisation</a></p>
</li>
<li>
<p><strong>Deutsches Historisches Museum — Tannenberg</strong><br />
关于 1914 年坦能堡胜利与 1410 年条顿骑士团败绩之间的宣传联系。<br />
<a href="https://www.dhm.de/archiv/ausstellungen/der-erste-weltkrieg/rooms/tannenberg.htm" target="_blank">Tannenberg 展览说明</a></p>
</li>
<li>
<p><strong>National Army Museum — The Commonwealth and the First World War</strong>
介绍英军正规军、地方部队与后备役的构成、战时志愿新军的训练，以及 1916 年引入征兵制的背景。
<a href="https://www.nam.ac.uk/explore/commonwealth-and-first-world-war" target="_blank">英国国家陆军博物馆：一战中的英国及帝国军队</a></p>
</li>
<li>
<p><strong>Jean Orselli / 法国环境与可持续发展总委员会 — Usages et usagers de la route, mobilité et accidents, 1860–2008，第一卷（2009）</strong><br />
第 371 页介绍汽车在法国军演、马达加斯加及布尔战争中的早期军事用途，并说明加利埃尼在马达加斯加组织汽车运输的经历。<br />
<a href="https://igedd.documentation.developpement-durable.gouv.fr/documents/Affaires-0004395/2005-0457-01_rapport.pdf#page=374" target="_blank">法国政府交通史研究，第 371 页</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>1914 年 8 月 3 日，巴黎东站准备出发的法国动员士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mobilisation_08-1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。照片记录法国总动员期间巴黎东站的士兵出发场景，原始资料来自法国国家图书馆 Gallica；Wikimedia Commons 页面标记原作为公有领域。</p>
</li>
<li>
<p><strong>1914 年德国施里芬—毛奇作战构想与法国第十七号计划示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Schlieffen_Plan.svg" target="_blank">Wikimedia Commons 原图页</a>。原始图由 Tinodela 制作，Tazadeperla 将其矢量化；Commons 页面说明其源图来自美国陆军资料，并依据美国联邦政府作品规则标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年的德国总参谋长赫尔穆特·冯·毛奇</strong>：<a href="https://commons.wikimedia.org/wiki/File:Helmuth_Johannes_Ludwig_von_Moltke_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。Agence Rol 摄于 1914 年，资料来自法国国家图书馆；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>一战初期的法国陆军总司令约瑟夫·霞飞</strong>：<a href="https://commons.wikimedia.org/wiki/File:Joseph_Joffre.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者不详，照片在一战时期已广泛出版；Commons 页面依据匿名旧作规则标记为公有领域。</p>
</li>
<li>
<p><strong>约 1914 年的比利时国王阿尔贝一世</strong>：<a href="https://commons.wikimedia.org/wiki/File:Albert_I.,_King_of_the_Belgians,_c._1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者未署名，原载于 1914 年出版物 <em>The War in Europe, its causes and consequences</em>；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年列日附近阻断道路、准备抵抗德军推进的比利时军队</strong>：<a href="https://commons.wikimedia.org/wiki/File:Belgian_troops_block_a_road_in_Li%C3%A8ge_-_L%C3%BCttich_against_the_German_advance_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。原载于 1919 年出版的 <em>Der Krieg 1914–19 in Wort und Bild</em>，作者未署名；Wikimedia Commons 页面依据作品年代标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年 8 月被比利时守军炸毁后的列日桥梁</strong>：<a href="https://commons.wikimedia.org/wiki/File:Li%C3%A8ge,_le_pont_des_Arches_d%C3%A9truit_par_les_Belges_%C3%A0_l%27approche_des_Allemands_-_btv1b6931567w.jpg" target="_blank">Wikimedia Commons 原图页</a>。资料来自法国国家图书馆数字馆藏；具体作者与版权状态以 Commons 文件页当前说明为准。</p>
</li>
<li>
<p><strong>1914 年逃离战区的比利时难民</strong>：<a href="https://commons.wikimedia.org/wiki/File:Belgian_refugees_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者不详，照片摄于 1914 年；Commons 页面依据匿名作品版权期限届满标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年 8 月边境战役主要战线与进军方向示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Battle_of_Frontiers_-_Map.jpg" target="_blank">Wikimedia Commons 原图页</a>。地图出自美国西点军校历史系战役地图资料；作为美国联邦政府作品，在美国属于公有领域。</p>
</li>
<li>
<p><strong>1914 年 8 月 23 日蒙斯战役兵力与战线示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Battle_of_Mons.jpg" target="_blank">Wikimedia Commons 原图页</a>。图源所列作者为亚历山大·冯·克鲁克，数字化版本来自多伦多大学 Robarts Library 馆藏；Wikimedia Commons 页面标记为美国公有领域。</p>
</li>
<li>
<p><strong>1914 年 8 月，从蒙斯撤退的英国远征军骑兵与马匹</strong>：<a href="https://commons.wikimedia.org/wiki/File:Retreat_from_Mons.jpg" target="_blank">Wikimedia Commons 原图页</a>。Paul Maze 摄，馆藏于帝国战争博物馆（IWM，Q 60695）；Commons 页面依据英国政府作品规则标记为公有领域。</p>
</li>
<li>
<p><strong>保罗·冯·兴登堡与埃里希·鲁登道夫</strong>：<a href="https://commons.wikimedia.org/wiki/File:Hindenburg_and_Ludendorff.jpg" target="_blank">Wikimedia Commons 原图页</a>。Bain News Service 摄，资料来自美国国会图书馆 George Grantham Bain Collection；馆方标注无已知版权限制，Commons 标记为公有领域。照片约摄于 1918 年，用于展示两位后来长期合作的德国将领形象。</p>
</li>
<li>
<p><strong>1914 年 8 月坦能堡战役德军机动与包围计划示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Tannenberg-tysk-plan.jpg" target="_blank">Wikimedia Commons 原图页</a>。制图者 Lars Helbo；作者将该图释放至公有领域。</p>
</li>
<li>
<p><strong>约 1914 年的德国第一集团军司令亚历山大·冯·克鲁克</strong>：<a href="https://commons.wikimedia.org/wiki/File:Alexander_von_Kluck_(ca._1914).jpg" target="_blank">Wikimedia Commons 原图页</a>。Mayer（Dresden）摄影，约摄于 1914 年，后作为明信片出版；Commons 页面标记为美国公有领域，并提示部分司法辖区的版权状态可能不同。</p>
</li>
<li>
<p><strong>1914 年 9 月第一次马恩河战役前夕德法英主要部队位置</strong>：<a href="https://commons.wikimedia.org/wiki/File:Positions_of_the_armies_before_the_Battle_of_the_Marne,_September_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。原图作者 George Herbert Perris（1866—1920），来自多伦多大学数字化馆藏；Wikimedia Commons 页面标记为美国公有领域。</p>
</li>
<li>
<p><strong>1914 年的巴黎军政长官约瑟夫·加利埃尼</strong>：<a href="https://commons.wikimedia.org/wiki/File:Galli%C3%A9ni.JPG" target="_blank">Wikimedia Commons 原图页</a>。作者不详，原资料标注为 1914 年的 <em>Géographia</em>；Commons 页面依据旧作版权期限标记为公有领域。</p>
</li>
<li>
<p><strong>法国陆军博物馆收藏的雷诺 AG-1“马恩河出租车”</strong>：<a href="https://commons.wikimedia.org/wiki/File:Taxi_de_la_Marne,_Mus%C3%A9e_de_l%27Arm%C3%A9e-IMG_0987.jpg" target="_blank">Wikimedia Commons 原图页</a>。Rama 于法国陆军博物馆拍摄，展示 1914 年马恩河战役相关的雷诺 AG-1 出租车实物；以 <a href="https://creativecommons.org/licenses/by-sa/2.0/fr/deed.en" target="_blank">CC BY-SA 2.0 France</a> 等自由许可证发布。</p>
</li>
<li>
<p><strong>一战初期埃纳河战线的德军战壕</strong>：<a href="https://commons.wikimedia.org/wiki/File:German_trenches_on_the_aisne.jpg" target="_blank">Wikimedia Commons 原图页</a>。Bain News Service 发布，馆藏于美国国会图书馆；Commons 页面依据馆方“无已知版权限制”说明标记为公有领域，年代可能为 1914—1915 年。</p>
</li>
<li>
<p><strong>1914 年秋季“奔向大海”期间西线不断向北延伸的示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Race_to_the_Sea_1914.png" target="_blank">Wikimedia Commons 原图页</a>。Gsl 根据 T. Dodson Stamps 与 Vincent J. Esposito 编纂的 <em>A Short Military History of World War I – Atlas</em> 制作；作者已将该文件释放至公有领域。</p>
</li>
<li>
<p><strong>1914 年伊瑟河地区人工淹没区与西部战线示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Yser_inundations_and_western_approaches_to_Houthoulst_Forest,_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。原图来自一战时期出版资料 <em>The Times History of the War</em>；Commons 页面标记其在美国为公有领域。</p>
</li>
<li>
<p><strong>1914 年 10 月，伊普尔布料厅前被英国远征军征用的民用卡车</strong>：<a href="https://commons.wikimedia.org/wiki/File:First_Battle_of_Ypres,_October-November_1914,_Q_57283.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者未署名，馆藏于帝国战争博物馆（IWM，Q 57283）；Commons 页面依据英国政府作品规则标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年的英国战争大臣赫伯特·基奇纳</strong>：<a href="https://commons.wikimedia.org/wiki/File:Herbert_Kitchener_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。Harris &amp; Ewing 摄于 1914 年，资料来自美国国会图书馆；馆方标注无已知版权限制，Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>第一次世界大战时期投入作战的法国 75 毫米野战炮</strong>：<a href="https://commons.wikimedia.org/wiki/File:%22A_French_%2775%27_in_Action.%22.jpg" target="_blank">Wikimedia Commons 原图页</a>。图片来自 Missouri Historical Society 收藏的第一次世界大战立体照片资料；馆方允许免费使用，Commons 页面将原作标记为公有领域。</p>
</li>
</ol><div><div><div></div><div>Note</div></div><div><p>Wikimedia Commons 上的历史图片可能来自不同国家、不同年代与不同馆藏，其版权状态并不完全一致。本文在配图来源中按照文件页当前标注说明作者、馆藏与授权信息；若用于转载、商业发布或二次创作，仍应以对应 Commons 文件页及原馆藏机构的最新授权说明为准。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/history/wwi/260924-wwi-1-from-sarajevo-to-the-wwi/</id>
      <title type="text">WWI-1 火药桶上的欧洲：一场危机如何演变成世界大战</title>
      <published>2026-09-24T00:00:00.000Z</published>
      <updated>2026-09-24T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/history/wwi/260924-wwi-1-from-sarajevo-to-the-wwi/"/>
      <summary type="text">一场发生在萨拉热窝的刺杀，为什么会在短短数周内演变成席卷欧洲的大战？从联盟体系、巴尔干民族问题与军备竞赛，到七月危机中的最后通牒、动员与宣战，重新梳理第一次世界大战爆发前那条不断收紧的锁链。</summary>
      <content type="html"><![CDATA[
<p>1914 年 6 月 28 日，奥匈帝国皇储弗朗茨·斐迪南大公与妻子索菲在波斯尼亚首府萨拉热窝遇刺。</p>
<p>刺客加夫里洛·普林西普只开了两枪。</p>
<p>37 天后，德国分别向俄罗斯、法国宣战，德军越过比利时边境，英国也向德国宣战。一起发生在巴尔干边缘的政治刺杀，最终引爆了欧洲主要大国之间的全面战争。</p>
<p>同盟关系并不意味着一国宣战、盟国就自动加入。三国同盟和三国协约性质不同，英国也没有因法俄参战而立即参战的义务。此前的摩洛哥危机、波斯尼亚危机和巴尔干战争，同样牵涉大国与军事威胁，却没有演变成欧洲全面战争。</p>
<p>1914 年的危机越过了这道界限，与此前几十年欧洲力量格局的变化密切相关。</p>
<hr />
<section><h1>逐渐失去战略缓冲的欧洲大陆<a href="#逐渐失去战略缓冲的欧洲大陆"><span>#</span></a></h1><p>19 世纪后半叶到 20 世纪初，欧洲一方面处在工业化、全球贸易和科学技术快速发展的时代，另一方面也在经历一场缓慢而深刻的力量重组。</p><p>其中最重要的变化之一，是 <strong>1871 年德意志帝国的建立</strong>。</p><p>德国统一以前，中欧由多个邦国组成。统一之后，一个人口众多、工业发展迅速、拥有强大陆军的国家突然出现在欧洲大陆中心。它西面是法国，东面是俄罗斯，南面是奥匈帝国，北面则通过北海面对英国。</p><p>德国统一改变了欧洲原有的力量平衡。如果法国与俄罗斯同时成为敌人，德国就可能面临两线作战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Portrait-of-Otto-von-Bismarck-%28by-Franz-Seraph-von-Lenbach%2C-1890%29--ff-87b249e55bf4.70b4qtiq0j.webp" alt="1890 年奥托·冯·俾斯麦肖像" /><figcaption>1890 年奥托·冯·俾斯麦肖像</figcaption></figure><p></p><p>俾斯麦执政时期，德国外交的一个重要目标，就是避免法国找到强大的大陆盟友。他试图维持与奥匈、俄国之间复杂而灵活的关系，让德国尽量处在欧洲外交体系的中心。</p><p>然而，到 19 世纪末，这种复杂的平衡开始松动。</p><section><h2>欧洲的两大战略集团<a href="#欧洲的两大战略集团"><span>#</span></a></h2><p>三国同盟形成于 19 世纪后期的俾斯麦外交体系；三国协约则由法、俄、英在十几年间分别改善双边关系，逐渐形成。两者的建立过程与约束力并不相同。</p><section><h3>德奥同盟：三国同盟最坚固的核心<a href="#德奥同盟三国同盟最坚固的核心"><span>#</span></a></h3><p>1871 年德国统一以后，俾斯麦最担心的问题之一，就是法国重新找到强大的盟友。</p><p>普法战争使法国失去了阿尔萨斯—洛林地区，也使法国长期对德国心存不满。如果德国与俄罗斯关系恶化，那么法国就可能亲近俄罗斯，从外交孤立中摆脱出来。</p><p>俾斯麦最初试图同时维持德国、奥匈帝国和俄罗斯之间的合作。1873 年，德国、奥匈帝国与俄罗斯建立“三皇同盟”，试图通过三个君主制帝国之间的协调维持欧洲大陆稳定。</p><p>但随着奥斯曼帝国不断衰落，俄罗斯希望扩大自己在巴尔干地区的影响，而奥匈帝国同样担心俄罗斯势力向东南欧扩张。</p><p>1877—1878 年俄土战争以后，俄罗斯迫使奥斯曼帝国签订《圣斯特凡诺条约》，一个领土庞大、受俄罗斯影响明显的保加利亚一度出现。</p><p>这立即引起奥匈帝国和英国的警惕。随后召开的柏林会议重新调整了对巴尔干地区的安排，削减了俄罗斯此前取得的部分成果。俄罗斯国内不少人因此认为，德国并没有在关键时刻维护俄国利益，德俄关系明显恶化。</p><p>在这种背景下，俾斯麦开始准备应对最坏的情况。</p><p>1879 年，德国与奥匈帝国签订 <strong>德奥同盟，也称“二国同盟”</strong>。</p><p>条约规定，如果其中一国遭到俄罗斯攻击，另一国将提供军事援助；如果遭到其他欧洲国家攻击，盟友至少应当保持中立。</p><p>这项防御性安排确立了此后几十年德国外交中最稳定的一组大国关系，成为后来三国同盟的核心。</p><div><div><div></div><div>Note</div></div><div><p>德奥同盟并不意味着俾斯麦已经彻底放弃与俄罗斯保持关系。1881 年德国、奥匈和俄罗斯一度恢复“三皇同盟”，1887 年德国又与俄罗斯签订《再保险条约》。俾斯麦真正想做的，是在德奥关系提供安全保障的同时，继续避免俄罗斯彻底倒向法国。</p></div></div></section><section><h3>意大利加入德国和奥匈<a href="#意大利加入德国和奥匈"><span>#</span></a></h3><p>1882 年，意大利加入德奥同盟，三国同盟由此形成。</p><p>意大利与奥匈帝国之间却仍有领土矛盾。19 世纪意大利统一以后，仍有部分意大利民族主义者认为特伦蒂诺、的里雅斯特等奥匈控制地区属于“未收复领土”。</p><p>促使意大利靠近德奥的一个重要原因，是它与法国之间的关系恶化。</p><p>意大利希望扩大自己在北非的影响，尤其关注突尼斯。但法国在 1881 年出兵并建立对突尼斯的保护统治，这让意大利遭受严重外交挫折。</p><p>面对法国压力，意大利希望找到能够改善自身战略位置的伙伴，于是在次年加入德国和奥匈帝国的联盟体系。</p><p>德国与奥匈的联盟关系最牢固，意大利则更多出于对法国的安全和殖民竞争考虑而加入。1914 年，意大利政府认为奥匈对塞尔维亚的行动不适用于条约规定的防御义务，因此没有随德奥参战。</p><p>后来，意大利甚至在 1915 年转而加入协约国一方作战。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Caricature-of-the-Triple-alliance--ff-ccf435d3d519.lwiv80ti4.webp" alt="19 世纪末至 20 世纪初法国讽刺画中的三国同盟" /><figcaption>19 世纪末至 20 世纪初法国讽刺画中的三国同盟</figcaption></figure><p></p></section><section><h3>法国如何打破外交孤立？<a href="#法国如何打破外交孤立"><span>#</span></a></h3><p>法国要摆脱普法战争后的外交孤立，需要找到一个能够牵制德国的欧洲盟友。俄奥两国在巴尔干的矛盾为此提供了机会，德国在俾斯麦下台以后也开始改变对俄政策。</p><p>1890 年，德国没有续签与俄罗斯之间的《再保险条约》。《再保险条约》原本有助于避免德俄直接对立。条约失效削弱了两国关系的缓冲，法国随即抓住机会推动对俄合作。</p><p>两国虽然政治制度差异巨大——法国是共和国，俄罗斯仍是沙皇专制帝国——却拥有越来越明显的共同战略利益：</p><p><strong>法国需要一个能够从东面牵制德国的大国；俄罗斯则需要一个能够在面对德国和奥匈时提供支持的欧洲伙伴。</strong></p><p>与此同时，法国资本大量进入俄罗斯，为俄国铁路建设和工业化提供融资，也进一步加强了两国联系。</p><p>1891—1894 年间，法俄关系逐步从外交接近转变为军事安排，并最终形成正式的 <strong>法俄同盟</strong>。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Voyage-du-tsar-%28Nicolas-II-et-la-tsarine-Alexandra-Feodorovna-en-pr%C3%A9sence-de-F%C3%A9lix-Faure-posant-la-premi%C3%A8re-pierr--ff-4000ec99ec3f.3nsewg25ox.webp" alt="1896 年尼古拉二世访法期间，俄国皇室与法国总统菲利·福尔共同出席亚历山大三世桥奠基仪式" /><figcaption>1896 年尼古拉二世访法期间，俄国皇室与法国总统菲利·福尔共同出席亚历山大三世桥奠基仪式</figcaption></figure><p></p><p>法国与俄罗斯分别位于德国西、东两侧。两国结盟，使德国必须认真考虑同时与两国作战的风险。</p></section><section><h3>英国为什么开始接近法国？<a href="#英国为什么开始接近法国"><span>#</span></a></h3><p>但此时英国仍然没有加入任何与德奥相对立的“三国集团”。</p><p>19 世纪的大部分时间里，英国往往避免长期卷入欧洲大陆固定联盟，同时依靠皇家海军、海外帝国和灵活外交维护自身利益。</p><p>英国与法国甚至长期存在大量殖民矛盾。双方曾在埃及、西非、纽芬兰等地区发生争端，1898 年的法绍达危机甚至一度让英法关系高度紧张。</p><p>到 20 世纪初，英法双方都开始寻求解决殖民争端，以改善彼此关系。</p><p>1904 年，英国与法国签署后来被称为 <strong>《英法协约》</strong> 的一系列协议。</p><p>它首先解决的是具体的殖民问题，而不是建立军事同盟。其中最重要的安排之一，是法国接受英国在埃及的特殊地位，而英国则承认法国在摩洛哥拥有特殊利益；此外，双方还解决了纽芬兰、西非等一系列长期争端。</p><p>主要殖民争端得到缓和以后，英法在欧洲安全问题上的合作空间随之扩大，外交与军事交流也越来越频繁。</p><p>特别是在此后的摩洛哥危机中，德国试图挑战法国在摩洛哥的地位，反而进一步推动英法靠近。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Entente-Cordiale-dancing--ff-ee34bd91195d.9o0l16br9m.webp" alt="1903 年表现英法友好关系的“英法协约”主题明信片" /><figcaption>1903 年表现英法友好关系的“英法协约”主题明信片</figcaption></figure><p></p></section><section><h3>英国和俄罗斯为什么也能和解？<a href="#英国和俄罗斯为什么也能和解"><span>#</span></a></h3><p>即使英法关系改善，欧洲仍然没有形成完整的“三国协约”。</p><p>因为英国和俄罗斯长期以来同样是竞争对手。19 世纪，两国围绕中亚、阿富汗、波斯以及通往印度的战略通道进行了长期竞争，后来通常被称为 <strong>“大博弈”</strong>。</p><p>在英国战略思维中，俄罗斯向中亚不断推进，一度被视为对英属印度安全的潜在威胁。</p><p>因此，如果英国与俄罗斯仍然彼此敌视，那么法国虽然同时与两国保持良好关系，却很难把三国真正连接起来。</p><p>这种局面在 20 世纪初开始改变。</p><p>1904—1905 年的日俄战争削弱了俄罗斯在远东的力量，而英国与日本已经建立同盟。</p><p>与此同时，英国越来越难以忽视欧洲方向的环境，尤其是德国海军扩张带来的压力。</p><p>对于俄罗斯而言，在远东遭受挫折以后，减少与英国在亚洲的竞争也具有现实价值。</p><p>于是，1907 年英国与俄罗斯签订 <strong>《英俄协约》</strong>。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/The-Russo-British-Pact-in-1907--ff-0a25bcf292c5.13mkjt2722.webp" alt="1907 年英俄协约划分波斯势力范围示意图" /><figcaption>1907 年英俄协约划分波斯势力范围示意图</figcaption></figure><p></p><p>双方围绕波斯、阿富汗和西藏等问题划定或确认各自利益范围，以减少长期以来在亚洲发生直接冲突的可能。</p></section><section><h3>“三国协约”其实不是一份三国盟约<a href="#三国协约其实不是一份三国盟约"><span>#</span></a></h3><p><strong>并不存在一份 1907 年由英国、法国和俄罗斯共同签署、名称就叫《三国协约》的统一军事盟约。</strong></p><p>所谓“三国协约”，实际上是三组关系逐渐叠加的结果：</p><p><strong>1890 年代的法俄同盟<br />
→ 1904 年英法协约<br />
→ 1907 年英俄协约<br />
→ 三国之间逐渐形成战略协调。</strong></p><p>因此，它和 1882 年通过正式条约建立的三国同盟并不完全对称。</p><p>法国和俄罗斯之间存在明确的军事同盟关系，而英国对法国和俄罗斯承担的义务要模糊得多。</p><p>英国虽然随后与法国展开越来越深入的军事和海军磋商，但直到 1914 年七月危机爆发，英国政府内部仍然存在一个非常现实的问题：</p><p><strong>如果法国和德国爆发战争，英国究竟是否必须参战？</strong></p></section></section><section><h2>两大集团的最终“成形”<a href="#两大集团的最终成形"><span>#</span></a></h2><p>到 20 世纪初，欧洲主要大国形成了两组战略联系：</p>

<table><thead><tr><th>集团</th><th>主要国家</th><th>实际关系</th></tr></thead><tbody><tr><td>三国同盟</td><td>德国、奥匈帝国、意大利</td><td>德奥关系最紧密；意大利与奥匈存在领土矛盾，联盟并不牢固</td></tr><tr><td>三国协约</td><td>法国、俄罗斯、英国</td><td>法俄是正式军事同盟；英国与法俄更多依靠协约、谅解和逐步加强的战略协调</td></tr></tbody></table><p>同盟体系扩大了地区危机的影响。两个国家发生冲突时，其他大国也会盘算：</p><ul>
<li>如果我不支持盟友，会不会影响我的信誉？</li>
<li>如果盟友被削弱，我以后会不会陷入孤立？</li>
<li>如果对手的盟友介入，我是否必须提前行动？</li>
<li>如果现在退让，下一场危机中会不会更不利？</li>
</ul><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Map-Europe-alliances-1914-en--ff-495122196cd5.45igl13j83.svg" alt="1914 年欧洲主要国家及联盟关系分布示意图" /><figcaption>1914 年欧洲主要国家及联盟关系分布示意图</figcaption></figure><p></p><hr /></section></section>
<section><h1>火星四溅的巴尔干：欧洲的“火药桶”<a href="#火星四溅的巴尔干欧洲的火药桶"><span>#</span></a></h1><p>欧洲的联盟体系只是为危机提供了扩散渠道，而巴尔干半岛正是欧洲最容易产生危机的地方。</p><p>19 世纪末到 20 世纪初，奥斯曼帝国不断衰落，巴尔干民族国家加速扩张，大国也在争夺由此出现的权力真空。</p><section><h2>奥斯曼帝国衰落以后，地图并没有稳定下来<a href="#奥斯曼帝国衰落以后地图并没有稳定下来"><span>#</span></a></h2><p>奥斯曼帝国曾经控制巴尔干大片地区。但从 19 世纪开始，它在欧洲的统治不断收缩。希腊、塞尔维亚、罗马尼亚、保加利亚等国家先后取得独立或高度自治地位。</p><p>表面上看，这似乎是民族国家逐步取代帝国统治的过程。</p><p>问题在于，巴尔干的人口分布从来不是整齐的。塞尔维亚人、克罗地亚人、斯洛文尼亚人、保加利亚人、希腊人、阿尔巴尼亚人、罗马尼亚人、穆斯林群体等长期交错居住，很难按照民族边界画出一张让所有人都满意的地图。</p><p>因此，一个民族国家的“统一”，往往意味着另一个国家的领土损失。</p><p>一个国家眼中的“收复民族土地”，在邻国眼中可能就是扩张甚至是侵略。</p><p>奥斯曼帝国的退场并没有解决地区矛盾，只是让原本被帝国统治压在内部的矛盾逐渐暴露出来。</p></section><section><h2>塞尔维亚的问题，为什么会变成奥匈帝国的生存问题？<a href="#塞尔维亚的问题为什么会变成奥匈帝国的生存问题"><span>#</span></a></h2><p>塞尔维亚是其中最关键的国家之一。</p><p>19 世纪末以后，一部分塞尔维亚民族主义者希望把塞尔维亚建设成南斯拉夫民族统一的核心。</p><p>所谓“南斯拉夫”，大体指欧洲东南部一些使用南斯拉夫语言、彼此具有文化与历史联系的民族群体，包括塞尔维亚人、克罗地亚人、斯洛文尼亚人等。</p><div><div><div></div><div>Note</div></div><div><p>“南斯拉夫”在这里首先是一种语言、文化和政治想象，并不意味着这些群体在宗教、历史经验和政治目标上完全一致。后来建立南斯拉夫联盟，也没有消除这些隔阂。</p></div></div><p><strong>大量南斯拉夫人口生活在奥匈帝国境内。</strong></p><p>1867 年奥地利帝国改组为奥匈帝国后，哈布斯堡君主统治着一个极其复杂的多民族帝国。德意志人和马扎尔人在政治结构中占据重要位置，但帝国内同时还有捷克人、斯洛伐克人、波兰人、乌克兰人、罗马尼亚人、克罗地亚人、塞尔维亚人、斯洛文尼亚人、意大利人等众多群体。</p><div><div><div></div><div>Note</div></div><div><p><strong>奥匈帝国的两套政府</strong></p><p>奥地利与匈牙利各有自己的政府和议会，共奉同一位君主。外交、共同军队及其经费由两部分共享的机构处理，因此帝国决策需要协调维也纳与布达佩斯两套政府。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Austria-Hungary-ethnic--ff-8d37f15a9e03.wjcodg1nd.svg" alt="1910 年奥匈帝国主要民族与语言群体分布图" /><figcaption>1910 年奥匈帝国主要民族与语言群体分布图</figcaption></figure><p></p><p>奥匈领导层担心，塞尔维亚吸引帝国内南斯拉夫人口的行动会引起其他民族效仿，动摇整个多民族帝国的政治结构。塞尔维亚越强，这种威胁感也就越强。</p></section><section><h2>1908 年波斯尼亚危机：难以抚平的创伤<a href="#1908-年波斯尼亚危机难以抚平的创伤"><span>#</span></a></h2><p>波斯尼亚和黑塞哥维那正位于这种矛盾的中心位置。</p><p>1878 年柏林会议以后，奥匈帝国获得了对波斯尼亚和黑塞哥维那的占领与管理权。虽然名义上它们仍属于奥斯曼帝国，但实际上已经由奥匈帝国控制。</p><p>1908 年，奥匈帝国干脆正式吞并了这一地区。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Le-Petit-Journal-Balkan-Crisis-%281908%29--ff-8d3e9d1b0faa.4qs47bxzio.webp" alt="1908 年《小日报》描绘波斯尼亚危机的政治讽刺画" /><figcaption>1908 年《小日报》描绘波斯尼亚危机的政治讽刺画</figcaption></figure><p></p><p>这立即引发严重危机。波斯尼亚拥有大量南斯拉夫人口，其中包括很多塞尔维亚人。塞尔维亚民族主义者希望这些地区未来能够与塞尔维亚结合，而奥匈的吞并也使得这种希望破灭。</p><p>俄罗斯也支持塞尔维亚的部分立场。但 1908 年的俄罗斯仍在恢复日俄战争和 1905 年革命造成的损失。</p><p>所以当德国明确站在奥匈一边时，俄罗斯最终并未准备为此发动战争，塞尔维亚也被迫接受被吞并的事实。</p><p>危机虽然结束，却给双方留下了两种截然相反的印象。</p><p>对奥匈和德国来说，这似乎证明了：</p><p><strong>只要态度足够强硬，俄罗斯可能会退让。</strong></p><p>对俄罗斯和塞尔维亚来说，它却是一场屈辱。</p><p>尤其对俄罗斯而言，如果此后每一次巴尔干危机都退让，它作为大国和斯拉夫保护者的信誉将越来越难以维持。</p><p>如果多次无法保护支持对象，其他国家就可能重新评估俄国的威慑力与联盟价值。这种担忧后来影响了 1914 年的决策。</p></section><section><h2>1912—1913 年巴尔干战争：塞尔维亚日渐强盛<a href="#19121913-年巴尔干战争塞尔维亚日渐强盛"><span>#</span></a></h2><p>1912 年，塞尔维亚、保加利亚、希腊、黑山组成巴尔干同盟，对奥斯曼帝国发动战争。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Mobilization-for-the-First-Balkan-War%2C-Belgrade-1912--ff-bde8f78d9e87.51ey0hd7ob.webp" alt="1912 年第一次巴尔干战争期间贝尔格莱德的动员场景" /><figcaption>1912 年第一次巴尔干战争期间贝尔格莱德的动员场景</figcaption></figure><p></p><p>奥斯曼帝国在欧洲的统治迅速崩溃。</p><p>随后，各战胜国又因马其顿等地区的分配问题爆发第二次巴尔干战争。</p><p>战争结束后，塞尔维亚的领土和人口得到显著扩张。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Srpska-vojska-u-Albaniji-1912--ff-9f8e967fb5fc.1vzg1jiss4.webp" alt="1912 年第一次巴尔干战争期间的塞尔维亚军官与士兵" /><figcaption>1912 年第一次巴尔干战争期间的塞尔维亚军官与士兵</figcaption></figure><p></p><p>这对塞尔维亚是一场民族国家扩张的胜利。</p><p>但在奥匈帝国看来，这却意味着：</p><p><strong>奥匈帝国南部出现了一个比过去更大、更自信、军事经验更丰富，而且更有能力吸引帝国内南斯拉夫民族的塞尔维亚。</strong></p><p>奥匈帝国内一部分政治和军事人物因此认为：</p><p>塞尔维亚问题如果继续拖下去，只会越来越难解决。</p><p>1914 年皇储遇刺后，奥匈帝国之所以迅速出现“必须趁这次机会彻底处理塞尔维亚”的声音，正是因为此前数年的经历已经让一些决策者形成了一种判断：</p><p><strong>时间可能不站在奥匈帝国这一边。</strong></p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Map-of-the-Balkans-%281913%29---fr--ff-f085ba86535c.9kgz3giom0.svg" alt="1913 年巴尔干半岛各国疆界与领土分布示意图" /><figcaption>1913 年巴尔干半岛各国疆界与领土分布示意图</figcaption></figure><p></p></section><section><h2>俄罗斯为什么不能简单放弃塞尔维亚？<a href="#俄罗斯为什么不能简单放弃塞尔维亚"><span>#</span></a></h2><p>俄罗斯对塞尔维亚的支持也不仅仅来自“同为斯拉夫民族”这一层关系。</p><p>泛斯拉夫主义和东正教文化联系确实影响了一部分俄罗斯公众和政治人物，但巴尔干对俄罗斯同样具有明确的战略价值。</p><p>俄罗斯长期关心黑海与地中海之间的海峡问题。如果奥匈势力不断向巴尔干扩张，俄罗斯不仅可能失去塞尔维亚这个地区伙伴，还可能进一步失去在东南欧的话语权。</p><p>民族与宗教联系、地区影响力和大国信誉，使俄罗斯很难在奥塞冲突中置身事外。俄国背后有法国，奥匈背后有德国，巴尔干危机因而可能牵动两组大国关系。</p><hr /></section></section>
<section><h1>军备竞赛与“抢先行动”逻辑<a href="#军备竞赛与抢先行动逻辑"><span>#</span></a></h1><p>第一次世界大战爆发以前，欧洲各国都在进行军备扩张。</p><p>德国和英国展开海军竞争；法国延长兵役期限；俄罗斯在日俄战争后加快军事改革和铁路建设；德国和奥匈也不断完善动员与作战计划。</p><p>扩军还改变了各国对时间压力的判断。</p><section><h2>德英海军竞争与战略不信任<a href="#德英海军竞争与战略不信任"><span>#</span></a></h2><p>德国皇帝威廉二世时期，德国推动“世界政策”，试图扩大海外影响力，大规模建设远洋海军。</p><p>英国的安全则长期依赖皇家海军。英国本土是岛国，海外殖民、贸易航线和粮食进口都高度依赖海上交通，因此，伦敦方面很难把德国建设一支足以在北海挑战英国的舰队，仅仅看作普通的国防现代化。</p><p>1906 年英国“无畏号”战列舰服役以后，旧式战列舰在主力舰竞争中的地位迅速下降，德国与英国又进入新一轮主力舰竞争。</p><div><div><div></div><div>Note</div></div><div><p><strong>无畏舰的技术特征</strong></p><p>“无畏号”集中配置十门 12 英寸重型主炮，并采用蒸汽轮机，最高航速约为 21 节。重型主炮与较高航速的组合成为这一代战列舰的鲜明特征，“无畏舰”也由舰名变成了一类战列舰的通称。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/HMS_Dreadnought_1906--ff-2e8c7659ea1c.4cloggpomu.webp" alt="约 1906—1907 年停泊中的英国“无畏号”战列舰" /><figcaption>约 1906—1907 年停泊中的英国“无畏号”战列舰</figcaption></figure><p></p><p>海军竞争加深了德英之间的战略互疑，使双方越来越把彼此视为潜在敌人。</p></section><section><h2>大陆战争最重要的资源：铁路和时间<a href="#大陆战争最重要的资源铁路和时间"><span>#</span></a></h2><p>在陆地上，情况更加复杂。</p><p>19 世纪末的欧洲已经进入大规模征兵时代。</p><p>一旦发生战争，一个大国需要在短时间内把数十万乃至数百万预备役军人召回部队，再通过铁路把他们送往边境。</p><p>这不是简单地下达一份“动员令”。</p><p>每个军团什么时候集合、在哪个车站上车、走哪条铁路、在哪一站换乘、什么时候到达集结地，都需要提前制定极其复杂的时刻表。</p><p>和平时期，这些计划只是纸面方案。一旦全面动员开始，它们就会产生强大的惯性。</p><p>如果一个国家启动了整套铁路运输和军事动员体系，再临时改变部署方向，就可能造成交通混乱、部队错位和补给中断，甚至在战争正式开始前就丧失战略优势。</p><p>这就产生了 1914 年最危险的一种心理：</p><blockquote><p><strong>如果敌人已经开始动员，而我们还在等待外交结果，就可能因为晚几天行动而输掉战争。</strong></p></blockquote><p>于是，动员本身越来越像一种战争信号。</p></section><section><h2>德国为什么对俄国动员尤其敏感？<a href="#德国为什么对俄国动员尤其敏感"><span>#</span></a></h2><p>德国的地理位置使这个问题更加尖锐。法国在西，俄罗斯在东。</p><p>德国总参谋部长期担心同时面对两个大国。后来通常被概括为“施里芬计划”的德国西线战争构想，其核心逻辑并不是简单的“德国要入侵法国”，而是试图解决这个两线战争难题：</p><div><div><div></div><div>Note</div></div><div><p>“施里芬计划”这个名称本身带有后来的概括性质。1914 年德国实际执行的部署并不是简单照搬施里芬早年的一份固定蓝图，而是在多年总参谋部规划基础上不断调整形成的西线作战方案。</p></div></div><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Alfred-von-Schlieffen-1906-%28cropped%29--ff-318caf17faf7.9o0l16brdf.webp" alt="1906 年的阿尔弗雷德·冯·施里芬" /><figcaption>1906 年的阿尔弗雷德·冯·施里芬</figcaption></figure><p></p><p>俄罗斯疆域辽阔，动员速度被认为相对较慢，而法国的军事力量则可以更快集中在德国西部边境。</p><p>因此，一旦德俄战争不可避免，德国倾向于先把主力集中到西线，迅速击败法国，再利用铁路把部队转向东线，全力对抗俄国。</p><p>而为了绕过法国东北部较坚固的防御体系，德国又必须要把大规模兵力部署到比利时方向。</p><p>这样，一个非常危险的战略链条提前形成了：</p><p><strong>俄罗斯一旦全面动员<br />
→ 德国担心战争窗口正在关闭<br />
→ 德国不能只对俄罗斯作战而放任法国<br />
→ 德国必须迅速在西线行动<br />
→ 西线行动又涉及中立的比利时<br />
→ 英国介入的可能性随之急剧上升。</strong></p><p>这条链条在 1914 年以前只是战争计划。</p><p>七月危机让它开始变成现实。</p><p>政治领导人仍能改变计划，但精确的动员和铁路时刻表提高了调整的代价。外交谈判需要时间，军事规划者却越来越把等待视为风险。</p><hr /></section></section>
<section><h1>萨拉热窝：刺杀不是凭空发生，也不是塞尔维亚政府的一纸命令<a href="#萨拉热窝刺杀不是凭空发生也不是塞尔维亚政府的一纸命令"><span>#</span></a></h1><p>1914 年 6 月 28 日，奥匈帝国皇储弗朗茨·斐迪南前往萨拉热窝访问。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Franz-Ferdinand-%26-Sophie-Leave-Sarajevo-Guildhall--ff-0881c37fdb88.9rk6yw4tzi.webp" alt="1914 年 6 月 28 日，弗朗茨·斐迪南与妻子索菲在随行人员陪同下离开萨拉热窝市政厅" /><figcaption>1914 年 6 月 28 日，弗朗茨·斐迪南与妻子索菲在随行人员陪同下离开萨拉热窝市政厅</figcaption></figure><p></p><p>这一天本身就具有高度象征意义。</p><p>6 月 28 日，即塞尔维亚历法中的“维多夫丹”，与 1389 年科索沃战役的历史记忆紧密相连。对塞尔维亚民族主义叙事而言，这是一个带有强烈民族象征的日期。</p><p>而奥匈帝国皇储恰恰在这一天访问 1908 年被正式吞并的波斯尼亚。</p><p>当地激进民族主义者很容易把这看成帝国统治的展示。</p><p>于是，一群塞尔维亚民族主义分子决定刺杀皇储，以示不满。</p><p>当天，一群与“青年波斯尼亚”有关的年轻人沿着皇储车队路线等待刺杀机会。</p><p>最初的袭击失败了。</p><p>刺客内德利科·查布里诺维奇向车队投掷炸弹，但没有炸中皇储乘坐的车辆。</p><p>按常理，发生如此严重的袭击后，访问本应立即取消。</p><p>可弗朗茨·斐迪南随后仍决定继续部分行程，并前往医院探望袭击中受伤的人。</p><p>车队临时更改路线，却因沟通失误驶入错误街道。</p><p>汽车停车、倒车时，19 岁的加夫里洛·普林西普恰好就在附近。</p><p>他走上前，向汽车开了两枪。</p><p>弗朗茨·斐迪南和妻子索菲先后中弹死亡。</p><p>这场刺杀能够成功，甚至带有一种令人难以置信的巧合。</p><section><h2>“青年波斯尼亚”“黑手会”和塞尔维亚国家之间是什么关系？<a href="#青年波斯尼亚黑手会和塞尔维亚国家之间是什么关系"><span>#</span></a></h2><p>普林西普是一名波斯尼亚塞族青年，但他的政治目标不能简单概括成“为塞尔维亚政府执行任务”。</p><p>他参与的“青年波斯尼亚”是一个松散的革命民族主义青年网络。成员的政治思想并不完全一致，但普遍反对奥匈统治，希望推动南斯拉夫民族解放或统一。</p><p>刺杀者所用的武器、接受的训练以及越境渠道，则与塞尔维亚境内的民族主义秘密网络有关。</p><p>最常被提到的是 <strong>“统一或死亡”</strong> 组织，也就是后来广为人知的“黑手会”。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Gavrilo-princip--ff-86c1322494c1.3yf8plhdqr.webp" alt="加夫里洛·普林西普" /><figcaption>加夫里洛·普林西普</figcaption></figure><p></p><p>黑手会内部有塞尔维亚军官和情报人员参与，其中部分人物确实为刺杀者提供了帮助。</p><p>塞尔维亚文官政府与这些激进军人之间本就存在权力冲突。</p><p>现有史料能够说明，这场刺杀虽然与塞尔维亚境内的民族主义组织和军方秘密网络存在联系，但这并不等于“塞尔维亚政府正式下令刺杀奥匈皇储”。</p><p>到 1914 年 7 月，维也纳方面越来越倾向于认为，无论塞尔维亚政府是否直接下令，一个容纳反奥民族主义网络、又不断壮大的塞尔维亚，本身就必须受到压制。刺杀调查因而逐渐变成了重塑巴尔干力量格局的政治机会。</p><hr /></section></section>
<section><h1>七月危机的前半段：奥匈想惩罚塞尔维亚，德国给了它冒险的底气<a href="#七月危机的前半段奥匈想惩罚塞尔维亚德国给了它冒险的底气"><span>#</span></a></h1><p>萨拉热窝刺杀以后，奥匈帝国并没有立即宣战。</p><p>相反，从 6 月 28 日到 7 月 23 日最后通牒发出，中间隔了三个多星期。这说明，欧洲大战并非情绪失控后的即时反应，而是一系列讨论、权衡和政策选择共同造成的结果。</p><section><h2>维也纳方面讨论的，不只是“如何调查刺杀”<a href="#维也纳方面讨论的不只是如何调查刺杀"><span>#</span></a></h2><p>刺杀消息传到维也纳后，奥匈领导层内部迅速出现强硬派。</p><p>尤其是外交大臣贝希托尔德等人，越来越倾向于认为单纯外交抗议无法解决问题。过去几年里，塞尔维亚通过巴尔干战争扩大实力，南斯拉夫民族主义又不断冲击帝国南部。</p><p>如果皇储遇刺以后，奥匈仍然只获得一份道歉和若干逮捕令的补偿，结果可能不是“问题解决”，而是进一步证明奥匈缺乏压制塞尔维亚的能力。</p><p>因此，部分决策者开始把刺杀理解为一个难得的机会：</p><p><strong>这一次必须让塞尔维亚遭受决定性的外交或军事挫败。</strong></p><p>不过，奥匈内部并非从一开始就完全一致。</p><p>匈牙利首相蒂萨最初就反对立即发动战争。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Portrait-of-Count-Leopold-Berchtold-%28cropped%29--ff-6438ef34d9ce.362d7v0s30.webp" alt="1914 年前后的奥匈外交大臣莱奥波德·贝希托尔德" /><figcaption>1914 年前后的奥匈外交大臣莱奥波德·贝希托尔德</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Tisza-Istv%C3%A1n-portr%C3%A9ja--ff-496ba96d1ddd.1ht0aoahx8.webp" alt="1912 年前后的匈牙利政治家伊什特万·蒂萨" /><figcaption>1912 年前后的匈牙利政治家伊什特万·蒂萨</figcaption></figure></div><p>他的担忧之一，是吞并更多塞尔维亚人口反而会进一步破坏帝国内部脆弱的民族平衡；他也担心俄国介入导致欧洲战争。</p><p>然而，德国的支持改变了奥匈对风险的判断。</p></section><section><h2>德国的“空头支票”<a href="#德国的空头支票"><span>#</span></a></h2><p>7 月初，奥匈派遣特使前往柏林确认德国态度。</p><p>德国领导层明确表示，即使奥匈对塞尔维亚采取强硬行动，并且因此引发俄国干预，德国仍会支持自己的盟友。</p><p>后世通常把这种支持承诺概括为德国给奥匈的 <strong>“空头支票”</strong>；这一称呼并非当时正式文件的名称。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Kaiser-Guillermo-II-1914--ff-a3786d0ed404.1766hiv9q2.webp" alt="约 1914 年的德国皇帝威廉二世" /><figcaption>约 1914 年的德国皇帝威廉二世</figcaption></figure><p></p><p>德国的承诺使奥匈领导层相信，即使俄罗斯介入，自己也不会孤立无援，因而更愿意对塞尔维亚采取军事行动。</p><p>德国担心，奥匈这个唯一可靠的大国盟友会因在巴尔干不断退让而日益虚弱；俄罗斯正在扩军，法俄合作也越来越紧密。在柏林部分军政人物看来，如果几年后仍须面对俄法大战，等待俄国完成军事改革只会使德国处境更加不利。这种判断并不意味着德国领导层已经一致决定借刺杀发动世界大战，却提高了他们对危机升级风险的接受程度。</p><p>柏林仍希望，奥匈若行动迅速、德国又坚定支持，俄罗斯也许会像 1908 年那样退让。奥匈则希望通过迅速压服塞尔维亚限制危机，后来俄国又试图以军事准备阻止奥匈摧毁塞尔维亚。各方都把展示决心视为迫使对手退让的手段，这些威慑举动却相互刺激，推动了危机升级。</p></section><section><h2>奥匈帝国的“等待”<a href="#奥匈帝国的等待"><span>#</span></a></h2><p>奥匈最终没有立即行动，而是直到 7 月 23 日才向塞尔维亚递交最后通牒。</p><p>这既有内部协调和准备方面的原因，也有对外交时机的考虑。国内方面，奥匈需要让最初持保留意见的蒂萨接受强硬政策，也需要起草具体要求。</p><p>至于外交时机，最后通牒的递交被特意安排在法国总统普恩加莱和总理维维亚尼结束对俄罗斯国事访问之后。</p><p>这样一来，最后通牒提出时，法俄两国最高层便难以及时面对面协调应对。</p><p>因此，等待并不意味着奥匈在不断寻求妥协。</p><p>相反，这段时间被用来精心策划一份极其强硬的外交文件。</p></section><section><h2>强硬的最后通牒<a href="#强硬的最后通牒"><span>#</span></a></h2><p>7 月 23 日，奥匈向塞尔维亚递交最后通牒，并只给予约 48 小时答复。</p><p>其中不少要求本身并非毫无依据，例如要求塞尔维亚压制公开的反奥宣传、解散相关民族主义组织、惩处涉案人员。</p><p>引起争议的，是奥匈要求让其官员参与塞尔维亚境内有关镇压反奥运动和调查刺杀阴谋的行动。</p><p>对一个主权国家而言，这意味着让外国政府人员直接介入本国执法和司法事务。</p><p>奥匈制定这份通牒时，本身就是为了让<strong>条件足够严苛，使塞尔维亚很难毫无保留地接受。</strong></p><p>如果塞尔维亚拒绝，奥匈便有了发动军事行动的理由。</p><p>于是，奥匈有意把一场原本可以围绕“调查刺杀”展开的外交交涉，推向了一场国家主权危机。</p><div><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Ultimatum_k.u.k_serbien--ff-37fb6e09a65a.7q34csim6.webp" alt="1914 年 7 月 23 日奥匈外交大臣贝希托尔德致驻贝尔格莱德公使的文书影像" /><figcaption>1914 年 7 月 23 日奥匈外交大臣贝希托尔德致驻贝尔格莱德公使的文书影像</figcaption></figure><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/TelegramWW1--ff-8444fdcb4b17.4922iqwlyx.webp" alt="1914 年 7 月 28 日奥匈帝国对塞尔维亚宣战电报" /><figcaption>1914 年 7 月 28 日奥匈帝国对塞尔维亚宣战电报</figcaption></figure></div><hr /></section></section>
<section><h1>七月危机的后半段：同盟没有“自动触发”战争，各国却一步步自发跟进<a href="#七月危机的后半段同盟没有自动触发战争各国却一步步自发跟进"><span>#</span></a></h1><p>7 月 25 日，塞尔维亚提交了对最后通牒的正式答复。</p><p>它接受了相当一部分要求，包括打击反奥宣传、解散有关组织和追究部分责任人，但对涉及奥匈官员直接参与塞尔维亚境内调查与执法的要求作出保留，并提出可以把部分争议交由国际仲裁。</p><p>这远不能看成是“拒绝”，甚至德国皇帝威廉二世后来阅读塞尔维亚答复时，也一度认为这已经取得了相当大的外交成果，战争未必还有必要。</p><p>但奥匈此时的目标已经不只是得到一份尽可能满意的答复。</p><p>维也纳希望通过制造一场危机，实质性削弱塞尔维亚。</p><p>因此，塞尔维亚没有完全接受所有条件，就足以让奥匈继续推进既定政策。</p><p>7 月 28 日，奥匈帝国向塞尔维亚宣战。</p><p>至此，欧洲出现局部战争。</p><p>但它仍然只是 <strong>奥匈—塞尔维亚战争</strong>。</p><section><h2>俄国面对的是“塞尔维亚问题”，也是自己的大国地位问题<a href="#俄国面对的是塞尔维亚问题也是自己的大国地位问题"><span>#</span></a></h2><p>俄罗斯不能轻易让奥匈帝国全面压倒塞尔维亚。</p><p>这不仅因为民族和宗教联系，也因为 1908 年波斯尼亚危机的记忆。</p><p>如果俄罗斯再次在德国和奥匈压力下退让，它担心自己在巴尔干的影响力和大国信誉都会受到打击。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Nicholas-II%2C-1914--ff-8ce6b420263f.3k8syq92y3.webp" alt="1914 年的俄罗斯皇帝尼古拉二世" /><figcaption>1914 年的俄罗斯皇帝尼古拉二世</figcaption></figure><p></p><p>但俄罗斯也面临一个军事难题：</p><p>如果只对奥匈帝国方向进行局部动员，俄国复杂的动员体系能否顺利执行？如果德国随后加入战争，再从局部动员转换为全面动员，会不会造成铁路和部署混乱？</p><p>所以俄罗斯军方更倾向于完整执行既定的“全面动员”方案。</p><p>而俄国领导层眼中一项旨在“防止塞尔维亚被摧毁”的措施，到了德国却有了完全不同的含义。</p></section><section><h2>“动员”几乎等于宣告战争<a href="#动员几乎等于宣告战争"><span>#</span></a></h2><p>对现代大规模战争而言，动员不是简单的外交姿态。它意味着一套涉及数百万人的战争机器开始运转。</p><p>俄罗斯全面动员以后，德国面对的问题是：</p><p>如果继续等待，那么等到俄军完全部署后，自己的“西线计划”是否还有充足的时间执行？</p><p>德国因此向俄罗斯发出最后通牒，要求停止动员，当然得到了俄国的拒绝。</p><p>1914 年 8 月 1 日，德国向俄罗斯宣战。</p><p>到这里，危机已经从巴尔干战争变成了德俄战争。</p></section><section><h2>法国为什么被卷入其中？<a href="#法国为什么被卷入其中"><span>#</span></a></h2><p>法国与俄罗斯之间存在正式的军事同盟。</p><p>对法国来说，如果俄罗斯在与德国的冲突中被单独击败，法国以后就可能重新陷入面对德国的战略孤立。</p><p>对德国来说，如果先把主力全部投入东线，而法国随后从西面进攻，德国就会陷入腹背受敌的局面。</p><p>因此，当德俄战争成为现实时，德国总参谋部很快转入既有的西线计划。</p><p>8 月 3 日，德国向法国宣战。</p><p>德国不愿等待法国是否先动手：在军事规划者看来，等到法国意图明确时，西线部署的时间优势可能已经消失。</p></section><section><h2>比利时问题为什么又使得英国参战？<a href="#比利时问题为什么又使得英国参战"><span>#</span></a></h2><p>按照德国西线进攻计划，大军需要经过卢森堡和比利时进入法国北部。</p><p>而比利时是中立国，德国要求比利时允许军队通过，被比利时拒绝。</p><p>随后德军直接越过边境。</p><p>英国此前虽然已经与法国展开军事和海军协调，但政府内部对于是否介入大陆战争仍存在严重分歧。</p><p>比利时问题改变了这种政治局面。</p><p>一方面，英国是 1839 年有关维持比利时中立安排的参与者。</p><p>另一方面，比利时和法国北部就在英吉利海峡对岸。</p><p>英国长期战略的核心之一，是不允许某一个大陆强国控制低地国家和海峡对岸的重要港口。</p><p>因此，比利时中立既是法律和外交问题，也是英国自己的安全问题。</p><p>8 月 4 日，英国向德国宣战。</p><div><div><div></div><div>Note</div></div><div><p><strong>英国参战与自治领</strong></p><p>1914 年，英国的宣战也使加拿大、澳大利亚、新西兰等自治领进入对德战争状态。这些地区随后组织远征军，陆续前往欧洲和其他战区。</p></div></div><p>奥塞之间的局部战争由此扩大为欧洲主要大国之间的战争。</p></section><section><h2>37 天：危机怎样一层层扩大<a href="#37-天危机怎样一层层扩大"><span>#</span></a></h2>

<table><thead><tr><th>日期</th><th>事件</th><th>危机性质的变化</th></tr></thead><tbody><tr><td><strong>6 月 28 日</strong></td><td>弗朗茨·斐迪南夫妇在萨拉热窝遇刺</td><td>奥匈帝国内部的安全与政治危机</td></tr><tr><td><strong>7 月 5—6 日前后</strong></td><td>德国向奥匈表达坚定支持</td><td>奥塞问题开始与德俄竞争相互牵连</td></tr><tr><td><strong>7 月 23 日</strong></td><td>奥匈向塞尔维亚递交最后通牒</td><td>刺杀调查转化为国家主权危机</td></tr><tr><td><strong>7 月 25 日</strong></td><td>塞尔维亚接受多数要求，但保留部分条款</td><td>仍存在外交妥协空间</td></tr><tr><td><strong>7 月 28 日</strong></td><td>奥匈向塞尔维亚宣战</td><td>地区战争正式开始</td></tr><tr><td><strong>7 月 29—30 日</strong></td><td>俄国军事准备升级并转向全面动员</td><td>地区战争进入大国军事对抗</td></tr><tr><td><strong>8 月 1 日</strong></td><td>德国向俄国宣战</td><td>奥塞战争扩大为德俄战争</td></tr><tr><td><strong>8 月 3 日</strong></td><td>德国向法国宣战</td><td>两线战争逻辑启动</td></tr><tr><td><strong>8 月 4 日</strong></td><td>德军进入比利时；英国向德国宣战</td><td>欧洲主要大国全面卷入</td></tr></tbody></table><p>英国外交大臣格雷曾推动大国调停；德国皇帝威廉二世与俄国沙皇尼古拉二世之间进行了著名的“威利—尼基”电报往来；德国、俄罗斯、英国和奥匈之间也曾交换不同的妥协方案。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Edward-Grey%2C-1st-Viscount-Grey-of-Falloden--ff-1b26dc69e3e6.102ym394dd.webp" alt="英国外交大臣爱德华·格雷肖像" /><figcaption>英国外交大臣爱德华·格雷肖像</figcaption></figure><p></p><p>外交协商尚未结束，军事部署的时限却不断压缩各国核实意图、修改条件的时间。</p><p></p><figure><img src="https://img.nightbugclub.cn/posts/history/WWI/260924-WWI-1-from-sarajevo-to-the-wwi/Mobilization-order-is-read-out-in-Berlin%2C-1-August-1914--ff-9a2ef4ee502e.73uqojbsq1.webp" alt="1914 年 8 月 1 日柏林街头民众聚集听取动员令" /><figcaption>1914 年 8 月 1 日柏林街头民众聚集听取动员令</figcaption></figure><p></p><hr /></section></section>
<section><h1>从地区危机到欧洲大战<a href="#从地区危机到欧洲大战"><span>#</span></a></h1><p>巴尔干冲突牵动了奥匈的帝国结构与俄国的地区影响力，同盟关系又把盟友的失败变成各国对自身安全的威胁。过去危机留下的经验，使德国更相信强硬能够奏效，也使俄国更难接受再次退让；动员计划则不断压缩妥协的时间。</p><p>刺杀提供了危机的起点，战争的扩大仍取决于七月各国政府的具体选择。奥匈试图借机压服塞尔维亚，德国以支持盟友和抢占时间应对风险，俄国以动员显示决心；这些行动相互刺激，最终把原本可以限制在局部的冲突推向了欧洲大战。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考第一次世界大战爆发史、七月危机研究、外交文件与公共历史资料。若希望继续深入阅读，可以优先从前 4 项开始：</p><section><h2>一、写作参考资料<a href="#一写作参考资料"><span>#</span></a></h2><ol>
<li>
<p><strong>Christopher Clark, <em>The Sleepwalkers: How Europe Went to War in 1914</em></strong><br />
近年讨论 1914 年战争爆发最常被引用的著作之一，重点不在于寻找单一“罪魁”，而在于解释欧洲列强如何在复杂互动中一步步走进战争。</p>
</li>
<li>
<p><strong>Margaret MacMillan, <em>The War That Ended Peace: The Road to 1914</em></strong><br />
对 19 世纪末至 1914 年欧洲政治、外交文化、军备竞赛与大国关系有相当完整的梳理，适合用来理解长期背景。</p>
</li>
<li>
<p><strong>Hew Strachan, <em>The First World War, Volume I: To Arms</em></strong><br />
研究第一次世界大战爆发、动员与战争初期进程的经典学术著作，尤其适合补充大战如何从危机转化为实际军事行动。</p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — July Crisis 1914</strong><br />
对萨拉热窝事件以后奥匈、德国、俄罗斯等国的决策过程、“空头支票”、最后通牒与动员危机进行系统梳理。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/july_crisis_1914/" target="_blank">https://encyclopedia.1914-1918-online.net/article/july_crisis_1914/</a></p>
</li>
<li>
<p><strong>International Encyclopedia of the First World War — Berchtold, Leopold Graf</strong><br />
介绍奥匈外交大臣贝希托尔德在刺杀以后推动对塞尔维亚强硬政策、协调德国支持并制定最后通牒的过程。<br />
<a href="https://encyclopedia.1914-1918-online.net/article/berchtold-leopold-graf/" target="_blank">https://encyclopedia.1914-1918-online.net/article/berchtold-leopold-graf/</a></p>
</li>
<li>
<p><strong>U.S. Department of State, Office of the Historian — Papers Relating to the Foreign Relations of the United States, 1914, Supplement, The World War</strong><br />
收录美国外交人员在七月危机期间对奥匈—塞尔维亚危机、俄国动员、德国反应以及欧洲局势的同期外交记录。<br />
<a href="https://history.state.gov/historicaldocuments/frus1914Supp" target="_blank">https://history.state.gov/historicaldocuments/frus1914Supp</a></p>
</li>
<li>
<p><strong>U.S. Department of State, Office of the Historian — The Crisis</strong><br />
按时间整理 1914 年 7 月下旬欧洲危机中的外交电报与战争扩散过程，可用于核对奥匈最后通牒、塞尔维亚答复、俄国动员以及各国调停活动。<br />
<a href="https://history.state.gov/historicaldocuments/frus1914Supp/toc-topics" target="_blank">https://history.state.gov/historicaldocuments/frus1914Supp/toc-topics</a></p>
</li>
<li>
<p><strong>Imperial War Museums — First World War history</strong><br />
关于战前欧洲联盟体系、军备扩张、帝国竞争以及第一次世界大战爆发过程的公共历史资料。<br />
<a href="https://www.iwm.org.uk/history/first-world-war" target="_blank">https://www.iwm.org.uk/history/first-world-war</a></p>
</li>
<li>
<p><strong>Encyclopaedia Britannica — World War I</strong><br />
关于第一次世界大战背景、巴尔干危机、萨拉热窝刺杀与战争爆发过程的综合性历史资料。<br />
<a href="https://www.britannica.com/event/World-War-I" target="_blank">https://www.britannica.com/event/World-War-I</a></p>
</li>
<li>
<p><strong>Imperial War Museums — The July Crisis / related First World War collections</strong><br />
可用于补充萨拉热窝事件照片、动员场景与一战初期图像资料。<br />
<a href="https://www.iwm.org.uk/collections" target="_blank">https://www.iwm.org.uk/collections</a></p>
</li>
<li>
<p><strong>Schönbrunn / Habsburger.net — The Dual Monarchy / The supranational state</strong><br />
关于奥地利、匈牙利各自的政府与议会，以及共同外交、军事和经费安排。<br />
<a href="https://ww1.habsburger.net/en/chapters/dual-monarchy-two-states-single-empire/1000" target="_blank">The Dual Monarchy: two states in a single empire</a><br />
<a href="https://ww1.habsburger.net/en/chapters/indivisible-and-inseparable-supranational-state" target="_blank">“Indivisible and inseparable” — the supranational state</a></p>
</li>
<li>
<p><strong>Manatū Taonga — The British Empire</strong><br />
新西兰文化与遗产部关于英国宣战与自治领参战关系的历史资料。<br />
<a href="https://nzhistory.govt.nz/page/british-empire" target="_blank">NZ History：The British Empire</a></p>
</li>
<li>
<p><strong>Royal Museums Greenwich — Dreadnought (1906); Warship; Battleship</strong>
馆藏说明介绍“无畏号”的十门 12 英寸主炮、蒸汽轮机、21 节航速，以及舰名成为一代战列舰通称的过程。
<a href="https://www.rmg.co.uk/collections/objects/rmgc-object-67323" target="_blank">格林尼治皇家博物馆：“无畏号”</a></p>
</li>
</ol></section><section><h2>二、文中配图来源<a href="#二文中配图来源"><span>#</span></a></h2><ol>
<li>
<p><strong>1890 年奥托·冯·俾斯麦肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Portrait_of_Otto_von_Bismarck_(by_Franz_Seraph_von_Lenbach,_1890).jpg" target="_blank">Wikimedia Commons 原图页</a>。弗朗茨·冯·伦巴赫 1890 年绘制的俾斯麦肖像；原作与数字化文件的版权状态见 Commons 文件页，属于公有领域作品。</p>
</li>
<li>
<p><strong>19 世纪末至 20 世纪初法国讽刺画中的三国同盟</strong>：<a href="https://commons.wikimedia.org/wiki/File:Caricature_of_the_Triple_alliance.jpg" target="_blank">Wikimedia Commons 原图页</a>。法国政治讽刺画，作者不详，描绘德国与奥匈扶持意大利、英国为其“诊脉”，法俄则在旁观察；Commons 页面将该历史作品标记为公有领域。</p>
</li>
<li>
<p><strong>1896 年尼古拉二世访法期间的亚历山大三世桥奠基仪式</strong>：<a href="https://commons.wikimedia.org/wiki/File:Voyage_du_tsar_(Nicolas_II_et_la_tsarine_Alexandra_Feodorovna_en_pr%C3%A9sence_de_F%C3%A9lix_Faure_posant_la_premi%C3%A8re_pierre_du)_pont_Alexandre_III_-_btv1b532609488.jpg" target="_blank">Wikimedia Commons 原图页</a>。Atelier Nadar 摄于 1896 年，馆藏于法国国家图书馆（Bibliothèque nationale de France）；原图页提供其馆藏与版权信息。</p>
</li>
<li>
<p><strong>1903 年“英法协约”主题明信片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Entente_Cordiale_dancing.png" target="_blank">Wikimedia Commons 原图页</a>。1903 年英法友好主题明信片，作者不详；Wikimedia Commons 页面将其标记为公有领域。</p>
</li>
<li>
<p><strong>1907 年英俄协约划分波斯势力范围示意图</strong>：<a href="https://commons.wikimedia.org/wiki/File:The_Russo-British_Pact_in_1907.jpg" target="_blank">Wikimedia Commons 原图页</a>。表现 1907 年英俄协约对波斯势力范围的划分；历史图像及其版权状态见 Commons 文件页。</p>
</li>
<li>
<p><strong>1914 年欧洲联盟地图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Map_Europe_alliances_1914-en.svg" target="_blank">Wikimedia Commons 原图页</a>。原图作者 historicair；英文译图由 Fluteflute、Bibi Saint-Pol 制作，为法文原图的英文衍生版本；<a href="https://creativecommons.org/licenses/by-sa/2.5/" target="_blank">CC BY-SA 2.5</a> 授权。</p>
</li>
<li>
<p><strong>1910 年奥匈帝国主要民族与语言群体分布图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Austria_Hungary_ethnic.svg" target="_blank">Wikimedia Commons 原图页</a>。ArdadN 根据 William R. Shepherd 1911 年历史地图集中的民族分布图制作；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>1908 年《小日报》波斯尼亚危机讽刺画</strong>：<a href="https://commons.wikimedia.org/wiki/File:Le_Petit_Journal_Balkan_Crisis_(1908).jpg" target="_blank">Wikimedia Commons 原图页</a>。1908 年 10 月 18 日法国《Le Petit Journal》插图，作者不详；描绘奥匈吞并波斯尼亚、保加利亚独立等巴尔干危机元素，页面标记为公有领域。</p>
</li>
<li>
<p><strong>1912 年第一次巴尔干战争期间贝尔格莱德的动员场景</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mobilization_for_the_First_Balkan_War,_Belgrade_1912.jpg" target="_blank">Wikimedia Commons 原图页</a>。1912 年摄于贝尔格莱德，作者不详；Commons 页面依据历史照片版权期限标记为公有领域。</p>
</li>
<li>
<p><strong>1912 年第一次巴尔干战争期间的塞尔维亚军官与士兵</strong>：<a href="https://commons.wikimedia.org/wiki/File:Srpska_vojska_u_Albaniji_1912.jpg" target="_blank">Wikimedia Commons 原图页</a>。1912 年塞尔维亚军队在阿尔巴尼亚方向的历史照片，来源指向塞尔维亚国家图书馆数字馆藏；版权状态见 Commons 原图页。</p>
</li>
<li>
<p><strong>1913 年巴尔干半岛形势图</strong>：<a href="https://commons.wikimedia.org/wiki/File:Map_of_the_Balkans_(1913)_-_fr.svg" target="_blank">Wikimedia Commons 原图页</a>。该图由 Tinodela 基于原始扫描图制作衍生版本；Wikimedia Commons 页面将原始作品及该文件标记为公有领域。</p>
</li>
<li>
<p><strong>英国“无畏号”战列舰</strong>：<a href="https://commons.wikimedia.org/wiki/File:HMS_Dreadnought_1906.jpg" target="_blank">Wikimedia Commons 原图页</a>。约摄于 1906—1907 年，摄影者未注明；来源为美国海军历史中心（U.S. Navy Historical Center），编号 NH 63367。该页面将其标记为美国联邦政府作品，在美国属于公有领域。</p>
</li>
<li>
<p><strong>1906 年的阿尔弗雷德·冯·施里芬</strong>：<a href="https://commons.wikimedia.org/wiki/File:Alfred_von_Schlieffen_1906_(cropped).jpg" target="_blank">Wikimedia Commons 原图页</a>。E. Bieber 照相馆摄于 1906 年，文件页所列来源为巴登—符腾堡州档案馆（Landesarchiv Baden-Württemberg）；当前文件是完整照片的裁切版本。Commons 标注为美国公有领域。</p>
</li>
<li>
<p><strong>弗朗茨·斐迪南与索菲离开萨拉热窝市政厅</strong>：<a href="https://commons.wikimedia.org/wiki/File:Franz_Ferdinand_%26_Sophie_Leave_Sarajevo_Guildhall.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄于 1914 年 6 月 28 日，摄影者不详；Wikimedia Commons 页面以 <a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank">CC0 1.0</a> 公共领域贡献方式提供。</p>
</li>
<li>
<p><strong>加夫里洛·普林西普照片</strong>：<a href="https://commons.wikimedia.org/wiki/File:Gavrilo_princip.jpg" target="_blank">Wikimedia Commons 原图页</a>。作者不详，页面所列来源为 <em>Photos of the Great War</em>；Wikimedia Commons 将该作品标记为公有领域。</p>
</li>
<li>
<p><strong>奥匈外交大臣莱奥波德·贝希托尔德肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Portrait_of_Count_Leopold_Berchtold_(cropped).jpg" target="_blank">Wikimedia Commons 原图页</a>。不晚于 1914 年，原载于 <em>The World’s Work</em>，作者不详；Commons 页面将其标记为公有领域。</p>
</li>
<li>
<p><strong>1912 年前后的伊什特万·蒂萨肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Tisza_Istv%C3%A1n_portr%C3%A9ja.jpg" target="_blank">Wikimedia Commons 原图页</a>。1912 年前后的历史肖像，作者不详；Wikimedia Commons 页面依据匿名作品出版年代标记为公有领域。</p>
</li>
<li>
<p><strong>约 1914 年的德国皇帝威廉二世</strong>：<a href="https://commons.wikimedia.org/wiki/File:Kaiser_Guillermo_II_1914.png" target="_blank">Wikimedia Commons 原图页</a>。约 1914 年刊载于西班牙杂志 <em>Mundo Gráfico</em>，作者未署名；Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>奥匈帝国对塞尔维亚最后通牒相关文书</strong>：<a href="https://commons.wikimedia.org/wiki/File:Ultimatum_k.u.k_serbien.png" target="_blank">Wikimedia Commons 原图页</a>。1914 年 7 月 23 日，奥匈外交大臣莱奥波德·贝希托尔德致驻贝尔格莱德公使弗拉基米尔·冯·吉斯尔的文书；来源为奥地利国家档案馆（Österreichisches Staatsarchiv），Wikimedia Commons 页面标记为公有领域。</p>
</li>
<li>
<p><strong>奥匈帝国对塞尔维亚宣战电报</strong>：<a href="https://commons.wikimedia.org/wiki/File:TelegramWW1.jpg" target="_blank">Wikimedia Commons 原图页</a>。1914 年 7 月 28 日由奥匈帝国政府发给塞尔维亚政府，页面所列作者为奥匈帝国政府；Wikimedia Commons 将该历史文书标记为公有领域。</p>
</li>
<li>
<p><strong>1914 年的俄罗斯皇帝尼古拉二世</strong>：<a href="https://commons.wikimedia.org/wiki/File:Nicholas_II,_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。1914 年俄国宫廷摄影师所摄家庭照片的裁切版本；Commons 页面将其标记为公有领域。</p>
</li>
<li>
<p><strong>英国外交大臣爱德华·格雷肖像</strong>：<a href="https://commons.wikimedia.org/wiki/File:Edward_Grey,_1st_Viscount_Grey_of_Falloden.jpg" target="_blank">Wikimedia Commons 原图页</a>。爱德华·格雷肖像，原作与数字化文件版权信息见 Wikimedia Commons 原图页。</p>
</li>
<li>
<p><strong>1914 年 8 月 1 日柏林宣读动员令</strong>：<a href="https://commons.wikimedia.org/wiki/File:Mobilization_order_is_read_out_in_Berlin,_1_August_1914.jpg" target="_blank">Wikimedia Commons 原图页</a>。摄影者不详，馆藏于英国帝国战争博物馆（Imperial War Museums），馆藏编号 Q 81755；Wikimedia Commons 页面将该历史照片的扫描版本标记为公有领域，原作属于已到期的英国政府 Crown Copyright 作品。</p>
</li>
</ol><div><div><div></div><div>Note</div></div><div><p>Wikimedia Commons 上的历史图片可能来自不同国家、不同年代与不同馆藏，其版权状态并不完全一致。本文在配图来源中按照文件页当前标注说明作者、馆藏与授权信息；若用于转载、商业发布或二次创作，仍应以对应 Commons 文件页及原馆藏机构的最新授权说明为准。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/tech/260922-music-player-improvements/</id>
      <title type="text">歌单没有及时更新——由一次 API 异常引发的音乐播放器改造</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/tech/260922-music-player-improvements/"/>
      <summary type="text">看文章的时候没歌听怎么行？记录我如何从一次歌单 API 数据异常出发，修正 Firefly 音乐播放器的一些小问题，以及加入我的一些“小巧思”。</summary>
      <content type="html"><![CDATA[<p>最近，我给博客的音乐播放器更新了歌单。</p>
<p>原本以为这只是一件很简单的事：在网易云音乐里把新歌曲加入歌单，刷新博客，播放器就应该自动读到最新内容。</p>
<p>但实际情况并不是这样。</p>
<p>无论怎么刷新，播放器里始终看不到新加入的歌曲。也正是从这个看似普通的问题开始，我顺着播放器的数据读取、播放队列和异常处理一路检查，最后干脆把几个一直想改、却没有认真整理过的地方一起完善了。</p>
<p>这篇文章记录的，就是这次播放器改造的完整过程。</p>
<hr />
<section><h1>问题并不在浏览器缓存<a href="#问题并不在浏览器缓存"><span>#</span></a></h1><p>最开始，我怀疑的是缓存。</p><p>毕竟播放器是一个长期存在于页面中的全局组件，又配合 Swup 保持播放状态，很容易让人联想到：是不是旧歌单被保存在了 <code>localStorage</code>，或者页面切换以后复用了旧数据？</p><p>检查代码后发现，播放器确实会把音量写入 <code>localStorage</code>，但歌单本身并不会被持久化。只要完整刷新页面，播放器仍然会重新向 Meting API 请求数据。</p><p>在播放器状态中，<code>localStorage</code> 只用于恢复音量：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>volume</span><span>: </span><span>localStorage</span><span>.</span></span><span>getItem</span><span>(</span><span>'music-player-volume'</span><span>) </span><span>!==</span><span> </span><span>null</span></div></div><div><div><div>2</div></div><div><span>  </span><span>?</span><span> </span><span>parseFloat</span><span><span>(</span><span>localStorage</span><span>.</span></span><span>getItem</span><span>(</span><span>'music-player-volume'</span><span>))</span></div></div><div><div><div>3</div></div><div><span>  </span><span>:</span><span><span> (</span><span>config</span><span>.</span></span><span>volume</span><span> </span><span>||</span><span> </span><span>0.7</span><span>),</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改音量时也只会写回这个键，并没有保存播放列表：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>localStorage</span><span>.</span></span><span>setItem</span><span>(</span><span>'music-player-volume'</span><span><span>, </span><span>val</span><span>.</span></span><span>toString</span><span>());</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>真正的问题出现在 API 的切换链路上。</p><p>当时配置的主 API 已经返回 <code>404</code>，播放器于是依次尝试备用接口。第一个能够返回非空数组的备用接口只返回了 3 首歌，而网易云源歌单和其他接口实际返回的是 7 首。</p><p>播放器原来的判断逻辑非常直接：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>Array</span><span>.</span></span><span>isArray</span><span><span>(</span><span>data</span><span>) </span></span><span>&amp;&amp;</span><span><span> </span><span>data</span><span>.</span></span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>playlist</span><span> </span><span>=</span><span><span> </span><span>data</span><span>.</span></span><span>map</span><span>(</span><span>function</span><span><span> (</span><span>item</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span> {</span></div></div><div><div><div>4</div></div><div><span>      </span><span>name</span><span>:</span><span><span> </span><span>item</span><span>.</span></span><span>title</span><span> </span><span>||</span><span><span> </span><span>item</span><span>.</span></span><span>name</span><span> </span><span>||</span><span> </span><span>'Unknown'</span><span>,</span></div></div><div><div><div>5</div></div><div><span>      </span><span>artist</span><span>:</span><span><span> </span><span>item</span><span>.</span></span><span>author</span><span> </span><span>||</span><span><span> </span><span>item</span><span>.</span></span><span>artist</span><span> </span><span>||</span><span> </span><span>'Unknown'</span><span>,</span></div></div><div><div><div>6</div></div><div><span>      </span><span>url</span><span>:</span><span><span> </span><span>item</span><span>.</span></span><span>url</span><span>,</span></div></div><div><div><div>7</div></div><div><span>      </span><span>pic</span><span>:</span><span><span> </span><span>item</span><span>.</span></span><span>pic</span><span> </span><span>||</span><span><span> </span><span>item</span><span>.</span></span><span>cover</span><span> </span><span>||</span><span> </span><span>''</span><span>,</span></div></div><div><div><div>8</div></div><div><span>      </span><span>lrc</span><span>:</span><span><span> </span><span>item</span><span>.</span></span><span>lrc</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span>;</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只要结果不是空数组，就认为请求成功，并停止尝试后续接口。</p><p>所以页面并不是没有重新请求，也不是浏览器偷偷保存了旧歌单，而是每次刷新都会稳定地拿到同一份不完整数据。</p><p>这个排查让我重新意识到一件事：第三方音乐 API 的“请求成功”，不一定等于“数据正确”，更不等于“歌曲一定能够播放”。</p><div><div><div></div><div>Note</div></div><div><p>对播放器来说，HTTP 状态正常、返回值也是非空数组，只能说明接口完成了一次响应。歌单是否完整、歌曲地址是否有效，仍然是另外两个问题。</p></div></div><p>对于歌单数量不一致，最直接的办法确实是每次请求全部 API，再采用歌曲数量最多的结果。但如果把这种行为直接写死，歌单没有更新时也会产生一轮不必要的并行请求。</p><p>因此，我没有在这里简单地把“第一个非空结果”替换成“永远请求全部”，而是先把它记成一个需要配置化的问题：既要保留响应更快、请求更少的旧行为，也要允许播放器在必要时比较多个接口。这点会在后文说明。</p><p>与此同时，检查实际播放结果还暴露了另一个问题：VIP 歌曲即使能返回名称、歌手和封面，也未必能提供真正可用的音频地址。这也把改造方向从“怎样选一个更完整的 API 歌单”，继续推向了“怎样让 API 歌曲和本地歌曲共存”。</p><hr /></section>
<section><h1>让 API 歌曲和本地歌曲同时存在<a href="#让-api-歌曲和本地歌曲同时存在"><span>#</span></a></h1><p>在确认歌单数量异常来自 API 回退链路后，我又碰到了真正促使我加入混合模式的情况：API 无法完整播放歌单里的 VIP 歌曲。</p><p>有些 VIP 歌曲能够正常返回名称、歌手和封面，看起来已经成功进入歌单，但真正点击播放时，API 提供的音频地址并不可用。只依赖联网接口，就意味着这些歌曲虽然“看得见”，却始终“听不到”。</p><p>播放器原本的类型定义只允许 <code>meting</code> 和 <code>local</code>：前者完全依赖联网 API，后者则只读取项目里的本地歌曲，两者只能选一个。</p><div><figure><figcaption><span>src/types/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 音乐播放器配置</span></div></div><div><div><div>2</div></div><div><span>export</span><span> </span><span>type</span><span> </span><span>MusicPlayerConfig</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>// 使用方式：'meting' 或 'local'</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>mode</span><span><span>?</span><span>:</span></span><span> </span><span>"meting"</span><span> </span><span>|</span><span> </span><span>"local"</span><span>; </span><span>// "meting" 使用 Meting API，"local" 使用本地音乐列表</span></div></div><div><div><div>5</div></div><div><span>  </span><span>// 使用方式：'meting'、'local' 或 'hybrid'</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>mode</span><span><span>?</span><span>:</span></span><span> </span><span>"meting"</span><span> </span><span>|</span><span> </span><span>"local"</span><span> </span><span>|</span><span> </span><span>"hybrid"</span><span>; </span><span>// "hybrid" 会将本地歌曲追加到 API 歌单末尾</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>// 默认音量 (0-1)</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>volume</span><span><span>?</span><span>:</span></span><span> </span><span>number</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这时我的需求就很明确了：普通歌曲继续由 API 管理，无法通过 API 正常播放的 VIP 歌曲则直接放到博客项目中。</p><p>于是我增加了第三种模式，并把博客当前使用的模式切换为 <code>hybrid</code>：</p><div><figure><figcaption><span>src/config/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 是否在侧边栏显示音乐播放器组件</span></div></div><div><div><div>2</div></div><div><span><span>showInSidebar</span><span>: </span></span><span>true</span><span>,</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 使用方式："meting" 使用 Meting API，"local" 使用本地音乐列表</span></div></div><div><div><div>5</div></div><div><span>mode</span><span>: </span><span>"meting"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>// 使用方式："meting" 使用 API，"local" 使用本地列表，"hybrid" 同时使用两者</span></div></div><div><div><div>7</div></div><div><span>mode</span><span>: </span><span>"hybrid"</span><span>,</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>// 默认音量 (0-1)</span></div></div><div><div><div>10</div></div><div><span><span>volume</span><span>: </span></span><span>0.7</span><span>,</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>混合模式会先获取 API 歌单，再把 <code>local.playlist</code> 中的歌曲追加到末尾：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>API 歌单</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>追加本地歌曲</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>最终播放列表</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对应的初始化逻辑也多出一个分支：API 成功时合并两份歌单，API 失败时只要还有本地歌曲，就不会让整个初始化过程失败。</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>try</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (</span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'meting'</span><span> </span><span>&amp;&amp;</span><span> </span><span>config</span><span>.</span><span>meting</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>await</span><span> </span><span>fetchMetingData</span><span>();</span></div></div><div><div><div>4</div></div><div><span><span><span>  </span></span><span>} </span></span><span>else</span><span> </span><span>if</span><span> (</span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'local'</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span> (</span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'meting'</span><span> </span><span>&amp;&amp;</span><span> </span><span>config</span><span>.</span><span>meting</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>await</span><span> </span><span>fetchMetingData</span><span>();</span></div></div><div><div><div>7</div></div><div><span><span><span>  </span></span><span>} </span></span><span>else</span><span> </span><span>if</span><span> (</span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'hybrid'</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>9</div></div><div><span>      </span><span>if</span><span> (</span><span>config</span><span>.</span><span>meting</span><span>) </span><span>await</span><span> </span><span>fetchMetingData</span><span>();</span></div></div><div><div><div>10</div></div><div><span><span><span>    </span></span><span>} </span></span><span>catch</span><span> (</span><span>e</span><span>) {</span></div></div><div><div><div>11</div></div><div><span>      </span><span>// 混合模式下 API 不可用时，仍允许本地歌曲正常工作。</span></div></div><div><div><div>12</div></div><div><span>      </span><span>if</span><span> (</span><span>config</span><span>.</span><span>localPlaylist</span><span>.</span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>throw</span><span> </span><span>e</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>warn</span><span>(</span><span>'Meting APIs failed, using local playlist only'</span><span>, </span><span>e</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span><span>    </span></span><span>}</span></span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>state</span><span>.</span><span>playlist</span><span> </span><span>=</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>concat</span><span>(</span><span>config</span><span>.</span><span>localPlaylist</span><span> </span><span>||</span><span> []);</span></div></div><div><div><div>17</div></div><div><span><span><span>  </span></span><span>} </span></span><span>else</span><span> </span><span>if</span><span> (</span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'local'</span><span>) {</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>state</span><span>.</span><span>playlist</span><span> </span><span>=</span><span><span> </span><span>config</span><span>.</span></span><span>localPlaylist</span><span> </span><span>||</span><span> [];</span></div></div><div><div><div>19</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>  </span><span>if</span><span><span> (</span><span>state</span><span>.</span></span><span>playlist</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>现阶段没有加入歌曲匹配、覆盖或去重逻辑。本地歌曲就是单纯地追加进去，配置也保持足够直观：</p><div><figure><figcaption><span>src/config/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>local</span><span>: {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>playlist</span><span>: [</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>4</div></div><div><span>      </span><span>name</span><span>:</span><span> </span><span>"萤火虫之舞"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>      </span><span>artist</span><span>:</span><span> </span><span>"萤火虫"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>      </span><span>url</span><span>:</span><span> </span><span>"/assets/music/萤火虫之舞.mp3"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>      </span><span>cover</span><span>:</span><span> </span><span>"/assets/music/cover/萤火虫之舞.jpg"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>      </span><span>lrc</span><span>:</span><span> </span><span>""</span><span>,</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>},</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>],</span></div></div><div><div><div>11</div></div><div><span>},</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Tip</div></div><div><p>本地音频、封面和歌词可以放在 <code>public/assets/music/</code> 下，并在配置中使用 <code>/assets/music/...</code> 路径。这样构建后仍能通过稳定的站内地址访问。</p></div></div><p>还有一个很重要的细节：如果 API 完全不可用，<code>hybrid</code> 模式仍然会继续初始化本地歌单。</p><p>这意味着第三方服务故障不会让整个播放器一起消失。至少本地保存的歌曲始终可以播放。</p><hr /></section>
<section><h1>“随机播放”其实不只是随机取一个数字<a href="#随机播放其实不只是随机取一个数字"><span>#</span></a></h1><p>整理完歌曲来源以后，我又注意到了播放器的随机模式。</p><p>原来的实现是每次需要下一首时，直接生成一个随机下标：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>nextIndex</span><span> </span></span><span>=</span><span><span> </span><span>Math</span><span>.</span></span><span>floor</span><span><span>(</span><span>Math</span><span>.</span></span><span>random</span><span>() </span><span>*</span><span><span> </span><span>state</span><span>.</span></span><span>playlist</span><span>.</span><span>length</span><span>);</span></div></div><div><div><div>2</div></div><div><span><span>prevIndex</span><span> </span></span><span>=</span><span><span> </span><span>Math</span><span>.</span></span><span>floor</span><span><span>(</span><span>Math</span><span>.</span></span><span>random</span><span>() </span><span>*</span><span><span> </span><span>state</span><span>.</span></span><span>playlist</span><span>.</span><span>length</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说，“下一首”和“上一首”都只是重新抽取一次。</p><p>从数学上看，它当然是随机的，但从听歌体验上看会有几个很明显的问题：</p><ul>
<li>当前歌曲可能马上再次出现；</li>
<li>有些歌曲短时间内连续出现；</li>
<li>有些歌曲可能一直抽不到；</li>
<li>点击“上一首”并不会回到刚才播放的歌曲。</li>
</ul><p>真正符合大多数人直觉的随机播放，通常不是“每次重新抽签”，而是先把整个歌单打乱，再按照打乱后的顺序播放。</p><p>因此，我没有直接替换旧逻辑，而是把随机策略做成了配置项：</p><div><figure><figcaption><span>src/types/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 播放模式：'list'=列表循环, 'one'=单曲循环, 'random'=随机播放</span></div></div><div><div><div>2</div></div><div><span>playMode</span><span>?:</span><span> </span><span>"list"</span><span> </span><span>|</span><span> </span><span>"one"</span><span> </span><span>|</span><span> </span><span>"random"</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 随机策略：'simple'=每次独立随机抽取, 'queue'=打乱队列且一轮内不重复</span></div></div><div><div><div>5</div></div><div><span>randomMode</span><span>?:</span><span> </span><span>"simple"</span><span> </span><span>|</span><span> </span><span>"queue"</span><span>;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 是否显示歌词</span></div></div><div><div><div>8</div></div><div><span>showLyrics</span><span>?:</span><span><span> </span><span>boolean</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中：</p><ul>
<li><code>simple</code> 保留原来的独立随机抽取；</li>
<li><code>queue</code> 使用打乱后的随机队列，一轮内不会重复。</li>
</ul><p>当前博客使用的是：</p><div><figure><figcaption><span>src/config/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span> </span><span>// 播放模式：'list'=列表循环, 'one'=单曲循环, 'random'=随机播放</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>playMode</span><span>: </span><span>"random"</span><span>,</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span> </span><span>// 随机策略：仅随机模式生效，'simple'=每次独立随机抽取, 'queue'=打乱队列且一轮内不重复</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>randomMode</span><span>: </span><span>"queue"</span><span>,</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// 是否启用歌词</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>showLyrics</span><span>: </span><span>false</span><span>,</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>队列通过 Fisher–Yates 算法打乱。播放完一轮后再生成新队列，同时排除当前歌曲，避免新一轮开始时立刻重复上一轮最后一首。</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>else</span><span> </span><span>if</span><span><span> (</span><span>config</span><span>.</span></span><span>playMode</span><span> </span><span>===</span><span> </span><span>'one'</span><span><span>) </span><span>state</span><span>.</span></span><span>playMode</span><span> </span><span>=</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>2</div></div><div><span>else</span><span><span> </span><span>state</span><span>.</span></span><span>playMode</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// Queue shuffle keeps every track unique within a round and records</span></div></div><div><div><div>5</div></div><div><span>// actual playback history so Previous can return to the prior track.</span></div></div><div><div><div>6</div></div><div><span>var</span><span> </span><span>randomQueue</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>7</div></div><div><span>var</span><span> </span><span>randomHistory</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>8</div></div><div><span>var</span><span> </span><span>randomHistoryStart</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>9</div></div><div><span>var</span><span> </span><span>randomHistoryCount</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>function</span><span> </span><span>resetRandomState</span><span>() {</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>randomQueue</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>randomHistory</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>randomHistoryStart</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>randomHistoryCount</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>function</span><span> </span><span>pushRandomHistory</span><span>(</span><span>index</span><span>) {</span></div></div><div><div><div>19</div></div><div><span>  </span><span>var</span><span> </span><span>limit</span><span> </span><span>=</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span>;</span></div></div><div><div><div>20</div></div><div><span>  </span><span>if</span><span> (</span><span>limit</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>return</span><span>;</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>  </span><span>if</span><span> (</span><span>randomHistoryCount</span><span> </span><span>&lt;</span><span> </span><span>limit</span><span>) {</span></div></div><div><div><div>23</div></div><div><span>    </span><span>var</span><span> </span><span>insertIndex</span><span> </span><span>=</span><span> (</span><span>randomHistoryStart</span><span> </span><span>+</span><span> </span><span>randomHistoryCount</span><span>) </span><span>%</span><span> </span><span>limit</span><span>;</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>randomHistory</span><span>[</span><span>insertIndex</span><span>] </span><span>=</span><span> </span><span>index</span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>randomHistoryCount</span><span>++</span><span>;</span></div></div><div><div><div>26</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>  </span><span>// Overwrite the oldest entry without shifting the entire array.</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>randomHistory</span><span>[</span><span>randomHistoryStart</span><span>] </span><span>=</span><span> </span><span>index</span><span>;</span></div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>randomHistoryStart</span><span> </span><span>=</span><span> (</span><span>randomHistoryStart</span><span> </span><span>+</span><span> </span><span>1</span><span>) </span><span>%</span><span> </span><span>limit</span><span>;</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>function</span><span> </span><span>popRandomHistory</span><span>() {</span></div></div><div><div><div>35</div></div><div><span>  </span><span>if</span><span> (</span><span>randomHistoryCount</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>  </span><span>var</span><span> </span><span>limit</span><span> </span><span>=</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span>;</span></div></div><div><div><div>38</div></div><div><span>  </span><span>var</span><span> </span><span>lastIndex</span><span> </span><span>=</span><span> (</span><span>randomHistoryStart</span><span> </span><span>+</span><span> </span><span>randomHistoryCount</span><span> </span><span>-</span><span> </span><span>1</span><span>) </span><span>%</span><span> </span><span>limit</span><span>;</span></div></div><div><div><div>39</div></div><div><span>  </span><span>var</span><span> </span><span>index</span><span> </span><span>=</span><span> </span><span>randomHistory</span><span>[</span><span>lastIndex</span><span>];</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>randomHistoryCount</span><span>--</span><span>;</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>  </span><span>if</span><span> (</span><span>randomHistoryCount</span><span> </span><span>===</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>randomHistory</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>randomHistoryStart</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>45</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span>  </span><span>return</span><span> </span><span>index</span><span>;</span></div></div><div><div><div>48</div></div><div><span>}</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>function</span><span> </span><span>refillRandomQueue</span><span>(</span><span>excludeIndex</span><span>) {</span></div></div><div><div><div>51</div></div><div><span>  </span><span>var</span><span> </span><span>indices</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>52</div></div><div><span>  </span><span>for</span><span> (</span><span>var</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>53</div></div><div><span>    </span><span>if</span><span> (</span><span>i</span><span> </span><span>!==</span><span> </span><span>excludeIndex</span><span>) </span><span>indices</span><span>.</span><span>push</span><span>(</span><span>i</span><span>);</span></div></div><div><div><div>54</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>  </span><span>// Fisher-Yates shuffle produces an unbiased random permutation.</span></div></div><div><div><div>57</div></div><div><span>  </span><span>for</span><span> (</span><span>var</span><span> </span><span>j</span><span> </span><span>=</span><span> </span><span>indices</span><span>.</span><span>length</span><span> </span><span>-</span><span> </span><span>1</span><span>; </span><span>j</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>; </span><span>j</span><span>--</span><span>) {</span></div></div><div><div><div>58</div></div><div><span>    </span><span>var</span><span> </span><span>swapIndex</span><span> </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>Math</span><span>.</span><span>random</span><span>() </span><span>*</span><span> (</span><span>j</span><span> </span><span>+</span><span> </span><span>1</span><span>));</span></div></div><div><div><div>59</div></div><div><span>    </span><span>var</span><span> </span><span>temp</span><span> </span><span>=</span><span> </span><span>indices</span><span>[</span><span>j</span><span>];</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>indices</span><span>[</span><span>j</span><span>] </span><span>=</span><span> </span><span>indices</span><span>[</span><span>swapIndex</span><span>];</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>indices</span><span>[</span><span>swapIndex</span><span>] </span><span>=</span><span> </span><span>temp</span><span>;</span></div></div><div><div><div>62</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span><span>  </span></span><span>randomQueue</span><span> </span><span>=</span><span> </span><span>indices</span><span>;</span></div></div><div><div><div>65</div></div><div><span>}</span></div></div><div><div><div>66</div></div><div>
</div></div><div><div><div>67</div></div><div><span>function</span><span> </span><span>getNextQueueIndex</span><span>() {</span></div></div><div><div><div>68</div></div><div><span>  </span><span>if</span><span> (</span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span> </span><span>&lt;=</span><span> </span><span>1</span><span>) </span><span>return</span><span> </span><span>state</span><span>.</span><span>currentIndex</span><span>;</span></div></div><div><div><div>69</div></div><div><span>  </span><span>if</span><span> (</span><span>randomQueue</span><span>.</span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>refillRandomQueue</span><span>(</span><span>state</span><span>.</span><span>currentIndex</span><span>);</span></div></div><div><div><div>70</div></div><div><span>  </span><span>return</span><span> </span><span>randomQueue</span><span>.</span><span>pop</span><span>();</span></div></div><div><div><div>71</div></div><div><span>}</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>// ── Event helpers ────────────────────────────────────────</span></div></div><div><div><div>74</div></div><div><span>function</span><span> </span><span>emit</span><span><span>(</span><span>name</span><span>, </span><span>detail</span><span>) {</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>下一首的逻辑则根据配置选择旧的独立抽取，或新的无重复队列：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>}</span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>nextIndex</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>if</span><span><span> (</span><span>state</span><span>.</span></span><span>playMode</span><span> </span><span>===</span><span> </span><span>2</span><span>) {</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>nextIndex</span><span> </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>Math</span><span>.</span><span>random</span><span>() </span><span>*</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span>);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span> (</span><span>config</span><span>.</span><span>randomMode</span><span> </span><span>===</span><span> </span><span>'queue'</span><span>) {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>nextIndex</span><span> </span><span>=</span><span> </span><span>getNextQueueIndex</span><span>();</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> (</span><span>nextIndex</span><span> </span><span>!==</span><span> </span><span>state</span><span>.</span><span>currentIndex</span><span>) </span><span>pushRandomHistory</span><span>(</span><span>state</span><span>.</span><span>currentIndex</span><span>);</span></div></div><div><div><div>8</div></div><div><span><span><span>  </span></span><span>} </span></span><span>else</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>nextIndex</span><span> </span><span>=</span><span> </span><span>Math</span><span>.</span><span>floor</span><span>(</span><span>Math</span><span>.</span><span>random</span><span>() </span><span>*</span><span> </span><span>state</span><span>.</span><span>playlist</span><span>.</span><span>length</span><span>);</span></div></div><div><div><div>10</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>11</div></div><div><span>} </span><span>else</span><span> {</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>nextIndex</span><span> </span><span>=</span><span><span> (</span><span>state</span><span>.</span></span><span>currentIndex</span><span> </span><span>+</span><span> </span><span>1</span><span>) </span><span>%</span><span><span> </span><span>state</span><span>.</span></span><span>playlist</span><span>.</span><span>length</span><span>;</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div><span>loadTrack</span><span><span>(</span><span>nextIndex</span><span>, </span></span><span>true</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>播放器还会记录真实的播放历史。因此在 <code>queue</code> 模式下，“上一首”终于拥有了符合直觉的含义：它会返回刚刚听过的歌曲，而不是再随机一次。</p><div><div><div></div><div>Tip</div></div><div><p>如果更在意“一轮之内每首歌都能听到”，推荐使用 <code>queue</code>；如果希望保留完全独立、允许短期重复的随机抽取，则可以继续使用 <code>simple</code>。</p></div></div><hr /></section>
<section><h1>大歌单下的随机队列性能<a href="#大歌单下的随机队列性能"><span>#</span></a></h1><p>随机队列完成以后，我又想到另一个问题：如果歌单特别大，会不会因为维护这个数组而变慢？</p><p>生成随机队列本身是 O(n)，而且每轮只执行一次，通常不是主要问题。真正值得注意的是取出队首时常用的 <code>shift()</code>：它需要移动后面的全部元素，如果从头到尾播放一个特别大的队列，累计复杂度可能接近 O(n²)。</p><p>因此，我在实现时选择了 <code>pop()</code>，而不是看起来更符合“队列”直觉的 <code>shift()</code>。歌单下标已经被完整打乱，从开头取和从末尾取并不会影响随机性；但 <code>pop()</code> 可以直接移除数组末尾的元素，不需要重新排列其余内容，正好避开了 <code>shift()</code> 的额外开销：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>function</span><span> </span><span>getNextQueueIndex</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span><span> (</span><span>state</span><span>.</span></span><span>playlist</span><span>.</span><span>length</span><span> </span><span>&lt;=</span><span> </span><span>1</span><span>) </span><span>return</span><span><span> </span><span>state</span><span>.</span></span><span>currentIndex</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>if</span><span><span> (</span><span>randomQueue</span><span>.</span></span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span>) </span><span>refillRandomQueue</span><span><span>(</span><span>state</span><span>.</span></span><span>currentIndex</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>return</span><span><span> </span><span>randomQueue</span><span>.</span></span><span>pop</span><span>();</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用户点击“上一首”时，当前歌曲也从末尾放回待播队列：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>config</span><span>.</span></span><span>randomMode</span><span> </span><span>===</span><span> </span><span>'queue'</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span><span> (</span><span>randomHistoryCount</span><span> </span></span><span>===</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>audio</span><span>.</span><span>currentTime</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>prevIndex</span><span> </span><span>=</span><span> </span><span>popRandomHistory</span><span>();</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>randomQueue</span><span>.</span><span>push</span><span><span>(</span><span>state</span><span>.</span></span><span>currentIndex</span><span>);</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样每次取歌和重新入队都变成了 O(1)。</p><p>播放历史则使用上文 diff 中的固定容量环形缓冲区，上限始终等于当前歌单长度。历史达到上限后，新记录会直接覆盖最旧记录，不会无限增长，也不需要为了删除第一项而移动整个数组。</p><p>对于普通的小歌单，这种优化几乎感觉不到差别；但既然随机队列本身很容易写成稳定的线性方案，就没有必要留下一个会随歌曲数量放大的隐患。</p><hr /></section>
<section><h1>API 请求方式也应该由配置决定<a href="#api-请求方式也应该由配置决定"><span>#</span></a></h1><p>这里回到了文章开头暂时留下的问题：当多个 API 返回的歌单数量不一致时，播放器究竟应该优先速度，还是优先完整性？</p><p>要回答它，其实需要拆开两个维度：第一是“允许请求哪些接口”，第二是“拿到多个有效结果后选择哪一个”。播放器原来会固定按照“主 API → 备用 API”的顺序请求，这种方式容错能力不错，但并不适合所有部署环境。</p><p>有时我只想使用自己信任的一个接口；有时则希望某个接口失效后自动切换。于是我把请求方式也加入了配置：</p><div><div><div><figure><figcaption><span>src/types/musicConfig.ts</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>meting</span><span>?:</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>// Meting API 地址</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>api?</span><span>:</span><span><span> </span><span>string</span><span>;</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>// API 请求方式：'sequential'=依次尝试主接口和备用接口, 'single'=仅使用主接口</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>requestMode</span><span>?:</span><span> </span><span>"sequential"</span><span> </span><span>|</span><span> </span><span>"single"</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>// 多 API 歌单选择：'first'=首个有效结果, 'largest'=并行请求并选择歌曲最多的结果</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>apiSelectionMode</span><span>?:</span><span> </span><span>"first"</span><span> </span><span>|</span><span> </span><span>"largest"</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>  </span><span>// 每个 API 请求的超时时间（毫秒）</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>timeoutMs</span><span>?:</span><span> </span><span>number</span><span>;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>  </span><span>// 音乐平台：netease=网易云音乐, tencent=QQ音乐, kugou=酷狗音乐, xiami=虾米音乐, baidu=百度音乐</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>server</span><span>?:</span><span> </span><span>"netease"</span><span> </span><span>|</span><span> </span><span>"tencent"</span><span> </span><span>|</span><span> </span><span>"kugou"</span><span> </span><span>|</span><span> </span><span>"xiami"</span><span> </span><span>|</span><span> </span><span>"baidu"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>两种模式的行为分别是：</p>

<table><thead><tr><th>模式</th><th>行为</th></tr></thead><tbody><tr><td><code>sequential</code></td><td>依次尝试主 API 和 <code>fallbackApis</code></td></tr><tr><td><code>single</code></td><td>只请求主 API，不尝试备用歌单接口</td></tr></tbody></table><p><code>requestMode</code> 解决了第一个维度，但允许使用多个 API 以后，第二个问题仍然存在：应该采用哪一个接口返回的歌单？</p><p>如果仍然沿用“遇到第一个非空数组就停止”的判断，就会再次落回文章开头的情况：源歌单有 7 首，播放器却因为先收到一份有效但不完整的响应，最终只拿到 3 首。</p><p><code>first</code> 模式保留的正是这段提前返回逻辑：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> (</span><span>var</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>; </span><span>i</span><span> </span></span><span>&lt;</span><span><span> </span><span>apis</span><span>.</span></span><span>length</span><span><span>; </span><span>i</span></span><span>++</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>var</span><span><span> </span><span>baseApi</span><span> </span></span><span>=</span><span><span> </span><span>apis</span><span>[</span><span>i</span><span>];</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>var</span><span><span> </span><span>data</span><span> </span></span><span>=</span><span> </span><span>await</span><span> </span><span>fetchPlaylistFromApi</span><span><span>(</span><span>baseApi</span><span>, </span><span>m</span><span>, </span><span>timeoutMs</span><span>);</span></span></div></div><div><div><div>5</div></div><div><span>    </span><span>applyMetingPlaylist</span><span><span>(</span><span>data</span><span>);</span></span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span><span> (</span><span>e</span><span> </span></span><span>&amp;&amp;</span><span><span> </span><span>e</span><span>.</span></span><span>isTimeout</span><span><span>) </span><span>hasTimeout</span><span> </span></span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>console</span><span>.</span><span>warn</span><span>(</span><span>'Meting API failed for '</span><span> </span><span>+</span><span><span> </span><span>baseApi</span><span>, </span><span>e</span><span>);</span></span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>因此，我又增加了上面类型定义中与 <code>requestMode</code> 并列的 <code>apiSelectionMode</code>，让“请求范围”和“结果选择”保持相互独立：</p>

<table><thead><tr><th>策略</th><th>行为</th></tr></thead><tbody><tr><td><code>first</code></td><td>按配置顺序请求，使用第一个有效结果并立即停止</td></tr><tr><td><code>largest</code></td><td>并行请求全部可用接口，采用歌曲数量最多的结果</td></tr></tbody></table><p><code>first</code> 适合歌单相对稳定、希望减少请求和等待时间的情况；<code>largest</code> 则更适合刚更新过歌单，或者不同第三方接口经常出现数据不同步的情况。</p><p>这两个配置最终会先决定候选接口，再决定串行提前返回，还是并行比较结果：</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fetchMetingData</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>config</span><span>.</span></span><span>meting</span><span>) </span><span>return</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span><span> </span><span>m</span><span> </span></span><span>=</span><span><span> </span><span>config</span><span>.</span></span><span>meting</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>var</span><span> </span><span>apis</span><span> </span><span>=</span><span> [</span><span>m</span><span>.</span><span>api</span><span>].</span><span>concat</span><span>(</span><span>m</span><span>.</span><span>fallbackApis</span><span> </span><span>||</span><span> []);</span></div></div><div><div><div>5</div></div><div><span>  </span><span>var</span><span> </span><span>apis</span><span> </span><span>=</span><span> </span><span>m</span><span>.</span><span>requestMode</span><span> </span><span>===</span><span> </span><span>'single'</span></div></div><div><div><div>6</div></div><div><span>    </span><span>?</span><span> [</span><span>m</span><span>.</span><span>api</span><span>]</span></div></div><div><div><div>7</div></div><div><span>    </span><span>:</span><span> [</span><span>m</span><span>.</span><span>api</span><span>].</span><span>concat</span><span>(</span><span>m</span><span>.</span><span>fallbackApis</span><span> </span><span>||</span><span> []);</span></div></div><div><div><div>8</div></div><div><span>  </span><span>var</span><span> </span><span>timeoutMs</span><span> </span><span>=</span><span> </span><span>Number</span><span>(</span><span>m</span><span>.</span><span>timeoutMs</span><span>);</span></div></div><div><div><div>9</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>Number</span><span>.</span><span>isFinite</span><span>(</span><span>timeoutMs</span><span>) </span><span>||</span><span> </span><span>timeoutMs</span><span> </span><span>&lt;=</span><span> </span><span>0</span><span>) </span><span>timeoutMs</span><span> </span><span>=</span><span> </span><span>8000</span><span>;</span></div></div><div><div><div>10</div></div><div><span>  </span><span>var</span><span> </span><span>hasTimeout</span><span> </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>apis</span><span> </span><span>=</span><span> </span><span>apis</span><span>.</span><span>filter</span><span>(</span><span>function</span><span> (</span><span>api</span><span>) { </span><span>return</span><span> </span><span>!!</span><span>api</span><span>; });</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>  </span><span>if</span><span> (</span><span>m</span><span>.</span><span>apiSelectionMode</span><span> </span><span>===</span><span> </span><span>'largest'</span><span> </span><span>&amp;&amp;</span><span> </span><span>apis</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>1</span><span>) {</span></div></div><div><div><div>14</div></div><div><span>    </span><span>var</span><span> </span><span>results</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>Promise</span><span>.</span><span>allSettled</span><span>(</span><span>apis</span><span>.</span><span>map</span><span>(</span><span>function</span><span> (</span><span>baseApi</span><span>) {</span></div></div><div><div><div>15</div></div><div><span>      </span><span>return</span><span> </span><span>fetchPlaylistFromApi</span><span>(</span><span>baseApi</span><span>, </span><span>m</span><span>, </span><span>timeoutMs</span><span>);</span></div></div><div><div><div>16</div></div><div><span><span><span>    </span></span><span>}));</span></span></div></div><div><div><div>17</div></div><div><span>    </span><span>var</span><span> </span><span>largestPlaylist</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>results</span><span>.</span><span>forEach</span><span>(</span><span>function</span><span> (</span><span>result</span><span>, </span><span>index</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>      </span><span>if</span><span> (</span><span>result</span><span>.</span><span>status</span><span> </span><span>===</span><span> </span><span>'fulfilled'</span><span>) {</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>largestPlaylist</span><span> </span><span>||</span><span> </span><span>result</span><span>.</span><span>value</span><span>.</span><span>length</span><span> </span><span>&gt;</span><span> </span><span>largestPlaylist</span><span>.</span><span>length</span><span>) {</span></div></div><div><div><div>22</div></div><div><span><span>          </span></span><span>largestPlaylist</span><span> </span><span>=</span><span> </span><span>result</span><span>.</span><span>value</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span><span>        </span></span><span>}</span></span></div></div><div><div><div>24</div></div><div><span>        </span><span>return</span><span>;</span></div></div><div><div><div>25</div></div><div><span><span><span>      </span></span><span>}</span></span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>      </span><span>if</span><span> (</span><span>result</span><span>.</span><span>reason</span><span> </span><span>&amp;&amp;</span><span> </span><span>result</span><span>.</span><span>reason</span><span>.</span><span>isTimeout</span><span>) </span><span>hasTimeout</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>console</span><span>.</span><span>warn</span><span>(</span><span>'Meting API failed for '</span><span> </span><span>+</span><span> </span><span>apis</span><span>[</span><span>index</span><span>], </span><span>result</span><span>.</span><span>reason</span><span>);</span></div></div><div><div><div>29</div></div><div><span><span><span>    </span></span><span>});</span></span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>if</span><span> (</span><span>largestPlaylist</span><span>) {</span></div></div><div><div><div>32</div></div><div><span>      </span><span>applyMetingPlaylist</span><span>(</span><span>largestPlaylist</span><span>);</span></div></div><div><div><div>33</div></div><div><span>      </span><span>return</span><span>;</span></div></div><div><div><div>34</div></div><div><span><span><span>    </span></span><span>}</span></span></div></div><div><div><div>35</div></div><div><span>    </span><span>throw</span><span> </span><span>createMetingFetchError</span><span>(</span><span>hasTimeout</span><span>);</span></div></div><div><div><div>36</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>  </span><span>for</span><span> (</span><span>var</span><span><span> </span><span>i</span><span> </span></span><span>=</span><span> </span><span>0</span><span><span>; </span><span>i</span><span> </span></span><span>&lt;</span><span><span> </span><span>apis</span><span>.</span></span><span>length</span><span><span>; </span><span>i</span></span><span>++</span><span>) {</span></div></div><div><div><div>39</div></div><div><span>    </span><span>var</span><span><span> </span><span>baseApi</span><span> </span></span><span>=</span><span><span> </span><span>apis</span><span>[</span><span>i</span><span>];</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div></div><div>Tip</div></div><div><p><code>requestMode: "single"</code> 时只存在一个候选 API，因此两种选择策略的结果相同，也不会额外产生请求。只有在 <code>sequential</code> 配置了多个接口时，<code>largest</code> 才会请求并比较全部结果。</p></div></div><p>同时，上面一并加入的 <code>timeoutMs</code> 可以单独控制每个 API 请求的超时时间。在实际配置中，这三个选项紧跟在主 API 地址之后：</p><div><figure><figcaption><span>src/config/musicConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 默认使用官方 API，也可以使用自定义 API</span></div></div><div><div><div>2</div></div><div><span><span>api</span><span>: </span></span><span>"https://api.moeyao.cn/meting/?server=:server&amp;type=:type&amp;id=:id"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>// API 请求方式："sequential" 依次尝试主接口和备用接口，"single" 仅使用主接口</span></div></div><div><div><div>4</div></div><div><span>requestMode</span><span>: </span><span>"sequential"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>// 多 API 歌单选择：仅在 sequential 模式生效。"first" 使用首个有效结果，"largest" 请求全部并选择歌曲最多的结果</span></div></div><div><div><div>6</div></div><div><span>apiSelectionMode</span><span>: </span><span>"first"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>// 每个 API 请求的超时时间（毫秒）</span></div></div><div><div><div>8</div></div><div><span>timeoutMs</span><span>: </span><span>10000</span><span>,</span></div></div><div><div><div>9</div></div><div><span>// 音乐平台：netease=网易云音乐, tencent=QQ音乐, kugou=酷狗音乐, xiami=虾米音乐, baidu=百度音乐</span></div></div><div><div><div>10</div></div><div><span><span>server</span><span>: </span></span><span>"netease"</span><span>,</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>超时范围不仅包括建立连接，也包括接收响应正文和解析 JSON。这样可以避免服务器只返回响应头、正文却迟迟不结束时，播放器仍然无限等待。</p><p>这里没有使用只包住 <code>fetch()</code> 的计时，而是让同一个 <code>AbortController</code> 一直覆盖到 <code>res.json()</code> 完成：</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// ── Meting fetch ─────────────────────────────────────────</span></div></div><div><div><div>2</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fetchMetingJson</span><span>(</span><span>fetchUrl</span><span>, </span><span>timeoutMs</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span> </span><span>controller</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>AbortController</span><span>();</span></div></div><div><div><div>4</div></div><div><span>  </span><span>var</span><span> </span><span>timeoutId</span><span> </span><span>=</span><span> </span><span>setTimeout</span><span>(</span><span>function</span><span> () {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>controller</span><span>.</span><span>abort</span><span>();</span></div></div><div><div><div>6</div></div><div><span><span><span>  </span></span><span>}, </span></span><span>timeoutMs</span><span>);</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>var</span><span> </span><span>res</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>fetchUrl</span><span>, { </span><span>signal</span><span>:</span><span> </span><span>controller</span><span>.</span><span>signal</span><span> });</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>res</span><span>.</span><span>ok</span><span>) </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>'HTTP '</span><span> </span><span>+</span><span> </span><span>res</span><span>.</span><span>status</span><span>);</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> </span><span>await</span><span> </span><span>res</span><span>.</span><span>json</span><span>();</span></div></div><div><div><div>12</div></div><div><span><span><span>  </span></span><span>} </span></span><span>catch</span><span> (</span><span>e</span><span>) {</span></div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span> (</span><span>controller</span><span>.</span><span>signal</span><span>.</span><span>aborted</span><span>) {</span></div></div><div><div><div>14</div></div><div><span>      </span><span>var</span><span> </span><span>timeoutError</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>'Meting API request timed out'</span><span>);</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>timeoutError</span><span>.</span><span>isTimeout</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>16</div></div><div><span>      </span><span>throw</span><span> </span><span>timeoutError</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span><span>    </span></span><span>}</span></span></div></div><div><div><div>18</div></div><div><span>    </span><span>throw</span><span> </span><span>e</span><span>;</span></div></div><div><div><div>19</div></div><div><span><span><span>  </span></span><span>} </span></span><span>finally</span><span> {</span></div></div><div><div><div>20</div></div><div><span>    </span><span>clearTimeout</span><span>(</span><span>timeoutId</span><span>);</span></div></div><div><div><div>21</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>22</div></div><div><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>fetchPlaylistFromApi</span><span>(</span><span>baseApi</span><span>, </span><span>m</span><span>, </span><span>timeoutMs</span><span>) {</span></div></div><div><div><div>25</div></div><div><span>  </span><span>var</span><span> </span><span>fetchUrl</span><span> </span><span>=</span><span> </span><span>baseApi</span></div></div><div><div><div>26</div></div><div><span><span><span>    </span></span><span>.</span></span><span>replace</span><span>(</span><span>':server'</span><span>, </span><span>m</span><span>.</span><span>server</span><span>)</span></div></div><div><div><div>27</div></div><div><span><span><span>    </span></span><span>.</span></span><span>replace</span><span>(</span><span>':type'</span><span>, </span><span>m</span><span>.</span><span>type</span><span>)</span></div></div><div><div><div>28</div></div><div><span><span><span>    </span></span><span>.</span></span><span>replace</span><span>(</span><span>':id'</span><span>, </span><span>m</span><span>.</span><span>id</span><span>)</span></div></div><div><div><div>29</div></div><div><span><span><span>    </span></span><span>.</span></span><span>replace</span><span>(</span><span>':r'</span><span>, </span><span>Math</span><span>.</span><span>random</span><span>());</span></div></div><div><div><div>30</div></div><div><span>  </span><span>if</span><span> (</span><span>m</span><span>.</span><span>auth</span><span>) </span><span>fetchUrl</span><span> </span><span>+=</span><span> </span><span>'&amp;auth='</span><span> </span><span>+</span><span> </span><span>m</span><span>.</span><span>auth</span><span>;</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>  </span><span>var</span><span> </span><span>data</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetchMetingJson</span><span>(</span><span>fetchUrl</span><span>, </span><span>timeoutMs</span><span>);</span></div></div><div><div><div>33</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>Array</span><span>.</span><span>isArray</span><span>(</span><span>data</span><span>) </span><span>||</span><span> </span><span>data</span><span>.</span><span>length</span><span> </span><span>===</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>34</div></div><div><span>    </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>'Meting API returned an empty playlist'</span><span>);</span></div></div><div><div><div>35</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>36</div></div><div><span>  </span><span>return</span><span> </span><span>data</span><span>;</span></div></div><div><div><div>37</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div></div><div>Note</div></div><div><p><code>timeoutMs</code> 是单个 API 请求的超时时间。<code>first</code> 会依次等待各个接口，因此总等待时间可能高于这个值；<code>largest</code> 会并行请求全部接口，整体等待时间通常接近一次超时周期，但会产生更多请求。</p></div></div><div><div><div></div><div>Important</div></div><div><p><code>requestMode</code> 只控制“获取歌单”时使用几个 API。歌曲进入播放列表后，如果具体音频地址播放失败，播放器原有的备用音频地址切换逻辑仍然独立工作。因此，<code>single</code> 当前表示“只用一个 API 获取歌单”，歌曲获取失败依旧会尝试其它 API。</p></div></div><p>具体来说，加载歌曲时仍会根据歌曲 ID 为当前曲目准备备用播放地址；这段逻辑不读取 <code>requestMode</code>：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>matchId</span><span> </span></span><span>&amp;&amp;</span><span><span> </span><span>matchServer</span><span> </span></span><span>&amp;&amp;</span><span><span> </span><span>config</span><span>.</span></span><span>meting</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>config</span><span>.</span></span><span>meting</span><span>.</span><span>fallbackApis</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>config</span><span>.</span><span>meting</span><span>.</span><span>fallbackApis</span><span>.</span><span>forEach</span><span>(</span><span>function</span><span><span> (</span><span>fallback</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>var</span><span><span> </span><span>fallbackUrl</span><span> </span></span><span>=</span><span><span> </span><span>fallback</span></span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>.</span><span>replace</span><span>(</span><span>':server'</span><span><span>, </span><span>matchServer</span><span>[</span></span><span>1</span><span>])</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>.</span><span>replace</span><span>(</span><span>':type'</span><span>, </span><span>'url'</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>.</span><span>replace</span><span>(</span><span>':id'</span><span><span>, </span><span>matchId</span><span>[</span></span><span>1</span><span>]);</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span><span> (</span><span>currentTrackUrls</span><span>.</span></span><span>indexOf</span><span><span>(</span><span>fallbackUrl</span><span>) </span></span><span>===</span><span> </span><span>-</span><span>1</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>currentTrackUrls</span><span>.</span><span>push</span><span><span>(</span><span>fallbackUrl</span><span>);</span></span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>纯 API 模式超时后可以手动重试<a href="#纯-api-模式超时后可以手动重试"><span>#</span></a></h1><p>如果使用纯 <code>meting</code> 模式，默认便不会读取本地歌曲了。那么请求超时以后，只显示一句“播放器错误”显然不够友好。</p><p>现在，当允许使用的接口没有成功返回，并且请求链路中发生了超时时，播放器面板会显示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>歌单请求超时</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>[重新获取]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>点击按钮后，播放器会重新执行完整的歌单请求流程。导航栏和侧边栏中的播放器共用同一个管理器，因此加载和重试状态也会同步。</p><p>管理器会把“是否可重试”作为状态的一部分发给界面，并允许强制重新初始化：</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>} </span><span>catch</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>console</span><span>.</span><span>error</span><span>(</span><span>'Music Manager init error:'</span><span><span>, </span><span>e</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span> </span><span>isTimeout</span><span> </span><span>=</span><span> </span><span>!!</span><span>(</span><span>e</span><span> </span><span>&amp;&amp;</span><span> </span><span>e</span><span>.</span><span>isTimeout</span><span>);</span></div></div><div><div><div>4</div></div><div><span>  </span><span>var</span><span> </span><span>errorMessage</span><span> </span><span>=</span><span> </span><span>isTimeout</span><span> </span><span>?</span><span> </span><span>config</span><span>.</span><span>i18n</span><span>.</span><span>timeout</span><span> </span><span>:</span><span> </span><span>config</span><span>.</span><span>i18n</span><span>.</span><span>error</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>error</span><span> </span><span>=</span><span> </span><span>errorMessage</span><span>;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>errorRetryable</span><span> </span><span>=</span><span> </span><span>config</span><span>.</span><span>mode</span><span> </span><span>===</span><span> </span><span>'meting'</span><span> </span><span>&amp;&amp;</span><span> </span><span>isTimeout</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>initialized</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>8</div></div><div><span>  </span><span>emit</span><span>(</span><span>'fm:init'</span><span>, {</span></div></div><div><div><div>9</div></div><div><span>    </span><span>playlist</span><span>:</span><span> [],</span></div></div><div><div><div>10</div></div><div><span>    </span><span>playMode</span><span>:</span><span><span> </span><span>state</span><span>.</span></span><span>playMode</span><span>,</span></div></div><div><div><div>11</div></div><div><span>    </span><span>volume</span><span>:</span><span><span> </span><span>state</span><span>.</span></span><span>volume</span><span>,</span></div></div><div><div><div>12</div></div><div><span>    </span><span>isMuted</span><span>:</span><span><span> </span><span>state</span><span>.</span></span><span>isMuted</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>14</div></div><div><span>  </span><span>emit</span><span>(</span><span>'fm:error'</span><span>, { </span><span>message</span><span>:</span><span> </span><span>config</span><span>.</span><span>i18n</span><span>.</span><span>error</span><span> });</span></div></div><div><div><div>15</div></div><div><span>  </span><span>emit</span><span>(</span><span>'fm:error'</span><span>, {</span></div></div><div><div><div>16</div></div><div><span>    </span><span>message</span><span>:</span><span> </span><span>errorMessage</span><span>,</span></div></div><div><div><div>17</div></div><div><span>    </span><span>retryable</span><span>:</span><span> </span><span>state</span><span>.</span><span>errorRetryable</span><span>,</span></div></div><div><div><div>18</div></div><div><span>    </span><span>reason</span><span>:</span><span> </span><span>isTimeout</span><span> </span><span>?</span><span> </span><span>'timeout'</span><span> </span><span>:</span><span> </span><span>'error'</span></div></div><div><div><div>19</div></div><div><span><span><span>  </span></span><span>});</span></span></div></div><div><div><div>20</div></div><div><span>} </span><span>finally</span><span> {</span></div></div><div><div><div>21</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>initializing</span><span> </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>22</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// ── Public API ───────────────────────────────────────────</span></div></div><div><div><div>2</div></div><div><span><span>window</span><span>.</span></span><span>__fireflyMusic</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>init</span><span>:</span><span><span> </span><span>init</span><span>,</span></span></div></div><div><div><div>4</div></div><div><span>  </span><span>retry</span><span>:</span><span> </span><span>function</span><span> () { </span><span>return</span><span> </span><span>init</span><span>(</span><span>true</span><span>); },</span></div></div><div><div><div>5</div></div><div><span>  </span><span>getState</span><span>:</span><span> </span><span>function</span><span> () {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>var</span><span><span> </span><span>track</span><span> </span></span><span>=</span><span><span> </span><span>state</span><span>.</span></span><span>playlist</span><span><span>[</span><span>state</span><span>.</span></span><span>currentIndex</span><span>] </span><span>||</span><span> </span><span>null</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>播放器面板收到可重试错误后，不再只替换标题，而是显示重试区域：</p><div><div><div><figure><figcaption><span>src/components/features/MusicPlayerView.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>on</span><span>(</span><span>'fm:lrc-index'</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span>  </span><span>updateLrcHighlight</span><span><span>(</span><span>e</span><span>.</span></span><span>detail</span><span>.</span><span>index</span><span>);</span></div></div><div><div><div>3</div></div><div><span>});</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>on</span><span>(</span><span>'fm:error'</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>if</span><span> (</span><span>e</span><span>.</span><span>detail</span><span>.</span><span>retryable</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>    </span><span>showRetry</span><span>(</span><span>e</span><span>.</span><span>detail</span><span>.</span><span>message</span><span>);</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span>;</span></div></div><div><div><div>9</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>ui</span><span>.</span><span>title</span><span>.</span><span>innerText</span><span> </span><span>=</span><span><span> </span><span>e</span><span>.</span></span><span>detail</span><span>.</span><span>message</span><span> </span><span>||</span><span><span> </span><span>cfg</span><span>.</span></span><span>i18n</span><span>.</span><span>error</span><span>;</span></div></div><div><div><div>11</div></div><div><span>});</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>// ── Button click delegates ───────────────────────────────</span></div></div><div><div><div>14</div></div><div><span><span>ui</span><span>.</span></span><span>btnPlay</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> () { </span><span>mgr</span><span>.</span></span><span>togglePlay</span><span>(); });</span></div></div><div><div><div>15</div></div><div><span><span>ui</span><span>.</span></span><span>btnNext</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> () { </span><span>mgr</span><span>.</span></span><span>playNext</span><span>(); });</span></div></div><div><div><div>16</div></div><div><span><span>ui</span><span>.</span></span><span>btnPrev</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> () { </span><span>mgr</span><span>.</span></span><span>playPrev</span><span>(); });</span></div></div><div><div><div>17</div></div><div><span><span>ui</span><span>.</span></span><span>btnRepeat</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> () { </span><span>mgr</span><span>.</span></span><span>cyclePlayMode</span><span>(); });</span></div></div><div><div><div>18</div></div><div><span><span>ui</span><span>.</span></span><span>btnMute</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> () { </span><span>mgr</span><span>.</span></span><span>toggleMute</span><span>(); });</span></div></div><div><div><div>19</div></div><div><span>ui</span><span>.</span><span>btnRetry</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span> () { </span><span>mgr</span><span>.</span><span>retry</span><span>(); });</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span><span>ui</span><span>.</span></span><span>volContainer</span><span>.</span><span>addEventListener</span><span>(</span><span>'click'</span><span>, </span><span>function</span><span><span> (</span><span>e</span><span>) {</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>在 <code>hybrid</code> 模式下，处理方式则不同。API 超时不会弹出一个阻塞整个播放器的错误面板，而是直接读取本地歌曲播放，不再请求 API。</p><p>这两种行为分别对应了两种不同的事实：</p><ul>
<li><code>meting</code> 超时以后确实没有歌曲可播，需要用户重试；</li>
<li><code>hybrid</code> 即使联网失败，本地歌曲仍然是有效内容，不应该被错误面板挡住。</li>
</ul><p>这里之所以只在纯 API 模式开放重试，也是由这一行判断直接决定的：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>state</span><span>.</span></span><span>errorRetryable</span><span> </span><span>=</span><span><span> </span><span>config</span><span>.</span></span><span>mode</span><span> </span><span>===</span><span> </span><span>'meting'</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>isTimeout</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>快速切歌时，新歌词可能被旧歌词替换<a href="#快速切歌时新歌词可能被旧歌词替换"><span>#</span></a></h1><p>最后一个修复与歌词有关。</p><p>播放器加载在线歌词时使用异步请求。如果连续快速切换歌曲，可能出现这样的顺序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>开始请求歌曲 A 的歌词</span></div></div><div><div><div>2</div></div><div><span>切换到歌曲 B</span></div></div><div><div><div>3</div></div><div><span>开始请求歌曲 B 的歌词</span></div></div><div><div><div>4</div></div><div><span>歌曲 B 的歌词先返回</span></div></div><div><div><div>5</div></div><div><span>歌曲 A 的歌词后返回</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果没有额外保护，最后返回的 A 歌词会覆盖当前正在播放的 B。</p><p>现在，每次切歌都会取消上一首尚未完成的歌词请求，并增加一个歌词加载版本号：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>切换歌曲</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>├─ AbortController 取消旧请求</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>└─ lyricsLoadVersion + 1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>歌词响应返回时，还要再次确认自己的版本号仍然属于当前歌曲。只有两个条件都满足，结果才允许写入播放器状态。</p><p>同时，歌词请求现在也会检查 HTTP 状态。服务端返回错误页面时，不会再把错误正文当成正常歌词处理。</p><p>对应的修改集中在同一条异步链路中。首先，在歌词函数前保存当前请求及版本，并在每次加载开始时让旧请求失效：</p><div><figure><figcaption><span>src/components/features/MusicManager.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// ── Lyrics ───────────────────────────────────────────────</span></div></div><div><div><div>2</div></div><div><span>var</span><span> </span><span>lyricsLoadVersion</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>3</div></div><div><span>var</span><span> </span><span>lyricsController</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>function</span><span> </span><span>loadLyrics</span><span><span>(</span><span>track</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span>  </span><span>var</span><span> </span><span>version</span><span> </span><span>=</span><span> </span><span>++</span><span>lyricsLoadVersion</span><span>;</span></div></div><div><div><div>7</div></div><div><span>  </span><span>if</span><span> (</span><span>lyricsController</span><span>) {</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>lyricsController</span><span>.</span><span>abort</span><span>();</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>lyricsController</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>lyrics</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>state</span><span>.</span><span>currentLrcIndex</span><span> </span><span>=</span><span> </span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>track</span><span>.</span></span><span>lrc</span><span>) {</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>真正发起网络请求时，再把控制器交给 <code>fetch()</code>，并在写入状态前核对版本：</p><div><div><div><figure><figcaption><span>src/components/features/MusicManager.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>isLrcUrl</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>lyricsController</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>AbortController</span><span>();</span></div></div><div><div><div>3</div></div><div><span>  </span><span>var</span><span> </span><span>controller</span><span> </span><span>=</span><span> </span><span>lyricsController</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>emit</span><span>(</span><span>'fm:lyrics'</span><span>, { </span><span>lyrics</span><span>:</span><span> [], </span><span>status</span><span>:</span><span> </span><span>'loading'</span><span> });</span></div></div><div><div><div>5</div></div><div><span>  </span><span>fetch</span><span>(</span><span>track</span><span>.</span><span>lrc</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span><span>    </span></span><span>.</span></span><span>then</span><span>(</span><span>function</span><span> (</span><span>r</span><span>) { </span><span>return</span><span> </span><span>r</span><span>.</span><span>text</span><span>(); })</span></div></div><div><div><div>7</div></div><div><span>  </span><span>fetch</span><span>(</span><span>track</span><span>.</span><span>lrc</span><span>, { </span><span>signal</span><span>:</span><span> </span><span>controller</span><span>.</span><span>signal</span><span> })</span></div></div><div><div><div>8</div></div><div><span><span><span>    </span></span><span>.</span></span><span>then</span><span>(</span><span>function</span><span> (</span><span>r</span><span>) {</span></div></div><div><div><div>9</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>r</span><span>.</span><span>ok</span><span>) </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>'HTTP '</span><span> </span><span>+</span><span> </span><span>r</span><span>.</span><span>status</span><span>);</span></div></div><div><div><div>10</div></div><div><span>      </span><span>return</span><span> </span><span>r</span><span>.</span><span>text</span><span>();</span></div></div><div><div><div>11</div></div><div><span><span><span>    </span></span><span>})</span></span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>.</span><span>then</span><span>(</span><span>function</span><span><span> (</span><span>text</span><span>) {</span></span></div></div><div><div><div>13</div></div><div><span>      </span><span>if</span><span> (</span><span>version</span><span> </span><span>!==</span><span> </span><span>lyricsLoadVersion</span><span>) </span><span>return</span><span>;</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>state</span><span>.</span><span>lyrics</span><span> </span><span>=</span><span> </span><span>parseLRC</span><span><span>(</span><span>text</span><span>);</span></span></div></div><div><div><div>15</div></div><div><span>      </span><span>emit</span><span>(</span><span>'fm:lyrics'</span><span>, { </span><span>lyrics</span><span>:</span><span><span> </span><span>state</span><span>.</span></span><span>lyrics</span><span>, </span><span>status</span><span>:</span><span> </span><span>'loaded'</span><span> });</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>17</div></div><div><span><span><span>    </span></span><span>.</span></span><span>catch</span><span>(</span><span>function</span><span> () {</span></div></div><div><div><div>18</div></div><div><span><span><span>    </span></span><span>.</span></span><span>catch</span><span>(</span><span>function</span><span> (</span><span>e</span><span>) {</span></div></div><div><div><div>19</div></div><div><span>      </span><span>if</span><span> (</span><span>version</span><span> </span><span>!==</span><span> </span><span>lyricsLoadVersion</span><span> </span><span>||</span><span> (</span><span>e</span><span> </span><span>&amp;&amp;</span><span> </span><span>e</span><span>.</span><span>name</span><span> </span><span>===</span><span> </span><span>'AbortError'</span><span>)) </span><span>return</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>state</span><span>.</span><span>lyrics</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>21</div></div><div><span>      </span><span>emit</span><span>(</span><span>'fm:lyrics'</span><span>, { </span><span>lyrics</span><span>:</span><span> [], </span><span>status</span><span>:</span><span> </span><span>'failed'</span><span> });</span></div></div><div><div><div>22</div></div><div><span><span><span>    </span></span><span>})</span></span></div></div><div><div><div>23</div></div><div><span><span><span>    </span></span><span>.</span></span><span>finally</span><span>(</span><span>function</span><span> () {</span></div></div><div><div><div>24</div></div><div><span>      </span><span>if</span><span> (</span><span>version</span><span> </span><span>===</span><span> </span><span>lyricsLoadVersion</span><span>) </span><span>lyricsController</span><span> </span><span>=</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>26</div></div><div><span><span>} </span><span>else</span><span> {</span></span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>state.</span><span>lyrics</span><span> </span><span>=</span><span> </span><span>parseLRC</span><span><span>(</span><span>track</span><span>.</span></span><span>lrc</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section>
<section><h1>最终配置<a href="#最终配置"><span>#</span></a></h1><p>经过这次调整，可以按照下面的结构配置播放器。这里使用的 API 地址、歌单 ID 和歌曲信息都只是占位示例，需要根据自己的环境替换：</p><div><div><div><figure><figcaption><span>src/config/musicConfig.ts（示例）</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>const</span><span> </span><span>musicPlayerConfig</span><span>:</span><span> </span><span>MusicPlayerConfig</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>showInNavbar</span><span>:</span><span> </span><span>true</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>showInSidebar</span><span>:</span><span> </span><span>true</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>mode</span><span>:</span><span> </span><span>"hybrid"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>  </span><span>volume</span><span>:</span><span> </span><span>0.7</span><span>,</span></div></div><div><div><div>6</div></div><div><span>  </span><span>playMode</span><span>:</span><span> </span><span>"random"</span><span>,</span></div></div><div><div><div>7</div></div><div><span>  </span><span>randomMode</span><span>:</span><span> </span><span>"queue"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>  </span><span>showLyrics</span><span>:</span><span> </span><span>false</span><span>,</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>meting</span><span>:</span><span> {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>api</span><span>:</span><span> </span><span>"https://music-api.example.com/meting?server=:server&amp;type=:type&amp;id=:id"</span><span>,</span></div></div><div><div><div>12</div></div><div><span>    </span><span>requestMode</span><span>:</span><span> </span><span>"sequential"</span><span>,</span></div></div><div><div><div>13</div></div><div><span>    </span><span>apiSelectionMode</span><span>:</span><span> </span><span>"first"</span><span>,</span></div></div><div><div><div>14</div></div><div><span>    </span><span>timeoutMs</span><span>:</span><span> </span><span>10000</span><span>,</span></div></div><div><div><div>15</div></div><div><span>    </span><span>server</span><span>:</span><span> </span><span>"netease"</span><span>,</span></div></div><div><div><div>16</div></div><div><span>    </span><span>type</span><span>:</span><span> </span><span>"playlist"</span><span>,</span></div></div><div><div><div>17</div></div><div><span>    </span><span>id</span><span>:</span><span> </span><span>"你的歌单 ID"</span><span>,</span></div></div><div><div><div>18</div></div><div><span>    </span><span>auth</span><span>:</span><span> </span><span>""</span><span>,</span></div></div><div><div><div>19</div></div><div><span>    </span><span>fallbackApis</span><span>:</span><span> [</span></div></div><div><div><div>20</div></div><div><span>      </span><span>"https://backup-api.example.com/meting?server=:server&amp;type=:type&amp;id=:id"</span><span>,</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>],</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>  </span><span>local</span><span>:</span><span> {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>playlist</span><span>:</span><span> [</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>{</span></div></div><div><div><div>27</div></div><div><span>        </span><span>name</span><span>:</span><span> </span><span>"本地歌曲"</span><span>,</span></div></div><div><div><div>28</div></div><div><span>        </span><span>artist</span><span>:</span><span> </span><span>"歌手名称"</span><span>,</span></div></div><div><div><div>29</div></div><div><span>        </span><span>url</span><span>:</span><span> </span><span>"/assets/music/song.mp3"</span><span>,</span></div></div><div><div><div>30</div></div><div><span>        </span><span>cover</span><span>:</span><span> </span><span>"/assets/music/cover/song.webp"</span><span>,</span></div></div><div><div><div>31</div></div><div><span>        </span><span>lrc</span><span>:</span><span> </span><span>"/assets/music/lrc/song.lrc"</span><span>,</span></div></div><div><div><div>32</div></div><div><span><span>      </span></span><span>},</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>],</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>35</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这些配置彼此独立：</p><ul>
<li><code>mode</code> 决定歌曲来自 API、本地，还是两者同时使用；</li>
<li><code>playMode</code> 决定列表循环、单曲循环或随机播放；</li>
<li><code>randomMode</code> 决定随机播放是独立抽取还是无重复队列；</li>
<li><code>requestMode</code> 决定歌单 API 是否允许依次回退；</li>
<li><code>apiSelectionMode</code> 决定采用第一个有效歌单，还是比较所有接口后选择歌曲最多的结果；</li>
<li><code>timeoutMs</code> 决定每个歌单请求最多等待多久。</li>
</ul><hr /></section>
<section><h1>结语<a href="#结语"><span>#</span></a></h1><p>这次修改最初只是因为“新加的歌曲为什么没有出现”。</p><p>但真正沿着数据链路检查以后，我发现播放器的问题并不只在某一个失效的接口上。歌曲来源、API 容错、随机队列、播放历史、超时反馈和异步竞态，其实都属于同一件事：播放器应该如何在外部数据并不可靠的情况下，仍然维持一个可预测的状态。</p><p>现在的实现依然不是什么复杂的专业音乐系统，也没有加入歌曲指纹、VIP 歌曲自动匹配或个性化推荐。但它已经比最初那个“拿到数组就直接播放”的组件稳定了许多：</p><ul>
<li>API 和本地歌曲可以共存；</li>
<li>第三方服务失效时，本地歌曲仍然可用；</li>
<li>随机播放一轮内不会重复；</li>
<li>大歌单不会因为数组头部操作逐渐变慢；</li>
<li>多个歌单 API 可以优先响应速度，也可以比较后选择歌曲最多的结果；</li>
<li>API 请求不会无限等待；</li>
<li>纯 API 模式可以手动重试；</li>
<li>快速切歌不会再串歌词。</li>
</ul><p>有时候，一个小问题最有价值的部分并不是把它临时修好，而是借着它重新看清整条链路。在修正现有问题的同时，顺便也可以实现一些新的想法。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260921-encryption-and-decryption/</id>
      <title type="text">密码学：从“加密”与“解密”说开去</title>
      <published>2026-09-21T00:00:00.000Z</published>
      <updated>2026-09-21T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260921-encryption-and-decryption/"/>
      <summary type="text">最好用的加密就是公开？从最基础的加密与解密讲起，逐步理解对称加密、非对称加密、哈希、数字签名等等，以及现代密码学的原理真正要解决的问题。</summary>
      <content type="html"><![CDATA[<p>我们每天都在使用密码学，只是大多数时候并不会意识到它的存在。</p>
<p>打开一个 HTTPS 网站、登录账号、连接 SSH、验证软件签名、使用密码管理器，甚至只是下载一个带 SHA-256 校验值的文件，背后都离不开密码学。</p>
<p>很多人第一次接触这个领域时，会把“加密”理解为“把正常文字变成乱码”，把“解密”理解为“把乱码恢复回来”。这种理解并没有错，但它只触及了密码学最表面的一层。</p>
<p>真正的现代密码学，关心的不只是“别人能不能看懂内容”，还包括：</p>
<ul>
<li>数据有没有被篡改；</li>
<li>对方是不是自己认为的那个人；</li>
<li>文件是不是来自可信的发布者；</li>
<li>密钥应该如何交换；</li>
<li>密码应该如何保存；</li>
<li>即使算法完全公开，系统是否仍然安全。</li>
</ul>
<p>也正因为如此，“加密”和“解密”其实只是密码学体系中的两个入口。</p>
<section><h1>从“明文”与“密文”开始<a href="#从明文与密文开始"><span>#</span></a></h1><p>最基本的加密过程，可以表示为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>明文 Plaintext</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>加密算法 + 密钥</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>密文 Ciphertext</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如，原始内容可能是：</p><p><code>Hello</code></p><p>经过某种加密算法处理后，则可能变成一串无法直接理解的数据：</p><p><code>7F A2 C9 13 ...</code></p><p>反过来，解密就是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>密文</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>解密算法 + 正确的密钥</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>明文</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里有三个最重要的概念：</p><ul>
<li><strong>明文（Plaintext）</strong>：原始、可理解的数据；</li>
<li><strong>密文（Ciphertext）</strong>：经过加密后的数据；</li>
<li><strong>密钥（Key）</strong>：控制加密和解密过程的关键参数。</li>
</ul><p>现代密码学通常并不依赖“算法保密”。</p><p>恰恰相反，大量真正可靠的密码算法，例如 AES、RSA、SHA 系列，都拥有公开规范，任何人都可以研究其内部原理。</p><p>安全性真正依赖的是：</p><blockquote><p>即使攻击者完全知道算法是如何工作的，只要不知道正确的密钥，仍然无法在可接受的成本内恢复原文。</p></blockquote><p>这也是现代密码学最重要的思想之一。</p></section>
<section><h1>为什么网络通信需要加密<a href="#为什么网络通信需要加密"><span>#</span></a></h1><p>当浏览器访问一个网站时，数据并不是从你的电脑瞬间“跳”到服务器。</p><p>一次网络请求中间可能经过：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>你的电脑</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>家庭路由器</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>运营商网络</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>互联网中的多个节点</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>目标服务器</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果所有数据都以明文形式传输，那么只要某个中间环节能够观察流量，就可能看到通信内容。</p><p>例如：</p><ul>
<li>用户名和密码；</li>
<li>Cookie；</li>
<li>API Token；</li>
<li>聊天内容；</li>
<li>文件内容；</li>
<li>表单数据。</li>
</ul><p>因此，加密首先解决的是一个非常直观的问题：</p><blockquote><p>即使数据被截获，也不能让旁观者直接理解其中的内容。</p></blockquote><p>这叫做<strong>机密性（Confidentiality）</strong>。</p><p>但仅仅“看不到”还远远不够。</p><p>假设攻击者虽然无法理解密文，却能够偷偷修改其中一部分，那么通信依然可能出现问题。因此，现代密码系统通常还需要保证：</p><ul>
<li><strong>完整性（Integrity）</strong>：数据没有被修改；</li>
<li><strong>身份认证（Authentication）</strong>：通信对象确实是自己认为的那一方；</li>
<li><strong>不可否认性（Non-repudiation）</strong>：某些情况下，可以证明某个操作确实由特定主体完成。</li>
</ul><p>这也是为什么 HTTPS 并不仅仅是“给网页内容加密”。</p></section>
<section><h1>凯撒密码：最早期的加密手段<a href="#凯撒密码最早期的加密手段"><span>#</span></a></h1><p>人类使用密码的历史远早于计算机。</p><p>最经典的例子之一是<strong>凯撒密码（Caesar Cipher）</strong>。</p><p>假设把所有英文字母向后移动三位：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A → D</span></div></div><div><div><div>2</div></div><div><span>B → E</span></div></div><div><div><div>3</div></div><div><span>C → F</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那么：</p><p><code>HELLO</code></p><p>会变成：</p><p><code>KHOOR</code></p><p>只要知道“移动三位”这个规则，就可以反向恢复原文。</p><p>这种方法现在当然谈不上安全，因为英语字母只有 26 个，攻击者完全可以把所有偏移量都尝试一遍。</p><p>这种把所有可能情况逐一尝试的方法，就叫做：<strong>暴力破解（Brute Force）</strong>。</p><p>早期密码系统中，很多方案的安全性都高度依赖“别人不知道规则”。但随着密码学的发展，人们逐渐认识到：</p><blockquote><p>如果一个系统必须依靠算法本身保密才能安全，那么它通常并不是真正可靠的密码系统。</p></blockquote></section>
<section><h1>密钥：现代加密真正的核心<a href="#密钥现代加密真正的核心"><span>#</span></a></h1><p>现代加密可以简单写成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Ciphertext = Encrypt(Key, Plaintext)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>解密则是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Plaintext = Decrypt(Key, Ciphertext)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中真正需要严格保护的是：</p><p><code>Key</code></p><p>也就是密钥。</p><p>可以把算法理解为一把公开设计的锁，而密钥才是真正决定谁能开锁的东西。</p><p>这就是所谓的 <strong>Kerckhoffs 原则</strong>，即：一个密码系统即使除了密钥之外的所有设计都被攻击者知道，也应该仍然保持安全。</p></section>
<section><h1>“最好的加密就是公开？”：为什么算法不怕被看见<a href="#最好的加密就是公开为什么算法不怕被看见"><span>#</span></a></h1><p>这个问题确实有点反直觉：</p><blockquote><p>人们千方百计想要加密一段数据，可这样的加密方法反而是公开的吗？</p></blockquote><p>准确来说，现代密码学追求的并不是“算法越公开越安全”，而是：</p><blockquote><p><strong>一个真正可靠的密码系统，不应该把“攻击者不知道算法”当成安全前提。</strong></p></blockquote><p>这和日常生活中的直觉有些不同。</p><p>如果我们自己发明了一套加密规则，例如：</p><blockquote><p>把每个字母先向后移动 7 位，再把字符串倒过来，然后每两个字符交换位置。</p></blockquote><p>只要暂时没人知道规则，密文看起来可能确实很神秘。</p><p>但这种安全性依赖的是：</p><p><strong>别人还没有猜到我们在做什么。</strong></p><p>一旦规则泄露，系统可能立刻失去保护能力。</p><p>这种思路通常被称为依赖<strong>隐蔽性</strong>或“把算法藏起来”。它并不是说隐藏实现细节毫无价值，而是说：<strong>隐藏算法本身不能成为密码系统最后一道、甚至唯一一道防线。</strong></p><p>现代密码学采用的是几乎相反的思路。</p><p>AES 的规范是公开的，SHA-256 的每一步运算是公开的，RSA 的数学原理也是公开的。攻击者完全可以知道：</p><ul>
<li>数据被怎样分块；</li>
<li>每一轮进行了什么运算；</li>
<li>公钥中包含哪些参数；</li>
<li>哈希函数怎样处理输入；</li>
<li>整个协议按照什么顺序执行。</li>
</ul><p>真正需要保密的，是其中的<strong>密钥</strong>，或者更具体一点，是<strong>私钥</strong>。</p><p>可以把它类比成现实中的门锁。</p><p>一把可靠的锁，不应该要求：</p><blockquote><p>“小偷绝对不能知道这把锁的内部结构。”</p></blockquote><p>更合理的目标是：</p><blockquote><p>“即使小偷已经拆过同型号的锁、拥有设计图、知道每个零件怎样运动，只要没有正确钥匙，仍然很难把这扇门打开。”</p></blockquote><p>这正是“算法公开、密钥保密”的意义。</p><p>公开还有另一个非常重要的好处：<strong>任何人都可以尝试攻击它。</strong></p><p>一种新的密码算法刚被提出时，即使设计者认为它非常牢固，也不能因此证明它真的安全。密码算法里可能隐藏着：</p><ul>
<li>某种设计者没有发现的数学规律；</li>
<li>某些输入下异常薄弱的结构；</li>
<li>比暴力破解更高效的攻击方法；</li>
<li>参数选择上的缺陷；</li>
<li>与实际实现结合后才会暴露的问题。</li>
</ul><p>如果算法长期公开，世界各地的密码学家、安全研究人员，甚至攻击者都可以研究它，寻找这些弱点。</p><p>有些算法经不起这样的检验，很快就会退出历史舞台；另一些算法在多年甚至几十年的公开分析中，始终没有出现足以破坏其实际安全性的攻击，于是才逐渐获得信任。</p><p>因此，“公开”本身并不会自动让一个算法变安全，但它让算法能够经历真正严格的检验。</p><p>这也是为什么：</p><blockquote><p><strong>“没人知道怎么破解”与“因为没人知道算法，所以没人破解”是两件完全不同的事情。</strong></p></blockquote><p>前者意味着：攻击者知道规则，仍然找不到现实可行的办法。</p><p>这也是为什么现代密码学普遍不鼓励个人随意设计所谓的“私有加密算法”。一个自己设计的算法也许看起来足够复杂，但“我暂时看不出漏洞”并不能证明它安全。成熟算法最宝贵的地方之一，恰恰是它们已经接受了大量公开分析。</p><p>所以，标题中的问题也可以得出答案了：</p><blockquote><p><strong>优秀的密码算法不需要害怕公开。真正需要保密的是密钥，而不是算法本身。</strong></p></blockquote><p>当然，这并不代表把密钥、随机数种子、服务器私钥或实现中的敏感信息公开也没有关系。算法公开与秘密材料公开完全是两回事。</p><div><div><div></div><div>Note</div></div><div><p>“算法公开”说的是<strong>设计与规则可以公开审查</strong>，并不是要求把运行中的秘密材料一起公开。像私钥、会话密钥、密码、随机种子等，依然必须受到严格保护。</p></div></div></section>
<section><h1>对称加密：一把钥匙负责加密和解密<a href="#对称加密一把钥匙负责加密和解密"><span>#</span></a></h1><p>现代加密首先可以分成两大类，即：对称加密与非对称加密。</p><p>**对称加密（Symmetric Encryption）**的特点非常直接：</p><blockquote><p>加密和解密使用同一个密钥。</p></blockquote><p>例如 Alice 和 Bob 事先拥有同一个密钥：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Alice</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>密钥 K + 加密算法</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>密文</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>密钥 K + 解密算法</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>Bob</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最著名的现代对称加密算法之一就是 <strong>AES（Advanced Encryption Standard）</strong>，常见形式包括 AES-128、AES-192 和 AES-256，其中数字表示密钥长度。</p><p>如果只用一句话概括 AES 的思路，可以把它理解成：</p><blockquote><p>把一小块数据反复“打散、替换、混合”，每一轮都掺入由密钥派生出的不同材料，直到原始结构被彻底打乱。</p></blockquote><p>AES 并不是把整份文件一次性塞进一个巨大的公式里，而是以固定大小的数据块为基本单位处理。AES 的分组大小固定为 <strong>128 bit</strong>，也就是 16 字节。可以把这 16 字节想象成一个 4×4 的小方格，算法会在这个方格上进行多轮变换。</p><p>每一轮里最核心的直觉大致有几步：</p><ul>
<li><strong>替换</strong>：把每个字节按照固定规则换成另一个值，打破输入与输出之间过于直接的关系；</li>
<li><strong>打乱位置</strong>：把原本相邻的数据搬到不同位置；</li>
<li><strong>混合</strong>：让一个位置的变化扩散到更多位置；</li>
<li><strong>加入轮密钥</strong>：把由原始密钥推导出的当前轮密钥混进数据。</li>
</ul><p>这几步不断重复，就会产生密码学中常说的两种效果：<strong>混淆（Confusion）<strong>和</strong>扩散（Diffusion）</strong>。前者让“密钥和密文之间的关系”变得难以分析，后者让明文中一个很小的变化迅速影响到大量密文位。</p><p>例如，原文只改动一个字符，最后生成的密文往往会出现大面积变化，而不是只改动某一个位置。</p><p>AES-128、AES-192 和 AES-256 使用的核心结构相同，主要区别在于密钥长度以及执行轮数。密钥越长，可供攻击者穷举的空间越大，但计算开销也会稍有增加。</p><p>对称加密最大的优点是 <strong>速度快</strong>。</p><p>因此，它非常适合大量数据的实际加密工作，例如：</p><ul>
<li>HTTPS 中的正文数据传输；</li>
<li>VPN 流量；</li>
<li>硬盘加密；</li>
<li>压缩包加密；</li>
<li>数据库字段加密；</li>
<li>文件加密。</li>
</ul><p>但它也面临一个非常现实的问题：</p><blockquote><p>双方第一次通信时，如何安全地把密钥交给对方？</p></blockquote><p>如果直接通过一个尚未加密的网络连接发送密钥，那么攻击者只要截获这把密钥，后续所有加密就失去了意义。</p><p>这个问题推动了另一类密码体系的发展。</p></section>
<section><h1>非对称加密：公钥与私钥<a href="#非对称加密公钥与私钥"><span>#</span></a></h1><p><strong>非对称加密（Asymmetric Encryption）</strong>，也叫<strong>公钥密码学（Public-key Cryptography）</strong>。</p><p>它不再只依赖一把密钥，而是使用一对彼此相关的密钥：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>公钥 Public Key</span></div></div><div><div><div>2</div></div><div><span>私钥 Private Key</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中：</p><ul>
<li>公钥可以公开；</li>
<li>私钥必须严格保密。</li>
</ul><p>假设 Bob 生成了一对密钥，并把公钥公开。</p><p>Alice 想给 Bob 发送秘密内容时，可以使用 Bob 的公钥加密数据，而只有掌握对应私钥的 Bob 才能够完成相应的解密操作。</p><p>这带来了一个非常重要的能力：</p><blockquote><p>双方不需要提前通过秘密渠道交换同一把密钥。</p></blockquote><p>因此，公钥密码学很大程度上解决了传统对称加密中的<strong>密钥分发问题</strong>。</p><p>不过，现实中的非对称密码系统远比“公钥加密、私钥解密”这句话更复杂。现代协议中，公钥密码学经常承担的是身份认证、数字签名、密钥协商等工作，而不是直接加密全部业务数据。</p></section>
<section><h1>RSA：最著名的公钥密码算法之一<a href="#rsa最著名的公钥密码算法之一"><span>#</span></a></h1><p>RSA 是密码学史上最著名的非对称算法之一。</p><p>它于 1977 年提出，名称来自三位设计者：</p><ul>
<li>Ron Rivest；</li>
<li>Adi Shamir；</li>
<li>Leonard Adleman。</li>
</ul><p>RSA 的安全性与大整数因数分解问题密切相关。</p><p>简单理解一下，假设现在有两个很大的质数 <code>p</code> 和 <code>q</code>，把它们相乘得到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>n = p × q</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这个乘法非常容易做。</p><p>但如果只把一个足够大的 <code>n</code> 交给别人，让对方重新找出原来的 <code>p</code> 和 <code>q</code>，计算难度就会急剧上升。RSA 正是利用了这种“正向容易、逆向困难”的不对称性。</p><p>当然，真正的 RSA 并不是“把两个质数相乘以后直接拿来加密”这么简单。它还会利用模运算构造出一组彼此配合的公开参数和私有参数。</p><p>可以把它想象成一种特殊的数学锁：</p><ul>
<li>公钥中包含一套可以公开的参数，任何人都可以拿来执行某种正向运算；</li>
<li>私钥中保存的是只有密钥持有者才知道的关键数学信息；</li>
<li>没有私钥的人虽然知道算法、也知道公钥，但想从结果反推出对应秘密，仍然会面临非常困难的计算问题。</li>
</ul><p>这里的关键并不是“公钥和私钥是一对完全相反的密码”，而是它们在数学上存在特殊关系：公开其中一部分信息，不等于公开另一部分信息。</p><p>RSA 既可以被用于某些加密场景，也可以构造数字签名。但现代协议会对原始 RSA 运算再叠加严格的填充方案，例如 OAEP 或 PSS，因为“直接拿数学公式处理原文”本身并不安全。</p><div><div><div></div><div>Note</div></div><div><p><strong>RSA、RSA-OAEP、RSA-PSS 不是同一个概念。</strong> RSA 是底层数学结构；OAEP 是常见的 RSA 加密填充方案，PSS 则用于 RSA 数字签名。实际系统讨论“RSA 是否安全”时，往往还必须看它具体搭配了什么方案和参数。</p></div></div><p>现代环境中常见的 RSA 密钥长度有：</p><p><code>2048 bit</code>、<code>3072 bit</code>、<code>4096 bit</code></p><p>不过，在现代 TLS 中，RSA 已经越来越少承担传统意义上的密钥交换任务，基于椭圆曲线的方案更加常见。</p></section>
<section><h1>ECC：更现代的椭圆曲线密码学<a href="#ecc更现代的椭圆曲线密码学"><span>#</span></a></h1><p>**ECC（Elliptic Curve Cryptography）**指的是椭圆曲线密码学。</p><p>它利用椭圆曲线上的数学难题构建密码系统。名字里的“椭圆”很容易让人误以为是在椭圆上画图，实际上这里说的是一类具有特定代数形式的曲线。</p><p>理解 ECC，不需要先掌握复杂公式。可以先凭借一个直觉：</p><blockquote><p>在曲线上，从一个起点按照规则“走很多步”很容易；但只看到起点和终点，想反推出到底走了多少步，却非常困难。</p></blockquote><p>在 ECC 中，这种“走很多步”的操作对应椭圆曲线上的标量乘法。给定一个公开起点 <code>G</code> 和一个秘密整数 <code>k</code>，计算：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>P = k × G</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>是高效可行的。</p><p>但如果别人只知道 <code>G</code> 和 <code>P</code>，想反推出 <code>k</code>，就会遇到所谓的<strong>椭圆曲线离散对数问题</strong>。对于选得合适的曲线和参数，这个反推过程在现实中极其困难。</p><p>于是，秘密整数可以作为私钥，而计算出的曲线点可以作为公钥。公开公钥不会直接暴露私钥。</p><p>ECC 的一个重要优势是：</p><blockquote><p>在相近安全强度下，它通常可以使用比 RSA 更短的密钥。</p></blockquote><p>例如非常粗略地比较：RSA 3072-bit 与 ECC 256-bit 强度相当。</p><p>因此，椭圆曲线方案通常具有：</p><ul>
<li>密钥更短；</li>
<li>传输开销更小；</li>
<li>计算效率较高。</li>
</ul><p>在 HTTPS、SSH、数字签名和密钥交换中，经常能看到：</p><p><code>ECDH</code>、<code>ECDHE</code>、<code>ECDSA</code>、<code>Ed25519</code>、<code>X25519</code></p><p>这些名称。</p><p>它们虽然都与现代公钥密码学有关，但承担的具体工作并不完全相同：</p><ul>
<li><strong>ECDH / X25519</strong> 更偏向于“协商共同秘密”；</li>
<li><strong>ECDHE</strong> 中的 <code>E</code> 表示 Ephemeral，也就是临时密钥，常用于获得前向保密；</li>
<li><strong>ECDSA / Ed25519</strong> 则主要用于数字签名。</li>
</ul><p>也就是说，“ECC”更像是一整类数学工具，而不是某一个单独用途的算法。</p><div><div><div></div><div>Note</div></div><div><p>这里最容易混淆的是：<strong>ECC 是一类公钥密码学方法，ECDH / X25519 主要用于密钥协商，ECDSA / Ed25519 主要用于数字签名。</strong> 它们都和椭圆曲线有关，但用途并不相同。</p></div></div></section>
<section><h1>为什么现实世界常常采用“混合加密”<a href="#为什么现实世界常常采用混合加密"><span>#</span></a></h1><p>如果非对称密码学这么方便，为什么不直接用它加密所有数据？</p><p>原因很简单：</p><p><strong>非对称密码学通常比对称密码学慢得多。</strong></p><p>因此，现实中的安全协议通常会把两者结合起来。</p><p>大致过程是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>非对称密码学</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>身份认证 / 密钥协商</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>生成双方共享的会话密钥</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>AES / ChaCha20 等对称算法</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>大量业务数据传输</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种设计称为 <strong>混合加密系统（Hybrid Cryptosystem）</strong>。</p><p>这里还有一个非常关键的角色：<strong>Diffie-Hellman 密钥交换</strong>。</p><p>它解决的问题并不是“直接把一把密钥发给对方”，而是：</p><blockquote><p>双方各自保留一个秘密值，只交换经过数学变换后的公开值，最后却能各自在本地算出同一个共享秘密。</p></blockquote><p>旁观者虽然能看到双方公开交换的数据，却很难从这些公开值反推出双方各自的秘密，因此也难以算出最终的共享结果。</p><p>如果使用椭圆曲线实现这一思想，就得到 ECDH；如果每次连接都临时生成新的密钥对，则常见形式就是 ECDHE。</p><p>可以把它类比成“调色”：</p><ul>
<li>Alice 有一种只有自己知道的秘密颜料 A；</li>
<li>Bob 也有自己的秘密颜料 B；</li>
<li>双方先约定一种公开底色 G；</li>
<li>各自把秘密颜色与底色混合后交换，变为 A + G &amp; B + G ；</li>
<li>再把收到的混合色与自己的秘密颜色继续混合，变为 A + G + B &amp; B + G + A。</li>
</ul><p>最终，两个人会得到同一种综合色；旁观者虽然看到了公开底色和中间交换的颜色，却很难倒推出双方各自真正的秘密颜色。</p><p>真实的 Diffie-Hellman 当然使用的是数学运算而不是颜料，但“公开交换材料，却不直接交换最终秘密”的直觉是相似的。</p><p>可以把它理解成：</p><blockquote><p>非对称密码学负责“安全地建立关系”，对称密码学负责“高效地传输大量数据”。</p></blockquote><p>这正是现代 HTTPS 的核心思路之一。</p></section>
<section><h1>HTTPS 并不是简单地“用 RSA 加密网页”<a href="#https-并不是简单地用-rsa-加密网页"><span>#</span></a></h1><p>很多早期科普文章会把 HTTPS 描述成：</p><blockquote><p>浏览器使用服务器的 RSA 公钥加密数据，服务器使用私钥解密。</p></blockquote><p>这种说法虽然能帮助初学者理解“公钥与私钥”，但对于现代 TLS 来说已经过于简化。</p><p>一次现代 HTTPS 连接，首先会经过 TLS 握手。</p><p>大致可以理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>浏览器</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>│ ClientHello</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>▼</span></div></div><div><div><div>5</div></div><div><span>服务器</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>服务器</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>9</div></div><div><span><span>   </span></span><span>│ ServerHello</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>│ Certificate</span></div></div><div><div><div>11</div></div><div><span><span>   </span></span><span>▼</span></div></div><div><div><div>12</div></div><div><span>浏览器</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>浏览器首先需要确认一个关键问题：</p><blockquote><p>正在通信的服务器，真的是自己想访问的那个网站吗？</p></blockquote><p>这里就需要使用<strong>数字证书（Certificate）</strong>。</p><p>证书通常由受信任的 CA，即**证书颁发机构（Certificate Authority）**签发。</p><p>常见的 CA 包括：</p><p><code>Let's Encrypt</code>、<code>DigiCert</code>、<code>GlobalSign</code>、<code>Sectigo</code></p><p>浏览器验证证书后，双方还会完成密钥协商。</p><p>现代 TLS 中常见的密钥协商方案之一是：</p><p><code>ECDHE</code></p><p>双方最终获得用于当前连接的会话密钥。</p><p>真正大量传输 HTTP 数据时，则通常使用高效的对称加密，例如：</p><p><code>AES-GCM</code></p><p>或者：</p><p><code>ChaCha20-Poly1305</code></p><p>因此：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HTTP + TLS = HTTPS</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>HTTPS 实际上是多种密码学机制共同工作的结果，而并不是某一种“加密算法”的简单应用。</p><div><div><div></div><div>Note</div></div><div><p>因此，“HTTPS 就是用服务器公钥把所有网页内容加密”只适合作为非常早期的入门类比。现代 TLS 通常会先完成身份认证与密钥协商，再使用对称加密保护大量业务数据。</p></div></div></section>
<section><h1>加密并不等于哈希<a href="#加密并不等于哈希"><span>#</span></a></h1><p>接触密码学后，一个最容易混淆的概念就是：</p><p><strong>Encryption 和 Hash 并不是同一回事。</strong></p><p>加密具有可逆性：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>明文</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>加密</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>密文</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>解密</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>明文</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而哈希通常被设计成单向过程：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>原始数据</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Hash</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>摘要</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如 <code>hello</code> 可以通过 SHA-256 得到一个固定长度的哈希摘要。</p><p>从原文计算摘要非常容易，但不能像解密密文一样，从摘要中直接恢复原文。</p><p>以 SHA-256 为例，它可以被粗略理解成一个“不断搅拌信息的摘要机”。</p><p>输入数据会先被切分成固定大小的数据块，每一块都会进入一系列轮函数，与当前内部状态反复混合。处理完这一块后得到的新状态，又会带到下一块继续处理。所有数据处理结束后，最终留下 256 bit 的结果，也就是 SHA-256 摘要。</p><p>这里最重要的不是记住每一步位运算，而是理解它想达到的几个效果：</p><ul>
<li>输入再长，输出长度始终固定；</li>
<li>输入发生极小变化，输出通常会大幅改变；</li>
<li>已知摘要，很难反推出原文；</li>
<li>想主动构造两份不同内容却得到同一摘要，也应该非常困难。</li>
</ul><p>这种“输入只变一点，输出却像彻底重新洗牌”的现象叫做<strong>雪崩效应（Avalanche Effect）</strong>。</p><p>例如 <code>hello</code> 和 <code>Hello</code> 只差一个字母大小写，但它们的 SHA-256 摘要完全不同。</p><p>因此，哈希并不是“把数据压缩后还可以解压”的过程，而更像是给数据生成一个高度敏感、固定长度的数学指纹。</p><p>常见的现代哈希算法包括：</p><p><code>SHA-256</code>、<code>SHA-384</code>、<code>SHA-512</code>、<code>SHA-3</code>、<code>BLAKE2</code>、<code>BLAKE3</code></p><p>哈希的一个非常重要的用途，就是检查数据完整性。</p></section>
<section><h1>为什么下载页面经常提供 SHA-256<a href="#为什么下载页面经常提供-sha-256"><span>#</span></a></h1><p>假设一个系统镜像下载页同时提供：</p><p><code>ubuntu.iso</code></p><p>和对应的：</p><p><code>SHA256: A91F...</code></p><p>下载完成后，你可以对本地文件重新计算 SHA-256。</p><p>如果结果也是：</p><p><code>A91F...</code></p><p>那么说明你手里的文件内容和发布方提供摘要时对应的文件一致。</p><p>即使文件只发生非常微小的变化，哈希结果通常也会完全不同。</p><p>不过需要注意：</p><blockquote><p>哈希只能证明“数据与某个摘要一致”，并不能自动证明“这个摘要本身可信”。</p></blockquote><p>如果攻击者既替换了下载文件，又能够替换网页上的 SHA-256，那么单纯的哈希校验同样会失效。</p><p>因此，更严格的软件分发体系通常还会加入<strong>数字签名</strong>。</p><div><div><div></div><div>Note</div></div><div><p>如果下载文件和对应的 SHA-256 都来自同一个已经被篡改的页面，那么“哈希一致”也无法证明文件来自真正的发布者。<strong>哈希负责检查内容是否一致，可信来源和数字签名负责补上“这个摘要是谁提供的”这一层。</strong></p></div></div></section>
<section><h1>数字签名：证明“是谁发的”和“有没有被篡改”<a href="#数字签名证明是谁发的和有没有被篡改"><span>#</span></a></h1><p>数字签名并不是“把整个文件用私钥加密”。</p><p>这种“私钥加密、公钥解密”的说法在一些入门材料里很常见，但对于现代数字签名来说并不准确。</p><div><div><div></div><div>Note</div></div><div><p>更准确的理解是：<strong>签名算法利用私钥生成一段只有对应公钥才能验证的签名数据</strong>。它和“把整份消息反向加密一遍”不是一回事。</p></div></div><p>真实系统通常会先对消息计算哈希摘要，再使用私钥对这个摘要相关的数据执行签名算法。可以粗略理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>原始消息</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>计算哈希</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>消息摘要</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>私钥 + 签名算法</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>数字签名</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>验证方收到消息和签名后，会自己重新计算消息摘要，再使用公钥按照对应算法验证签名是否成立。</p><p>这样做有两个好处：一是无需对一份可能很大的文件直接执行昂贵的公钥运算；二是只要消息发生细微变化，摘要就会改变，原来的签名也就无法继续通过验证。</p><p>它真正解决的是：</p><blockquote><p>如何证明一份数据确实与某个私钥持有者有关，并且签名之后没有被修改？</p></blockquote><p>假设 Alice 拥有一对私钥和公钥。她使用私钥对消息的摘要生成签名，别人拿到原始消息、签名和 Alice 的公钥后，就可以验证签名是否匹配。</p><p>如果验证成功，可以说明：</p><ul>
<li>当前消息与签名对应；</li>
<li>消息在签名之后没有发生相应篡改；</li>
<li>签名由对应私钥生成。</li>
</ul><p>数字签名被大量应用在：</p><ul>
<li>HTTPS 证书；</li>
<li>软件发布；</li>
<li>操作系统软件包；</li>
<li>Git Commit；</li>
<li>SSH；</li>
<li>应用程序签名。</li>
</ul><p>例如 GitHub 中有时会在 Commit 旁边显示：</p><p><code>Verified</code></p><p>本质上就是平台成功验证了相应的数字签名。</p><p>这并不是说 Git Commit 被“加密”了，而是说明其签名能够通过验证。</p></section>
<section><h1>为什么密码不应该直接“加密保存”<a href="#为什么密码不应该直接加密保存"><span>#</span></a></h1><p>“网站怎么保存用户密码”是密码学中非常典型的问题。</p><p>很多人的第一反应是：</p><blockquote><p>把密码加密后保存到数据库。</p></blockquote><p>但这通常不是正确做法。</p><p>如果系统保存的是可解密的密码，那么只要攻击者得到了解密密钥，所有用户密码都有可能被直接恢复。</p><p>更加合理的方式是保存：</p><p><strong>密码的哈希结果。</strong></p><p>用户注册时：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Password</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Password Hash</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>数据库</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用户登录时：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户输入密码</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>重新计算密码哈希</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>与数据库中的结果比较</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样，系统本身并不需要保存用户的原始密码。</p><p>而且，真正用于密码存储的算法通常不是普通 SHA-256，而是专门设计的密码哈希算法，例如 Argon2、bcrypt、scrypt 和 PBKDF2。</p><p>为什么不直接使用 SHA-256？</p><p>因为 SHA-256 的设计目标之一就是 <strong>计算得非常快</strong>。对于普通文件完整性检查，这是一件好事，但对于密码来说，太快反而会帮助攻击者——攻击者可以在很短的时间内尝试大量候选密码。</p><p>密码哈希算法故意反其道而行。</p><p>它们会有意识地把一次计算变得更“贵”：</p><ul>
<li><strong>PBKDF2</strong> 的核心思路是重复计算很多轮，让每一次猜测都更耗时间；</li>
<li><strong>bcrypt</strong> 同样通过可调成本参数提高重复计算的代价；</li>
<li><strong>scrypt</strong> 不只消耗 CPU，还刻意要求较多内存，让大规模并行破解更昂贵；</li>
<li><strong>Argon2</strong> 更进一步，把时间成本、内存成本和并行度都设计成可调参数。</li>
</ul><p>对正常用户来说，登录时多花几十毫秒或几百毫秒通常没有明显感觉；但对于一个想每秒尝试数百万、数十亿个密码的攻击者来说，这种额外成本会被成倍放大。</p><p>所以密码哈希的目标并不是“越快越好”，而是：</p><blockquote><p>对正常登录来说足够快，对大规模猜密码来说尽可能昂贵。</p></blockquote><div><div><div></div><div>Tip</div></div><div><p>这也是为什么密码数据库泄露后，攻击者通常不是“解密哈希”，而是不断尝试候选密码，再比较计算结果。专门的密码哈希算法要做的，就是让每一次这样的尝试都更昂贵。</p></div></div></section>
<section><h1>Salt：为什么同一个密码不应该得到同一个结果<a href="#salt为什么同一个密码不应该得到同一个结果"><span>#</span></a></h1><p>密码哈希中还有一个非常重要的概念：</p><p><strong>Salt（盐）</strong>。</p><p>假设两个用户使用完全相同的密码：</p><p><code>password123</code></p><p>如果系统只是简单计算：</p><p><code>Hash(password123)</code></p><p>那么两个人数据库中的结果也会完全一样。</p><p>攻击者一眼就能发现：</p><blockquote><p>这两个账户使用了相同密码。</p></blockquote><p>为了避免这种情况，系统会为不同用户生成随机 Salt：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户 A：</span></div></div><div><div><div>2</div></div><div><span>password123 + randomA</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>用户 B：</span></div></div><div><div><div>5</div></div><div><span>password123 + randomB</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后再计算密码哈希。</p><p>这样，即使两个人使用同一个密码，最终保存的结果也不同。</p><p>Salt 本身通常不需要保密。</p><p>它主要用于抵御预计算攻击，并避免相同密码产生完全相同的数据库记录。</p></section>
<section><h1>注意：Base64 看起来像密文，但它不是加密<a href="#注意base64-看起来像密文但它不是加密"><span>#</span></a></h1><p>另一个非常常见的误区是：</p><blockquote><p>一串看不懂的字符，就是加密后的结果。</p></blockquote><p>Base64 就特别容易让人产生这种错觉。</p><p>例如：</p><p><code>hello</code></p><p>经过 Base64 编码后会变成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>aGVsbG8=</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>原本熟悉的单词突然变成了一串看起来毫无规律的字符，甚至还带有 <code>=</code>，确实很像某种“密文”。</p><p>但 Base64 并不是加密，而是一种<strong>编码方式（Encoding）</strong>。</p><p>它最大的区别在于：</p><blockquote><p><strong>Base64 不需要密钥，也没有任何秘密规则。</strong></p></blockquote><p>任何知道“这是一段 Base64”的人，都可以按照公开规则直接把它还原。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>aGVsbG8=</span></div></div><div><div><div>2</div></div><div><span>↓ Base64 Decode</span></div></div><div><div><div>3</div></div><div><span>hello</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里不存在“破解”过程，也不存在“猜密钥”。</p><p>做的仅仅是：</p><p><strong>按照 Base64 的规则把数据重新解释回来。</strong></p><section><h2>Base64 为什么会变成这些奇怪的字符<a href="#base64-为什么会变成这些奇怪的字符"><span>#</span></a></h2><p>要理解这一点，首先要知道：计算机中的数据本质上都是二进制。</p><p>例如字符 <code>hello</code> 在计算机中，会先对应成一串字节。</p><p>以 ASCII / UTF-8 表示时：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>h → 01101000</span></div></div><div><div><div>2</div></div><div><span>e → 01100101</span></div></div><div><div><div>3</div></div><div><span>l → 01101100</span></div></div><div><div><div>4</div></div><div><span>l → 01101100</span></div></div><div><div><div>5</div></div><div><span>o → 01101111</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把它们连接起来，就是一长串 0 和 1。</p><p>Base64 接下来并不会对这些数据进行什么“保密运算”，而只是改变它们的分组方式。</p><p>普通字节以 <strong>8 bit</strong> 为一组，而 Base64 会把数据重新切成 <strong>6 bit</strong> 一组。</p><p>原因是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>2^6 = 64</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>6 bit 一共正好可以表示 64 种不同的值。</p><p>Base64 因此准备了一张包含 64 个字符的公开字符表：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A-Z</span></div></div><div><div><div>2</div></div><div><span>a-z</span></div></div><div><div><div>3</div></div><div><span>0-9</span></div></div><div><div><div>4</div></div><div><span>+</span></div></div><div><div><div>5</div></div><div><span>/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一共正好 64 个字符。</p><p>每一个 6 bit 的数值，都对应表中的一个字符。</p><p>于是原来的二进制数据：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>01101000 01100101 01101100 ...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会被重新切分成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>011010 000110 010101 101100 ...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后每一组再查表转换成对应字符，最终就得到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>aGVsbG8=</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以 Base64 看起来很陌生，并不是因为信息被“加密”了，而只是因为：</p><blockquote><p><strong>原来的字节被换了一种表示方式。</strong></p></blockquote></section><section><h2>为什么还需要 Base64<a href="#为什么还需要-base64"><span>#</span></a></h2><p>既然 Base64 不能保密，那为什么互联网中到处都能看到它？</p><p>因为它解决的根本就不是安全问题，而是：</p><blockquote><p><strong>如何让二进制数据方便地通过只适合文本的环境传输。</strong></p></blockquote><p>图片、压缩文件、密钥、证书等数据，本质上可能包含各种字节值。</p><p>但一些早期协议、文本格式或者配置文件，并不适合直接放入任意二进制内容。</p><p>Base64 可以把这些数据转换成一组非常普通的可打印字符，因此更容易：</p><ul>
<li>放进 JSON、XML 等文本格式；</li>
<li>通过电子邮件传输附件；</li>
<li>嵌入网页；</li>
<li>保存证书或密钥数据；</li>
<li>在文本协议中传输二进制内容。</li>
</ul><p>例如网页里有时可以看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>data:image/png;base64,...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这并不是“加密图片”，而是在把图片的二进制内容转换成 Base64 文本，以便直接嵌入 HTML 或 CSS。</p><p>PEM 格式的证书中也经常可以看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>-----BEGIN CERTIFICATE-----</span></div></div><div><div><div>2</div></div><div><span>MIID...</span></div></div><div><div><div>3</div></div><div><span>-----END CERTIFICATE-----</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>中间那一大串看起来很像密文的内容，本质上通常也是经过 Base64 编码后的二进制数据。</p></section><section><h2><code>=</code> 又是什么<a href="#-又是什么"><span>#</span></a></h2><p>Base64 末尾经常会出现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>=</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>==</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也很容易让它看起来更像某种复杂密码。</p><p>实际上，这个符号通常只是<strong>填充（Padding）</strong>。</p><p>因为 Base64 每次会把 3 个字节，也就是 24 bit 数据，转换成 4 组 6 bit：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>24 bit</span></div></div><div><div><div>2</div></div><div><span>=</span></div></div><div><div><div>3</div></div><div><span>6 + 6 + 6 + 6</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果最后剩下的数据不足 3 字节，就需要使用 <code>=</code> 补齐输出长度。</p><p>因此：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>aGVsbG8=</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>末尾的 <code>=</code> 并不包含某种密码学含义，只是在告诉解码器：</p><blockquote><p>最后一组数据没有填满。</p></blockquote><p>而且某些 Base64 变体甚至可以省略这些 Padding。</p></section><section><h2>Base64 和加密真正的区别<a href="#base64-和加密真正的区别"><span>#</span></a></h2><p>两者最关键的区别就是：</p><p><strong>Base64 没有秘密。</strong></p><p>Base64的编码规则是公开可查的，任何人都可以从 Base64 编码反推出原始信息。</p><p>而真正的加密数据，攻击者即使知道使用了什么算法，也会因为缺少<strong>密钥</strong>而无法解密。</p><p>因此：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Base64 ≠ Encryption</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更加准确地说：</p><blockquote><p><strong>编码解决“怎么表示数据”，加密解决“谁能看懂数据”。</strong></p></blockquote><p>同样，<code>URL Encoding</code>、<code>Hex Encoding</code>、<code>Unicode Encoding</code> 等也都属于数据表示或转换方式，而不是加密。</p><p>所以以后看到一串“不像正常文字”的内容时，不能仅凭它看起来很乱，就判断它已经经过加密。</p><p>它也可能只是：</p><ul>
<li>Base64；</li>
<li>十六进制；</li>
<li>URL Encoding；</li>
<li>压缩后的数据；</li>
<li>二进制数据的文本表示。</li>
</ul><p>真正判断一段数据是否经过加密，关键不在于它“看起来有多乱”，而在于：</p><blockquote><p><strong>恢复原始信息时，是否需要某个只有特定通信方掌握的秘密。</strong></p></blockquote><div><div><div></div><div>Note</div></div><div><p>Base64 常见特征包括只出现 <code>A-Z</code>、<code>a-z</code>、<code>0-9</code>、<code>+</code>、<code>/</code>，末尾有时带一个或两个 <code>=</code>，但这些都只能作为“可能是 Base64”的线索。<strong>不能仅凭外观判断一段数据到底是编码、压缩还是加密。</strong></p></div></div></section></section>
<section><h1>编码、加密、哈希和数字签名有什么区别<a href="#编码加密哈希和数字签名有什么区别"><span>#</span></a></h1><p>把几个最容易混淆的概念放在一起，会更加清楚：</p>

<table><thead><tr><th>技术</th><th>是否可逆</th><th>是否使用密钥</th><th>主要用途</th></tr></thead><tbody><tr><td>Encoding</td><td>是</td><td>否</td><td>数据表示与传输</td></tr><tr><td>Encryption</td><td>是</td><td>是</td><td>保护机密性</td></tr><tr><td>Hash</td><td>通常否</td><td>否</td><td>摘要、完整性检查</td></tr><tr><td>MAC</td><td>否</td><td>是</td><td>完整性与身份认证</td></tr><tr><td>Digital Signature</td><td>用公钥验证</td><td>私钥签名</td><td>身份、来源与完整性</td></tr></tbody></table><p>这张表更适合把前文出现过的几个概念放在一起对照：它们虽然经常同时出现在一个安全系统里，但解决的问题并不相同。</p></section>
<section><h1>密钥长度为什么重要<a href="#密钥长度为什么重要"><span>#</span></a></h1><p>密码系统的安全性与密钥空间密切相关。</p><p>例如 AES-128 的密钥理论上有：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>2^128</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>种可能。</p><p>也就是大约：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>340,282,366,920,938,463,463,374,607,431,768,211,456</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>种组合。</p><p>因此，传统意义上逐个尝试所有 AES-128 密钥在现实中并不可行。</p><p>AES-256 的密钥空间则更大：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>2^256</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以在真正的攻击中，攻击者往往不会选择“把所有密钥试一遍”这种最笨的方法。</p><p>相比之下，他们更愿意尝试：</p><ul>
<li>弱密码；</li>
<li>密钥泄露；</li>
<li>配置错误；</li>
<li>程序漏洞；</li>
<li>随机数缺陷；</li>
<li>社会工程；</li>
<li>协议实现漏洞。</li>
</ul><p>这也引出了密码学中一个非常重要的现实：</p><blockquote><p>一个安全的算法，不等于一个安全的系统。</p></blockquote><div><div><div></div><div>Tip</div></div><div><p>看到“使用 AES-256”时，最多只能说明某个环节采用了 AES-256。密钥怎么生成、怎么保存、Nonce 是否正确使用、协议有没有认证、程序有没有漏洞，这些同样会决定整个系统最终是否安全。</p></div></div></section>
<section><h1>算法没有被破解，系统仍然可能被攻破<a href="#算法没有被破解系统仍然可能被攻破"><span>#</span></a></h1><p>假设一个程序使用 AES-256。</p><p>算法本身没有问题。</p><p>但开发者却直接把密钥写在源码中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>KEY = "MySecretKey..."</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>攻击者根本不需要破解 AES。</p><p>只需要把程序反编译，找到密钥即可。</p><p>又比如：</p><ul>
<li>私钥文件权限配置错误；</li>
<li>Token 被写进日志；</li>
<li>密钥通过不安全的渠道发送；</li>
<li>随机数发生器可以被预测；</li>
<li>Nonce 被错误重复使用。</li>
</ul><p>这些问题都可能让一个“使用顶级密码算法”的系统彻底失效。</p><p>因此，现实中的密码安全不仅关心数学算法，还必须同时关心：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>算法安全吗？</span></div></div><div><div><div>2</div></div><div><span>密钥长度足够吗？</span></div></div><div><div><div>3</div></div><div><span>密钥如何生成？</span></div></div><div><div><div>4</div></div><div><span>密钥保存在哪里？</span></div></div><div><div><div>5</div></div><div><span>密钥如何交换？</span></div></div><div><div><div>6</div></div><div><span>随机数可靠吗？</span></div></div><div><div><div>7</div></div><div><span>实现有没有漏洞？</span></div></div><div><div><div>8</div></div><div><span>参数有没有重复？</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>密码学是整个安全系统的一部分，而不是安全系统的全部。</p></section>
<section><h1>为什么密码学如此依赖随机数<a href="#为什么密码学如此依赖随机数"><span>#</span></a></h1><p>现代密码系统中经常会出现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Key</span></div></div><div><div><div>2</div></div><div><span>IV</span></div></div><div><div><div>3</div></div><div><span>Nonce</span></div></div><div><div><div>4</div></div><div><span>Salt</span></div></div><div><div><div>5</div></div><div><span>Challenge</span></div></div><div><div><div>6</div></div><div><span>Token</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中很多参数都需要依赖随机数生成。</p><p>但密码学中的随机数不能随便使用普通的伪随机函数。</p><p>例如一些编程环境中的普通：</p><p><code>Math.random()</code></p><p>通常并不是为密码学设计的。</p><p>密码系统需要的是：</p><p><strong>CSPRNG（Cryptographically Secure Pseudorandom Number Generator）</strong></p><p>也就是：</p><p><strong>密码学安全伪随机数生成器。</strong></p><p>它的重要要求之一是：</p><blockquote><p>即使攻击者观察到之前的一部分输出，也不应该能够轻易预测后续结果。</p></blockquote><p>Linux 中常见的安全随机来源包括：</p><p><code>/dev/urandom</code></p><p>而现代编程语言和操作系统通常也会提供专门的密码学安全随机 API。</p><p>如果随机数可以被预测，那么很多看起来“密钥长度很长”的安全系统仍然可能迅速崩溃。</p></section>
<section><h1>IV 和 Nonce 到底是什么<a href="#iv-和-nonce-到底是什么"><span>#</span></a></h1><p>这是实际接触实际密码算法时，经常会遇到的概念。</p><p>它们通常不是密码，也不是秘密密钥。</p><p>它们是加密过程中额外使用的参数。</p><p>例如使用：</p><p><code>AES-GCM</code></p><p>时，通常需要提供一个 Nonce。</p><p>一个非常重要的原则是：</p><blockquote><p>对同一个密钥来说，某些算法要求 Nonce 绝对不能重复。</p></blockquote><p>如果错误地重复使用 Nonce，可能会严重破坏加密系统的安全性。</p><div><div><div></div><div>Note</div></div><div><p><strong>“Nonce 不能重复”与“Nonce 必须随机”不是完全相同的要求。</strong> 不同算法对 Nonce 的生成方式有不同规定；有些方案最关键的是在同一密钥下保持唯一，而不是单纯追求“看起来随机”。实际使用时应遵循具体算法或协议的规范。</p></div></div><p>因此，实际的现代加密远不是：</p><p><code>AES(password, file)</code></p><p>这么简单。</p><p>更加完整的结构可能是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Key</span></div></div><div><div><div>2</div></div><div><span>Nonce</span></div></div><div><div><div>3</div></div><div><span>Plaintext</span></div></div><div><div><div>4</div></div><div><span>AAD</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>6</div></div><div><span>AES-GCM</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>8</div></div><div><span>Ciphertext</span></div></div><div><div><div>9</div></div><div><span>Authentication Tag</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中 Authentication Tag 用来帮助检测数据是否被修改。</p><div><div><div></div><div>Warning</div></div><div><p>对 AES-GCM 来说，在同一密钥下重复使用不该重复的 Nonce 会造成严重安全问题，甚至可能同时破坏机密性和完整性。因此，Nonce 管理不是“附加细节”，而是算法正确使用的一部分。</p></div></div></section>
<section><h1>只加密还不够：AEAD<a href="#只加密还不够aead"><span>#</span></a></h1><p>早期密码系统有时只关注：</p><blockquote><p>让别人看不到内容。</p></blockquote><p>但后来人们越来越意识到：</p><blockquote><p>看不到，并不意味着改不了。</p></blockquote><p>攻击者可能完全无法理解一段密文，却仍然能够修改它的某些字节。</p><p>如果系统不能发现这种篡改，就可能带来严重问题。</p><p>因此现代密码协议越来越倾向使用：</p><p><strong>AEAD（Authenticated Encryption with Associated Data）</strong></p><p>即：<strong>带关联数据的认证加密。</strong></p><p>常见方案包括 <strong>AES-GCM</strong> 和 <strong>ChaCha20-Poly1305</strong>。</p><p>它们的思路不是“先随便加密，再额外附带一个校验码”，而是把<strong>加密</strong>和<strong>认证</strong>组合成一个完整过程。</p><p>以 AES-GCM 为例，可以把结果粗略理解成两部分：</p><ul>
<li><strong>Ciphertext</strong>：真正的密文，用于隐藏原始内容；</li>
<li><strong>Authentication Tag</strong>：认证标签，用来检查密文以及相关数据有没有被篡改。</li>
</ul><p>接收方解密时，并不是只要“密钥正确”就直接接受结果，而是还要验证认证标签。如果标签不匹配，就说明数据、Nonce、附加认证数据中的某一部分可能发生了变化，整个结果应当被视为无效。</p><p>ChaCha20-Poly1305 采用的是另一组内部算法：ChaCha20 负责生成密钥流并完成加密，Poly1305 负责生成认证标签。虽然内部数学结构与 AES-GCM 不同，但对使用者来说承担的目标非常相似：同时提供机密性和完整性。</p><p>因此，这类“既保密，又能检查是否被篡改”的设计，已经成为现代网络协议中的主流思路。</p></section>
<section><h1>把整个密码学体系放在一起看<a href="#把整个密码学体系放在一起看"><span>#</span></a></h1><p>看到这里，可以发现现代密码学并不是在寻找“世界上最强的一种加密算法”。</p><p>AES、RSA、ECC、SHA-256、Argon2 看起来都属于“密码学”，但它们解决的根本不是同一个问题。把它们放在一起比较谁“更强”，就像比较门锁、身份证、指纹、保险箱和快递封条谁更安全一样——它们本来就承担不同职责。</p><p>更实用的理解方式，是先问：</p><blockquote><p><strong>我现在到底想解决什么问题？</strong></p></blockquote>

<table><thead><tr><th>想解决的问题</th><th>需要的工具</th><th>常见算法或方案</th></tr></thead><tbody><tr><td>不让别人看懂数据</td><td>对称加密</td><td>AES、ChaCha20</td></tr><tr><td>在公开网络上协商共同秘密</td><td>密钥交换</td><td>Diffie-Hellman、ECDH、X25519</td></tr><tr><td>建立公钥与私钥体系</td><td>公钥密码学</td><td>RSA、ECC</td></tr><tr><td>判断数据有没有变化</td><td>哈希</td><td>SHA-256、SHA-3、BLAKE2</td></tr><tr><td>证明消息来自某个私钥持有者</td><td>数字签名</td><td>RSA-PSS、ECDSA、Ed25519</td></tr><tr><td>同时保证保密和防篡改</td><td>AEAD</td><td>AES-GCM、ChaCha20-Poly1305</td></tr><tr><td>安全保存用户密码</td><td>密码哈希 / KDF</td><td>Argon2、scrypt、bcrypt、PBKDF2</td></tr></tbody></table><p>从这张表里可以看到，<strong>“密码学算法”并不等于“加密算法”</strong>。</p><p>SHA-256 不负责把密文解回来；Argon2 也不是拿来加密文件的；Ed25519 的主要任务不是隐藏内容，而是生成和验证签名；Diffie-Hellman 更不是把一段正文变成密文，而是帮助双方建立共享秘密。</p><p>它们真正的关系更像是一套工具箱：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>密码学</span></div></div><div><div><div>2</div></div><div><span>│</span></div></div><div><div><div>3</div></div><div><span>├─ 保密</span></div></div><div><div><div>4</div></div><div><span>│   ├─ AES</span></div></div><div><div><div>5</div></div><div><span>│   └─ ChaCha20</span></div></div><div><div><div>6</div></div><div><span>│</span></div></div><div><div><div>7</div></div><div><span>├─ 建立共同秘密</span></div></div><div><div><div>8</div></div><div><span>│   ├─ Diffie-Hellman</span></div></div><div><div><div>9</div></div><div><span>│   ├─ ECDH</span></div></div><div><div><div>10</div></div><div><span>│   └─ X25519</span></div></div><div><div><div>11</div></div><div><span>│</span></div></div><div><div><div>12</div></div><div><span>├─ 摘要与完整性</span></div></div><div><div><div>13</div></div><div><span>│   ├─ SHA-256</span></div></div><div><div><div>14</div></div><div><span>│   └─ SHA-3</span></div></div><div><div><div>15</div></div><div><span>│</span></div></div><div><div><div>16</div></div><div><span>├─ 身份与签名</span></div></div><div><div><div>17</div></div><div><span>│   ├─ RSA-PSS</span></div></div><div><div><div>18</div></div><div><span>│   ├─ ECDSA</span></div></div><div><div><div>19</div></div><div><span>│   └─ Ed25519</span></div></div><div><div><div>20</div></div><div><span>│</span></div></div><div><div><div>21</div></div><div><span>├─ 认证加密</span></div></div><div><div><div>22</div></div><div><span>│   ├─ AES-GCM</span></div></div><div><div><div>23</div></div><div><span>│   └─ ChaCha20-Poly1305</span></div></div><div><div><div>24</div></div><div><span>│</span></div></div><div><div><div>25</div></div><div><span>└─ 密码存储</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>├─ Argon2</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>├─ bcrypt</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>├─ scrypt</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>└─ PBKDF2</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>真正的安全系统，往往就是从这个工具箱里拿出几样东西组合起来。</p></section>
<section><h1>用 HTTPS 再把这些工具串一次<a href="#用-https-再把这些工具串一次"><span>#</span></a></h1><p>前面分别介绍这些概念时，它们可能仍然显得有些零散。HTTPS 正好可以把其中很多工具连接起来。</p><p>一次现代 HTTPS 连接，可以粗略理解成下面这条链：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>服务器证书 + 数字签名</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>浏览器确认服务器身份</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>ECDHE 等密钥协商</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>双方得到共同的会话密钥</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>AES-GCM / ChaCha20-Poly1305</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>后续 HTTP 数据被加密并认证</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里每一个环节解决的问题都不同。</p><p><strong>数字证书和数字签名</strong>主要解决的是：“我连接的真的是这个网站吗？”</p><p><strong>ECDHE</strong> 解决的是：“双方怎样在有人旁观的网络上建立共同秘密？”</p><p><strong>AES-GCM 或 ChaCha20-Poly1305</strong> 解决的是：“建立连接以后，大量网页数据怎样高效地保密，同时还能检测篡改？”</p><p>哈希函数则会在证书、签名、协议内部的多个位置参与计算和验证。</p><p>所以，HTTPS 的安全并不是来自某一个“超级算法”，而是来自多种密码学工具各司其职，再由 TLS 协议把它们组合起来。</p><p>同样的思路也能看到在 SSH、VPN、软件签名、密码管理器等系统中。</p><p>因此，当我们以后看到一句：</p><blockquote><p>“这个系统使用了 AES-256。”</p></blockquote><p>其实还不能据此判断整个系统是否安全。</p><p>我们还需要继续问：</p><ul>
<li>密钥是怎么生成的？</li>
<li>密钥又是怎么交换的？</li>
<li>数据有没有认证和完整性保护？</li>
<li>对方的身份怎么确认？</li>
<li>私钥保存在哪里？</li>
<li>随机数和 Nonce 是否正确使用？</li>
<li>协议和程序实现有没有漏洞？</li>
</ul><p>这正是现代密码学从“一个加密算法”发展为“一整套安全系统”的地方。</p><div><div><div></div><div>Tip</div></div><div><p>如果以后再遇到一个陌生的密码学名词，可以先问一句：<strong>它到底是在解决“保密、完整性、身份、密钥交换，还是密码存储”中的哪一个问题？</strong> 往往比先记住算法名字更容易建立整体理解。</p></div></div></section>
<section><h1>结语<a href="#结语"><span>#</span></a></h1><p>如果只是刚刚进入密码学领域，最值得先记住的其实不是某个算法的数学细节，而是以下几组区别：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>加密 ≠ 哈希</span></div></div><div><div><div>2</div></div><div><span>加密 ≠ 编码</span></div></div><div><div><div>3</div></div><div><span>公钥 ≠ 私钥</span></div></div><div><div><div>4</div></div><div><span>密码 ≠ 密钥</span></div></div><div><div><div>5</div></div><div><span>哈希 ≠ 密码哈希</span></div></div><div><div><div>6</div></div><div><span>加密 ≠ 数字签名</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这些概念一旦分清，之后再去理解 HTTPS、SSH、软件签名和密码存储，就会容易很多。</p><p>同时也应该记住现代密码学非常重要的一条原则：</p><blockquote><p>真正可靠的安全系统，并不是依靠“别人不知道算法”，而是依靠公开、经过长期检验的算法，以及正确生成、交换、保存和使用的密钥。</p></blockquote><p>所以，“最好的加密就是公开？”并不是说“公开以后自然就会安全”，而是说：<strong>足够成熟的密码算法，应当能够在所有规则都公开、所有攻击者都可以研究它的情况下，仍然把安全性牢牢建立在密钥和经过验证的数学难题之上。</strong></p><p>从这个角度看，“加密”并不是把信息变成乱码这么简单。</p><p>它更像是一场围绕<strong>机密性、完整性、身份与信任</strong>展开的系统工程。</p><p>而我们每天使用的 HTTPS、SSH、数字证书和软件签名，正是这套系统在现实世界中的具体体现。</p></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考 NIST、IETF、RFC Editor、OWASP 等机构发布的密码学标准、协议规范与安全实践资料：</p><ol>
<li>
<p>NIST — FIPS 197: Advanced Encryption Standard (AES)<br />
AES 的官方标准，介绍 AES-128、AES-192、AES-256，以及 128 bit 分组、密钥扩展和各轮变换等基本结构。
<a href="https://csrc.nist.gov/pubs/fips/197/final?utm_source=chatgpt.com" target="_blank">NIST：FIPS 197 — Advanced Encryption Standard (AES)</a></p>
</li>
<li>
<p>NIST — FIPS 180-4: Secure Hash Standard (SHS)<br />
介绍 SHA-224、SHA-256、SHA-384、SHA-512 等安全哈希算法，以及消息摘要在完整性检测中的用途。
<a href="https://csrc.nist.gov/pubs/fips/180-4/upd1/final?utm_source=chatgpt.com" target="_blank">NIST：FIPS 180-4 — Secure Hash Standard</a></p>
</li>
<li>
<p>NIST — FIPS 186-5: Digital Signature Standard (DSS)<br />
NIST 数字签名标准，介绍数字签名在数据完整性、签名者身份认证等方面的作用，并规定相关签名算法。
<a href="https://csrc.nist.gov/pubs/fips/186-5/final?utm_source=chatgpt.com" target="_blank">NIST：FIPS 186-5 — Digital Signature Standard</a></p>
</li>
<li>
<p>RFC 8017 — PKCS #1: RSA Cryptography Specifications Version 2.2<br />
RSA 密码体系的重要规范，介绍 RSA 的基础运算、密钥表示、RSA-OAEP 加密以及 RSA-PSS 数字签名等机制。
<a href="https://www.rfc-editor.org/info/rfc8017/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 8017 — RSA Cryptography Specifications</a></p>
</li>
<li>
<p>NIST SP 800-56A Rev. 3 — Recommendation for Pair-Wise Key-Establishment Schemes Using Discrete Logarithm Cryptography<br />
介绍基于离散对数问题的密钥建立方法，包括 Diffie-Hellman、椭圆曲线 Diffie-Hellman 等，是理解 ECDH、密钥协商和共享秘密的重要资料。
<a href="https://csrc.nist.gov/pubs/sp/800/56/a/r3/final?utm_source=chatgpt.com" target="_blank">NIST：SP 800-56A Rev. 3 — Key Establishment</a></p>
</li>
<li>
<p>RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA)<br />
介绍 EdDSA 数字签名体系，以及 Ed25519、Ed448 等具体方案的密钥生成、签名和验证过程。
<a href="https://www.rfc-editor.org/info/rfc8032/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 8032 — EdDSA / Ed25519</a></p>
</li>
<li>
<p>RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3<br />
TLS 1.3 协议规范，介绍现代 HTTPS 中的握手、证书认证、密钥协商、会话密钥以及认证加密等机制。
<a href="https://www.rfc-editor.org/info/rfc8446/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 8446 — TLS 1.3</a></p>
</li>
<li>
<p>NIST SP 800-38D — Galois/Counter Mode (GCM) and GMAC<br />
介绍 GCM 认证加密模式，是理解 AES-GCM 如何同时提供数据机密性和完整性认证的重要资料。
<a href="https://csrc.nist.gov/pubs/sp/800/38/d/final?utm_source=chatgpt.com" target="_blank">NIST：SP 800-38D — GCM and GMAC</a></p>
</li>
<li>
<p>RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols<br />
介绍 ChaCha20 流密码、Poly1305 消息认证器以及两者组合形成的 ChaCha20-Poly1305 AEAD 方案。
<a href="https://www.rfc-editor.org/info/rfc8439/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 8439 — ChaCha20 and Poly1305</a></p>
</li>
<li>
<p>OWASP Cheat Sheet Series — Password Storage Cheat Sheet<br />
介绍为什么密码应当使用单向密码哈希而非可逆加密保存，并讨论 Salt、Argon2id、scrypt、bcrypt、PBKDF2 以及计算成本设计。
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html?utm_source=chatgpt.com" target="_blank">OWASP：Password Storage Cheat Sheet</a></p>
</li>
<li>
<p>RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications<br />
Argon2 的规范资料，介绍其密码哈希设计、内存硬化特性以及 Argon2d、Argon2i、Argon2id 等模式。
<a href="https://www.rfc-editor.org/info/rfc9106/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 9106 — Argon2</a></p>
</li>
<li>
<p>RFC 4648 — The Base16, Base32, and Base64 Data Encodings<br />
Base64 的标准规范，详细说明 8 bit 数据如何重新组织为 6 bit 单元、64 字符编码表以及 <code>=</code> Padding 的含义，可用于理解为什么 Base64 是编码而不是加密。
<a href="https://www.rfc-editor.org/info/rfc4648/?utm_source=chatgpt.com" target="_blank">RFC Editor：RFC 4648 — Base16, Base32 and Base64</a></p>
</li>
<li>
<p>NIST SP 800-57 Part 1 Rev. 5 — Recommendation for Key Management: Part 1 — General<br />
介绍密码密钥的生命周期、密钥类型、密钥保护、生成、存储与管理，是理解“算法公开、密钥保密”以及密钥管理重要性的参考资料。
<a href="https://csrc.nist.gov/pubs/sp/800/57/pt1/r5/final?utm_source=chatgpt.com" target="_blank">NIST：SP 800-57 Part 1 Rev. 5 — Key Management</a></p>
</li>
<li>
<p>NIST SP 800-90A Rev. 1 — Recommendation for Random Number Generation Using Deterministic Random Bit Generators<br />
介绍密码学安全随机数生成器与 DRBG，包括基于哈希、HMAC 和分组密码构造的随机比特生成机制，是理解 CSPRNG 和密码学随机数的重要资料。
<a href="https://csrc.nist.gov/pubs/sp/800/90/a/r1/final?utm_source=chatgpt.com" target="_blank">NIST：SP 800-90A Rev. 1 — Random Number Generation</a></p>
</li>
<li>
<p>OWASP Cheat Sheet Series — Cryptographic Storage Cheat Sheet<br />
从工程实践角度介绍数据加密、密钥管理、算法选择以及密码不应使用可逆加密保存等问题，可作为本文“算法安全不等于系统安全”部分的补充资料。
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html?utm_source=chatgpt.com" target="_blank">OWASP：Cryptographic Storage Cheat Sheet</a></p>
</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260920-internet-vulnerability-malware-history/</id>
      <title type="text">从 Morris 到 Log4Shell：互联网安全四十年攻防简史</title>
      <published>2026-09-20T00:00:00.000Z</published>
      <updated>2026-09-20T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260920-internet-vulnerability-malware-history/"/>
      <summary type="text">从感染文件的病毒，到席卷网络的蠕虫，再到潜伏系统的木马；恶意程序不断寻找新的突破口，杀毒软件与防御机制也随之升级。走近互联网安全四十余年的攻防史，观看这场至今仍未结束的博弈。</summary>
      <content type="html"><![CDATA[<p>互联网发展的历史，同时也是一部漏洞与恶意程序共同演化的历史。</p>
<p>如果只看漏洞与安全缺陷，我们会看到缓冲区溢出、SQL 注入、远程代码执行、0day 漏洞不断出现；如果只看恶意软件与攻击活动，我们又会看到文件病毒、蠕虫、木马、僵尸网络、勒索软件不断演化。</p>
<p>但真正推动互联网安全不断升级的，往往并不是“某个漏洞”或者“某个病毒”，而是二者结合之后产生的结果。</p>
<blockquote><p>漏洞提供入口，恶意程序则把这个入口变成传播、控制、窃密、勒索甚至破坏现实设备的工具。</p></blockquote>
<p>从 Morris Worm 到 CIH，从“熊猫烧香”到 WannaCry，再到 Log4Shell 与现代供应链攻击，互联网安全的发展轨迹其实非常清晰：攻击者不断寻找新的入口，而恶意软件也不断改变进入系统之后的目的。</p>
<p>但如果只从攻击者一侧观察，这段历史仍然是不完整的。</p>
<p>几乎每一次大规模安全事件之后，防守方都会补上一层新的防线：漏洞推动补丁机制成熟，网络蠕虫推动防火墙与自动更新普及，内存破坏推动 DEP、ASLR 等保护机制出现，而这些新的防御又会迫使攻击者寻找新的绕过方式。</p>
<p>因此，互联网安全的发展并不是单向的“攻击技术越来越强”，而更像一场旷日持久的<strong>攻防博弈</strong>：</p>
<blockquote><p>攻击者寻找新的突破口，防守方修补暴露出的弱点；新的防御提高攻击成本，而攻击者又继续寻找下一条路径。</p></blockquote>
<hr />
<section><h1>互联网早期：安全并不是第一优先级<a href="#互联网早期安全并不是第一优先级"><span>#</span></a></h1><p>互联网的前身： ARPANET 诞生于一个与今天完全不同的环境。</p><p>当时能够接入网络的，主要是政府机构、大学和研究单位。网络规模有限，参与者数量也不多，因此整个系统在很大程度上建立在“参与者可信”的假设之上。</p><p>这直接影响了早期网络协议的设计。</p><p>Telnet 可以直接通过网络登录远程计算机，却长期使用明文传输用户名和密码；FTP 的账号和数据同样可以明文传输；早期电子邮件协议也缺乏今天常见的身份验证机制。很多网络服务默认认为来自网络中的请求是正常请求，而不是潜在的攻击输入。</p><p>从今天的角度来看，这些设计显得相当危险。</p><p>但在当时，它们并不完全不合理。毕竟，如果一个网络只连接少量经过授权的科研机构，那么“如何方便地互联”显然比“如何防范互联网上数以亿计的陌生设备”更加重要。</p><p>问题在于，互联网发展得太过迅速，这一切都发生了变化。</p><p>网络迅速扩大，而很多原本建立在信任基础上的协议和软件，并没有同步完成安全转型。</p><p>1988 年，一个著名的网络蠕虫，让整个互联网第一次真正意识到这个问题。</p><p>它就是 <strong>Morris Worm——莫里斯蠕虫</strong>。</p><hr /></section>
<section><h1>1988：Morris Worm，让网络攻击第一次实现自动传播<a href="#1988morris-worm让网络攻击第一次实现自动传播"><span>#</span></a></h1><p>1988 年 11 月，一个仅有 99 行代码的程序开始在当时的互联网中自行传播。</p><p>它后来被称为 Morris Worm。</p><p>今天看来，蠕虫自动扫描网络、寻找目标、利用漏洞，然后继续感染下一台机器，似乎已经是非常熟悉的攻击模式。但在当年，这是一件极具冲击力的事情。</p><p>Morris Worm 综合利用了 Unix 系统中的多个问题，包括网络服务漏洞、弱口令和配置缺陷。一旦进入某台机器，就会继续寻找其他可感染主机。</p><p>它的传播方式并不是“复制一个文件然后等用户打开”，而是主动寻找下一台主机，并尝试通过不同方式进入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>发现其他主机</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>尝试网络服务漏洞</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>尝试弱口令 / 信任关系</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>进入系统</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>复制并启动自己</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>继续寻找下一台主机</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也是“网络蠕虫”与传统文件病毒最核心的区别：<strong>传播过程本身就是自动化的。</strong></p><p>它并不是传统意义上必须依附文件传播的“病毒”，而是一种更加顺应当时时代的恶意程序：</p><p><strong>网络蠕虫。</strong></p><p>传统病毒通常需要附着在其他文件或程序上，例如感染可执行文件，再等待用户运行后继续复制；蠕虫则可以独立运行，并通过网络主动寻找下一个目标。</p><p>Morris Worm 最重要的意义并不是它感染了多少机器，而是它证明了：</p><blockquote><p>漏洞不只是让某一台计算机被攻破，还可以成为恶意程序自动传播的通道。</p></blockquote><p>从这一刻开始，安全问题不再只是“某个人入侵了某台计算机”。</p><p>正如现实中的传染病会在人群中扩散，网络蠕虫也可以从一台机器自动传播到另一台机器。</p><p>Morris Worm 之后，计算机安全事件响应机制开始受到重视，CERT/CC 也在这一时期成立。</p><p>Morris Worm 展示了攻击方第一次能够借助网络快速放大一个安全问题，而防守方的回应，则不再只是让某个管理员单独修好某台机器，而是开始建立事件通报、漏洞协调和应急响应机制。</p><p>也就是说，防御第一次开始尝试以<strong>组织化响应</strong>对抗<strong>自动化传播</strong>。</p><p>互联网安全由此逐渐从少数技术人员关注的问题，发展成一个正式的研究和管理领域。</p><hr /></section>
<section><h1>1990 年代：病毒与漏洞利用开始同时成熟<a href="#1990-年代病毒与漏洞利用开始同时成熟"><span>#</span></a></h1><p>进入 1990 年代后，个人计算机和 Windows 系统迅速普及。</p><p>这一时期，传统文件病毒依然是最常见的恶意程序之一。病毒可以感染 <code>.exe</code>、<code>.com</code> 等可执行文件，也可以感染启动扇区，甚至通过 Office 宏进入 Word 和 Excel 文档。</p><p>与此同时，漏洞利用技术也开始逐渐成熟。</p><p>其中最具代表性的一类，就是 <strong>缓冲区溢出</strong>。</p><p>很多程序使用 C 或 C++ 编写，而这些语言允许程序直接操作内存。如果开发者没有正确检查输入数据的长度，攻击者就可能向一个固定大小的缓冲区写入超过其容量的数据。</p><p>原本的内存结构可能类似：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[ 缓冲区 ][ 其他数据 ][ 返回地址 ]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果输入内容超过缓冲区长度，多余的数据就可能继续覆盖后面的内存区域。</p><p>一旦攻击者能够控制程序的返回地址，就可能进一步改变程序的执行流程。</p><p>这让原本看起来只是“程序崩溃”的错误，变成了“执行攻击者代码”的安全漏洞。</p><p>1996 年，Aleph One 发表经典文章《Smashing The Stack For Fun And Profit》，系统介绍了栈缓冲区溢出的利用方式。这篇文章后来几乎成为漏洞利用技术史上的标志性文献。</p><p>但如果要理解 1990 年代末的恶意软件，单讲漏洞还不够。</p><p>这一时期还有一个极具代表性的名字：<strong>CIH</strong></p><hr /></section>
<section><h1>1998—1999：CIH，破坏型文件病毒时代的代表<a href="#19981999cih破坏型文件病毒时代的代表"><span>#</span></a></h1><p><strong>CIH</strong>大约在 1998 年出现，并在 1999 年因为大规模发作而广为人知。</p><p>它通常也被称为“切尔诺贝利病毒”或“4 月 26 日病毒”。</p><p>与后来依靠网络漏洞高速传播的蠕虫不同，CIH 更接近传统意义上的<strong>文件感染型病毒</strong>。</p><p>它会感染 Windows 可执行文件，并将自己的代码嵌入其中。某些 CIH 变种还会尽量利用 PE 文件中原本没有被使用的空隙存放自身代码，从而减小文件体积明显变化带来的异常感。</p><p>当用户运行被感染程序时，病毒代码也会被执行，并继续寻找其他可执行文件进行感染。</p><p>其基本逻辑可以简化为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>运行被感染程序</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>CIH 代码被执行</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>寻找其他可执行文件</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>写入病毒代码</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>等待触发日期</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>执行破坏载荷</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>某些 CIH 变种在特定日期触发后，会尝试覆盖硬盘数据；在部分硬件和系统环境下，还可能破坏 Flash BIOS 内容。</p><p>这使 CIH 成为 1990 年代末最著名的破坏型病毒之一。</p><p>CIH 的意义不只是“破坏力强”。</p><p>它代表的是一个仍然带有明显“病毒文化”色彩的时代。</p><p>当时很多病毒作者更在意的是：</p><ul>
<li>能否感染更多文件；</li>
<li>能否隐藏自身；</li>
<li>能否展示复杂的技术技巧；</li>
<li>能否造成足够明显的破坏。</li>
</ul><p>也就是说，在 CIH 所代表的时代：</p><blockquote><p>计算机本身就是攻击和破坏的目标。</p></blockquote><p>这和后来恶意软件逐渐追求盗号、控制、流量变现乃至勒索，有非常明显的区别。</p><p>从恶意软件演化史来看，CIH 很适合作为“传统文件病毒时代”的一个阶段性顶点。</p><hr /></section>
<section><h1>1999—2000：Melissa 与 ILOVEYOU，邮件和社会工程学接管传播<a href="#19992000melissa-与-iloveyou邮件和社会工程学接管传播"><span>#</span></a></h1><p>1999 年，**Melissa（梅丽莎）**大规模传播。</p><p>它利用 Microsoft Word 文档中的宏功能，并结合 Outlook 邮件客户端自动向通讯录中的联系人发送带毒文档。</p><p>它的传播思路非常典型：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户打开带宏文档</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>宏代码执行</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>感染 Word 模板 / 文档</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>读取 Outlook 通讯录</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>自动发送带毒附件</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说，Melissa 并不需要攻破操作系统内核，而是把 Word 宏和 Outlook 自动化接口这些“正常功能”拼接成了一条传播链。</p><p>它并不主要依靠传统系统漏洞，而是滥用了 Office 宏和邮件客户端的自动化能力。</p><p>这说明攻击面并不只来自程序错误。</p><p>很多时候，合法功能本身也可能被恶意利用。</p><p>2000 年，**ILOVEYOU（爱虫，也常称“我爱你”蠕虫）**又进一步展示了社会工程学的威力。</p><p>用户收到主题类似“ILOVEYOU”的邮件，因为好奇打开附件，恶意脚本随后修改本地文件，并继续通过通讯录传播。</p><p>它本质上是一个脚本型蠕虫。用户一旦运行附件，脚本就会执行本地文件操作，并利用邮件客户端继续向联系人复制自己。</p><p>和 Melissa 相比，ILOVEYOU 更突出的一点是：<strong>传播链条的第一步不是技术突破，而是诱导用户主动执行。</strong></p><p>它的技术本身并不算复杂，但传播效果却极其惊人。</p><p>原因很简单：</p><p>它利用的不是某个高深漏洞，而是人的心理。</p><p>从这一时期开始，一个重要的攻击思想逐渐成熟：</p><blockquote><p>如果无法骗过计算机，也可以先骗过人。</p></blockquote><p>这条路线后来一直延续到今天的钓鱼邮件、伪造登录页、恶意二维码和社交平台诈骗。</p><hr /></section>
<section><h1>Web 的兴起：Web 应用成为新的漏洞重灾区<a href="#web-的兴起web-应用成为新的漏洞重灾区"><span>#</span></a></h1><p>1990 年代后期到 2000 年代初，互联网开始进入真正意义上的 Web 时代。</p><p>PHP、ASP、JSP、CGI 等动态网页技术逐渐普及，数据库也开始大量用于网站后台。</p><p>过去的漏洞往往存在于操作系统或网络服务中，而现在，任何一个网站程序都有可能成为攻击目标。</p><p>于是，一批今天依然很常见的 Web 漏洞开始出现：</p><ul>
<li>SQL 注入；</li>
<li>XSS；</li>
<li>命令注入；</li>
<li>文件包含；</li>
<li>目录遍历；</li>
<li>任意文件上传；</li>
<li>身份验证绕过；</li>
<li>Session 劫持。</li>
</ul><p>以 SQL 注入为例，如果开发者直接把用户输入拼接到 SQL 语句中，攻击者就可能通过构造特殊输入改变原本查询逻辑。</p><p>例如，程序原本只想执行一次普通的账号查询：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>SELECT</span><span> * </span><span>FROM</span><span> users</span></div></div><div><div><div>2</div></div><div><span>WHERE</span><span><span> username </span><span>=</span><span> </span></span><span>'$username'</span></div></div><div><div><div>3</div></div><div><span>AND</span><span> </span><span>password</span><span><span> </span><span>=</span><span> </span></span><span>'$password'</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果 <code>$username</code> 和 <code>$password</code> 直接来自用户输入，那么攻击者提供的内容就可能被数据库当成 SQL 语法的一部分，而不是单纯的数据。</p><p>问题的本质并不是 SQL 本身“不安全”，而是程序将“用户提供的数据”和“程序执行的指令”混在了一起。</p><p>XSS 和命令注入其实也有类似的思想。XSS 是让原本应该被当作网页内容的数据，被浏览器误当成脚本执行；命令注入则是让原本应该只是参数的数据，被操作系统外壳解释成了新的命令。</p><p>因此，这一类漏洞背后的共同问题可以概括为：</p><blockquote><p><strong>数据与指令的边界被打破了。</strong></p></blockquote><p>现代防御通常会通过参数化查询、上下文相关转义、严格输入校验等方式，尽量让“数据始终只是数据”。</p><p>2001 年，OWASP 成立。后来广为流传的 OWASP Top 10，也逐渐成为 Web 安全领域最著名的风险分类之一。</p><p>这也是 Web 安全攻防逐渐体系化的过程。</p><p>攻击方不断寻找新的输入点，把用户数据变成 SQL、脚本或系统命令；防守方则逐渐形成参数化查询、输出编码、输入校验、权限隔离和安全开发规范等方法。</p><p>于是，攻防双方争夺的重点从“某台服务器有没有明显漏洞”，进一步深入到了：</p><blockquote><p><strong>应用程序如何理解和处理每一份来自外部的数据。</strong></p></blockquote><p>从这里开始，漏洞已经不再只是操作系统开发者的问题，也成为普通网站开发者每天都要面对的问题。</p><hr /></section>
<section><h1>2001—2004：高速网络蠕虫<a href="#20012004高速网络蠕虫"><span>#</span></a></h1><p>如果说 Morris Worm 证明了自动传播攻击的可能性，那么 2000 年代初的一系列蠕虫，则真正展示了这种攻击在大规模互联网中的破坏力。</p><p>这一时期有几个重要背景：</p><p>Windows 个人电脑大量普及，越来越多设备直接接入互联网，而自动更新机制和用户安全意识还远没有今天成熟。</p><p>一台没有及时安装补丁的计算机，只要暴露在互联网上，就可能被自动扫描和感染。</p><section><h2>Code Red：不需要用户点击的攻击<a href="#code-red不需要用户点击的攻击"><span>#</span></a></h2><p>2001 年，**Code Red（红色代码）**出现。</p><p>它利用 Microsoft IIS Web 服务器中的缓冲区溢出漏洞传播。</p><p>一旦成功进入服务器，Code Red 会驻留在内存中继续扫描其他 IIS 主机，并尝试重复利用同一个漏洞进行传播。部分变种还会篡改网页内容或在特定阶段参与拒绝服务攻击。</p><p>因此它的运行模式很像：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>扫描 IIS</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>利用缓冲区溢出</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>进入服务器</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>继续扫描</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>感染更多服务器</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>和 Melissa、ILOVEYOU 最大的区别在于：</p><p>用户不需要打开邮件，也不需要运行附件。</p><p>服务器只要存在漏洞并暴露在网络中，就可能被感染。</p><p>这就是“漏洞 + 自动传播程序”的经典组合。</p></section><section><h2>Nimda：多传播渠道开始出现<a href="#nimda多传播渠道开始出现"><span>#</span></a></h2><p>同年出现的 **Nimda（尼姆达）**更进一步。</p><p>它不仅能够利用 Web 服务器漏洞，还会通过邮件、网络共享、被感染网站和本地文件传播。</p><p>Nimda 的特别之处就在于“多向传播”。一台主机既可能因为服务器漏洞被感染，也可能因为用户打开邮件附件而感染，还可能通过共享目录或访问被感染网页受到影响。</p><p>因此它不像早期很多蠕虫那样只有一条固定路线，而更像同时打开了多个传播通道：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>邮件附件</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>├─→ 用户执行</span></div></div><div><div><div>3</div></div><div><span>Web 漏洞</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>├─→ 服务器感染</span></div></div><div><div><div>5</div></div><div><span>网络共享</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>├─→ 局域网传播</span></div></div><div><div><div>7</div></div><div><span>被感染网页</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>└─→ 客户端继续受影响</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这说明恶意程序已经开始采用多条传播路线。</p><p>如果一种方法失败，还可以尝试另一种。</p></section><section><h2>SQL Slammer：几百字节代码也能影响全球网络<a href="#sql-slammer几百字节代码也能影响全球网络"><span>#</span></a></h2><p>2003 年出现的 **SQL Slammer（也称“蓝宝石”或“SQL 杀手”）**利用 Microsoft SQL Server 中的缓冲区溢出漏洞传播。</p><p>它体积非常小，并且主要驻留在内存中运行。一旦感染一台服务器，就会高速生成随机 IP 并不断发送攻击数据包。</p><p>它几乎不需要复杂的持久化、文件复制或用户交互，传播逻辑非常直接：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>利用 SQL Server 漏洞</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>恶意代码进入内存</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>高速生成随机 IP</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>向下一批服务器发送攻击数据</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>正因为步骤少、体积小、传播环节短，它才能在极短时间内产生巨大的网络扫描流量。</p><p>这种简单粗暴的设计让它获得了惊人的传播速度，并造成严重网络拥塞。</p><p>Slammer 证明了：</p><blockquote><p>恶意程序不需要庞大复杂，只要传播效率足够高，同样可以给整个互联网造成明显冲击。</p></blockquote></section><section><h2>Blaster 与 Sasser：普通 Windows 用户开始直接感受到漏洞利用型攻击<a href="#blaster-与-sasser普通-windows-用户开始直接感受到漏洞利用型攻击"><span>#</span></a></h2><p>2003 年的 **Blaster Worm（冲击波蠕虫）**利用 Windows RPC 服务漏洞传播。</p><p>它会扫描其他 Windows 主机并尝试利用 RPC 服务缺陷，在成功利用后让目标主机下载并启动蠕虫程序。</p><p>很多用户感染后会看到“系统将在 60 秒后关闭”之类的提示，随后计算机自动重启。这个现象并不完全是“病毒故意弹出倒计时”，而常常是系统关键 RPC 服务崩溃后触发的 Windows 自动关机机制。</p><p>2004 年的 **Sasser Worm（震荡波蠕虫）**则利用 Windows LSASS 服务漏洞传播。</p><p>Sasser 的思路与 Blaster 很相似：扫描网络中的易受攻击主机，利用服务漏洞进入系统，再让新感染主机继续扫描。两者都是典型的“无需用户点击”的网络蠕虫。</p><p>它们共同改变了很多普通用户对“中病毒”的认识。</p><p>过去人们会认为：</p><blockquote><p>只要不下载陌生程序就不会中毒。</p></blockquote><p>但 Blaster 和 Sasser 告诉用户：</p><blockquote><p>即使你什么都没有点击，只要系统存在漏洞并连接互联网，也可能被感染。</p></blockquote><p>这也是防火墙、自动更新和补丁管理后来越来越重要的原因。</p><p>这一时期的攻防关系非常典型：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>攻击方</span></div></div><div><div><div>2</div></div><div><span>自动扫描 + 漏洞利用 + 高速传播</span></div></div><div><div><div>3</div></div><div><span><span>              </span></span><span>↓</span></div></div><div><div><div>4</div></div><div><span>防守方</span></div></div><div><div><div>5</div></div><div><span>防火墙 + 补丁管理 + 自动更新 + 网络隔离</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当“不要乱点附件”已经不足以保护用户时，防守方开始把安全能力直接做到操作系统和网络边界里。</p><p>但这并没有结束攻防。</p><p>随着系统补丁越来越及时、网络服务越来越难被直接攻破，攻击者又开始把注意力转向弱口令、社会工程、浏览器、第三方软件以及用户本身。</p><hr /></section></section>
<section><h1>2000 年代中后期：恶意软件开始从“破坏”转向“赚钱”<a href="#2000-年代中后期恶意软件开始从破坏转向赚钱"><span>#</span></a></h1><p>这一时期，恶意软件的目标逐渐发生变化。</p><p>网上银行、网络游戏、即时通信、电商和数字支付快速发展，互联网账号开始拥有直接经济价值。</p><p>于是攻击目标从“让你的电脑出问题”逐渐变成：</p><ul>
<li>盗取银行账户；</li>
<li>盗取网络游戏账号；</li>
<li>窃取 QQ 等即时通信账号；</li>
<li>发送垃圾邮件；</li>
<li>控制设备；</li>
<li>构建僵尸网络；</li>
<li>引流和广告欺诈。</li>
</ul><p>恶意软件开始明显商业化。</p><p>这一转折，在中文互联网语境中有一个非常有代表性的案例：</p><p><strong>熊猫烧香。</strong></p><hr /></section>
<section><h1>2006—2007：熊猫烧香，从破坏型病毒走向黑产化恶意软件<a href="#20062007熊猫烧香从破坏型病毒走向黑产化恶意软件"><span>#</span></a></h1><p>“熊猫烧香”在 2006 年末到 2007 年初大规模传播。</p><p>它最有辨识度的特征，就是大量被感染文件的图标会变成一个“熊猫举着三炷香”的形象。</p><p>对很多当时的国内 Windows 用户来说，这几乎成为了“电脑中病毒”的视觉符号。</p><p>但如果只把它理解成“一个图标很特别的病毒”，就低估了它在恶意软件演化史上的意义。</p><p>熊猫烧香带有明显的文件感染能力，同时又存在网络传播、下载其他恶意组件和黑产化利用等特征。</p><p>它会感染本地可执行文件，并修改部分网页文件；在一些传播场景中，还会借助局域网共享、可移动存储介质等方式继续扩散。用户看到的大量“熊猫烧香”图标，本质上是文件被病毒修改后的外在表现。</p><p>可以简单理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>感染本地文件</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>修改文件图标 / 内容</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>利用共享或移动介质扩散</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>下载或配合其他恶意组件</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>盗号 / 控制 / 变现</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>相关传播体系中还出现了大量变种，恶意程序的目的也逐渐和盗号、控制、流量变现等利益联系起来。</p><p>这和 CIH 所代表的时代已经完全不同。</p><p>CIH 更像：</p><blockquote><p>感染 → 隐藏 → 复制 → 等待触发 → 破坏。</p></blockquote><p>而到了熊猫烧香时代，思路开始变成：</p><blockquote><p>感染 → 传播 → 控制 → 盗号 / 僵尸网络 / 引流 → 获利。</p></blockquote><p>这个变化非常关键。</p><p>因为它意味着：</p><p><strong>被感染的计算机不再只是破坏对象，而开始成为一种“资产”。</strong></p><p>攻击者没有必要马上把电脑弄坏。</p><p>一台能够联网、能够运行、还能长期接受控制的计算机，可能更加有价值。</p><p>它可以被用来盗取账号、发垃圾邮件、攻击其他网站、挂马、刷流量，甚至加入僵尸网络。</p><p>这也迫使防守方改变思路。</p><p>过去，人们往往把“电脑是否崩溃、文件是否损坏”当成判断感染的重要依据；但面对追求长期控制的木马和僵尸网络，真正危险的状态反而可能是：</p><blockquote><p><strong>电脑看起来一切正常，但攻击者已经悄悄获得了控制权。</strong></p></blockquote><p>因此，防御的重点开始从“阻止明显破坏”，进一步转向进程行为、网络连接、持久化机制和异常活动的检测。</p><p>因此，“熊猫烧香”很适合被看作中文互联网恶意软件从传统病毒文化逐渐走向黑产化的一道标志。</p><hr /></section>
<section><h1>病毒、蠕虫和木马的边界开始模糊<a href="#病毒蠕虫和木马的边界开始模糊"><span>#</span></a></h1><p>到了 2000 年代中后期，“病毒”这个大众称呼已经越来越难准确描述所有恶意程序。</p><p>传统病毒强调的是：</p><p><strong>感染文件，并自我复制。</strong></p><p>蠕虫强调的是：</p><p><strong>独立运行，并通过网络自动传播。</strong></p><p>木马则更加重视：</p><p><strong>伪装、控制和长期驻留。</strong></p><p>但现实中的恶意软件越来越可能同时具备多种能力：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>文件感染</span></div></div><div><div><div>2</div></div><div><span>+</span></div></div><div><div><div>3</div></div><div><span>网络传播</span></div></div><div><div><div>4</div></div><div><span>+</span></div></div><div><div><div>5</div></div><div><span>远程控制</span></div></div><div><div><div>6</div></div><div><span>+</span></div></div><div><div><div>7</div></div><div><span>下载其他组件</span></div></div><div><div><div>8</div></div><div><span>+</span></div></div><div><div><div>9</div></div><div><span>僵尸网络接入</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>因此专业领域越来越倾向于使用一个更大的总称：</p><p><strong>Malware——恶意软件。</strong></p><p>“病毒”只是其中的一种。</p><hr /></section>
<section><h1>僵尸网络：感染本身不再是最终目标<a href="#僵尸网络感染本身不再是最终目标"><span>#</span></a></h1><p>随着恶意软件商业化，<strong>Botnet——僵尸网络</strong>逐渐成为互联网黑产的重要基础设施。</p><p>攻击者通过漏洞、木马、恶意下载或者弱口令控制大量设备，再把这些机器组织成一个可以统一下发指令的网络。</p><p>被控制设备可以用于：</p><ul>
<li>发动 DDoS；</li>
<li>发送垃圾邮件；</li>
<li>扫描漏洞；</li>
<li>挖矿；</li>
<li>广告欺诈；</li>
<li>传播其他恶意软件。</li>
</ul><p>每台设备本身可能不起眼，但数万甚至数十万台设备同时行动，就会形成巨大的攻击能力。</p><p>这个时代最有代表性的恶意软件之一，就是 <strong>Conficker</strong>。</p><hr /></section>
<section><h1>2008：Conficker，漏洞、弱口令与僵尸网络的结合<a href="#2008conficker漏洞弱口令与僵尸网络的结合"><span>#</span></a></h1><p>**Conficker（飞客）**利用 Windows Server Service 漏洞传播，同时也会尝试弱口令、网络共享和可移动设备等其他传播方式。</p><p>它的传播逻辑并不依赖单一入口，而是同时尝试：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>系统漏洞</span></div></div><div><div><div>2</div></div><div><span>+</span></div></div><div><div><div>3</div></div><div><span>共享目录</span></div></div><div><div><div>4</div></div><div><span>+</span></div></div><div><div><div>5</div></div><div><span>弱口令</span></div></div><div><div><div>6</div></div><div><span>+</span></div></div><div><div><div>7</div></div><div><span>可移动设备</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>感染后，它还会修改系统设置、干扰部分安全服务，并尝试与控制基础设施建立联系。</p><p>这意味着 Conficker 已经不只是“会复制自己的蠕虫”，而开始具备明显的长期控制和僵尸网络属性。</p><p>它感染规模巨大。</p><p>更重要的是，感染并不是它的最终目的。</p><p>被感染设备可以继续成为一个大型僵尸网络的一部分，等待后续控制。</p><p>这意味着恶意软件的设计目标发生了根本变化：</p><p>过去：</p><blockquote><p>感染本身就是目的。</p></blockquote><p>现在：</p><blockquote><p>感染只是获得长期控制权的第一步。</p></blockquote><p>这条思路后来在木马、APT、挖矿程序和勒索攻击中不断发展。</p><hr /></section>
<section><h1>2010：Stuxnet，让恶意代码进入物理世界<a href="#2010stuxnet让恶意代码进入物理世界"><span>#</span></a></h1><p>2010 年被发现的 <strong>Stuxnet（震网）</strong>，是整个网络安全历史中的分水岭之一。</p><p>它针对的不是普通用户电脑，而是工业控制系统。</p><p>Stuxnet 利用了多种高级攻击技术，其中包括当时尚未公开修复的 0day 漏洞。</p><section><h3>什么是 0day 漏洞<a href="#什么是-0day-漏洞"><span>#</span></a></h3><p>这里提到的 <strong>0day（Zero-Day，零日）漏洞</strong>，并不是某一种特定类型的漏洞。</p><p>它描述的其实是一个漏洞所处的“时间状态”。</p><p>简单来说：</p><blockquote><p><strong>当一个漏洞已经存在，但软件厂商还不知道它，或者还没有来得及提供有效修复时，这个漏洞就可能被称为 0day 漏洞。</strong></p></blockquote><p>“Zero-Day”这个名字中的“0”，可以理解为：</p><blockquote><p>防御方用于修复这个问题的时间是 <strong>0 天</strong>。</p></blockquote><p>也就是说，当攻击者已经掌握利用方式，而厂商和用户还没有补丁可用时，防守方几乎没有传统意义上的“更新系统就能解决”的机会。</p><div><div><div></div><div>Note</div></div><div><p>这里有三个容易混淆的概念：<strong>0day 漏洞</strong>指尚未得到有效修复的未知或新发现漏洞；<strong>0day Exploit</strong> 指针对它编写的利用方法或代码；<strong>0day Attack</strong> 则是攻击者实际使用这类漏洞利用发动的攻击。三者分别对应“弱点”“利用方式”和“实际攻击行为”。</p></div></div><p>例如，一个研究人员发现了某软件中的严重内存漏洞，但没有公开，也没有通知厂商。</p><p>此时：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>漏洞已经存在</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>厂商尚不知情</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>没有补丁</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>攻击者如果掌握利用方法</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>就可能发动 0day 攻击</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也是为什么 0day 漏洞通常被认为尤其危险。</p><p>普通已知漏洞曝光后，防御方至少可以采取一个最直接的办法：</p><blockquote><p>安装补丁。</p></blockquote><p>但面对 0day 时，这条路可能暂时不存在。</p><section><h4>为什么 0day 很难防御<a href="#为什么-0day-很难防御"><span>#</span></a></h4><p>假设一个已经公开的漏洞被发现。</p><p>正常情况下，安全流程可能是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>发现漏洞</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>厂商分析</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>发布安全公告</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>提供补丁</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>用户安装更新</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而 0day 攻击的情况可能是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>攻击者发现漏洞</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>秘密开发 Exploit</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>开始攻击</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>受害者出现异常</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>安全研究人员发现攻击</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>厂商才知道漏洞存在</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>13</div></div><div><span>开始开发补丁</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说，攻防双方的顺序被反了过来。</p><p>攻击者已经开始行动，而防御方甚至还不知道自己应该修什么。</p><p>传统杀毒软件和入侵检测系统也可能面临类似问题。</p><p>如果某个攻击方式从未出现过，那么基于“已知特征”的检测方法可能没有对应规则。</p><p>因此 0day 的危险之处并不仅仅是：</p><blockquote><p>“这个漏洞很厉害。”</p></blockquote><p>而是：</p><blockquote><p><strong>防御方缺少针对这个具体漏洞提前准备好的补丁和检测规则。</strong></p></blockquote><p>不过，“难防”并不代表“完全无法防御”。</p><p>现代安全体系不会只依赖“知道漏洞之后打补丁”，还会通过：</p><ul>
<li>最小权限；</li>
<li>浏览器与应用沙箱；</li>
<li>DEP / NX；</li>
<li>ASLR；</li>
<li>EDR 行为检测；</li>
<li>网络隔离；</li>
<li>应用白名单；</li>
<li>异常行为监控；</li>
</ul><p>来限制未知漏洞成功利用之后能够造成的影响。</p><p>例如，安全软件可能不知道：</p><blockquote><p>“这是某个从未见过的 0day Exploit。”</p></blockquote><p>但它仍然可能发现：</p><blockquote><p>“一个普通文档程序突然启动了命令解释器，并开始连接陌生服务器。”</p></blockquote><p>这种检测关注的是<strong>行为是否异常</strong>，而不是必须提前知道漏洞名称。</p><p>因此现代安全越来越强调：</p><blockquote><p><strong>即使不知道漏洞在哪里，也要让攻击者利用漏洞之后难以继续行动。</strong></p></blockquote></section><section><h4>0day 并不会永远是 0day<a href="#0day-并不会永远是-0day"><span>#</span></a></h4><p>“0day”并不是漏洞永久拥有的属性。</p><p>一旦漏洞被公开、厂商确认问题并发布补丁，它通常就不再属于严格意义上的 0day。</p><p>此后的攻击可能变成：</p><p><strong>N-day 漏洞利用。</strong></p><p>也就是：</p><blockquote><p>漏洞已经公开，甚至已经存在补丁，但仍然有人没有及时更新系统。</p></blockquote><p>现实中，这种情况其实非常常见。</p><div><div><div></div><div>Tip</div></div><div><p><strong>0day 并不会永远是 0day。</strong> 一旦漏洞公开、厂商确认问题并发布补丁，后续围绕它展开的攻击通常就更接近 N-day 漏洞利用。此时真正的问题往往不再是“有没有补丁”，而是“补丁有没有真正部署”。</p></div></div><p>很多大规模网络攻击并不需要昂贵的 0day。</p><p>攻击者只需要寻找：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>已经公开漏洞</span></div></div><div><div><div>2</div></div><div><span>+</span></div></div><div><div><div>3</div></div><div><span>已经存在 Exploit</span></div></div><div><div><div>4</div></div><div><span>+</span></div></div><div><div><div>5</div></div><div><span>仍未安装补丁的设备</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>就可能继续发动攻击。</p><p>WannaCry 就非常适合用来理解这种区别。</p><p>它爆发时，Microsoft 已经发布了对应 SMB 漏洞的安全更新。</p><p>因此 WannaCry 所利用的并不是“当时还没有修复方案的 0day”，而是：</p><blockquote><p><strong>已经存在补丁，但大量设备尚未完成更新的已知漏洞。</strong></p></blockquote><p>这也说明了一个很重要的现实：</p><blockquote><p>0day 最危险的地方是“还不知道怎么修”，而已知漏洞最危险的地方则往往是“明明已经能修，却还没有修”。</p></blockquote><p>它并不是依靠“一个超级漏洞”完成全部攻击，而是把多个漏洞利用环节与不同攻击能力组合在一起。</p><p>这种思路后来常被称为<strong>漏洞利用链</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>进入目标系统</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>提升权限</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>绕过限制</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>在内部继续传播</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>接触工业控制软件</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>修改控制逻辑</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>攻击链中的不同漏洞利用环节各自解决一部分问题，组合起来就可能突破原本彼此隔离的多层防线。</p><p>这也是为什么 0day 尤其具有价值：防御方在漏洞尚未公开、补丁尚未普及之前，往往很难针对性阻断这一环。</p><p>它最终影响的是现实世界中的工业设备。</p></section></section><section><h3>Stuxnet 的真正目标：核设施中的工业设备<a href="#stuxnet-的真正目标核设施中的工业设备"><span>#</span></a></h3><p>Stuxnet 最特殊的地方，还在于它并不是为了随机感染尽可能多的电脑。</p><p>后续研究普遍认为，它的主要目标是伊朗纳坦兹（Natanz）的铀浓缩设施。</p><p>Stuxnet 会检查目标系统中是否存在特定的 Siemens 工业控制软件、变频器以及符合特定运行条件的设备。只有当环境与预设目标高度吻合时，它才会进一步修改控制逻辑。</p><p>这意味着它并不是：</p><blockquote><p>“感染之后就立刻破坏。”</p></blockquote><p>而更像是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>进入系统</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>识别工业控制环境</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>确认设备与运行条件</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>只有目标符合预设条件</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>修改控制逻辑</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>影响离心机等物理设备</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种高度针对性的设计，也是 Stuxnet 与普通蠕虫最明显的区别之一。</p><p>它可以在多种 Windows 系统中传播，但真正准备执行工业破坏载荷的环境却非常具体。</p><p>这也是为什么 Stuxnet 经常被视为现代“网络武器”概念的重要分水岭：恶意代码的目标已经不只是窃取数据或破坏计算机，而是通过控制软件进一步影响现实世界中的工业过程。</p><p>这意味着恶意软件第一次非常明确地展示出：</p><blockquote><p>数字世界中的代码，可以直接改变现实世界中的物理过程。</p></blockquote><p>Stuxnet 之后，网络攻击开始越来越频繁地与国家安全、工业安全和关键基础设施联系在一起。</p><hr /></section></section>
<section><h1>APT：漏洞更多只是攻击链的初始入口<a href="#apt漏洞更多只是攻击链的初始入口"><span>#</span></a></h1><p>随着攻击技术的发展，安全行业开始频繁使用一个术语：</p><p><strong>APT——Advanced Persistent Threat，高级持续性威胁。</strong></p><p>和早期“发现漏洞 → 发动攻击 → 完成破坏”的模式相比，APT 更强调长期、隐蔽和有明确目标的入侵过程。</p><p>攻击者可能先调查目标组织，然后通过钓鱼邮件、暴露在公网的服务、弱口令或软件漏洞获得最初访问权限。</p><p>之后，他们往往不会立即发动破坏，而是继续：</p><ul>
<li>提升权限；</li>
<li>窃取管理员账号；</li>
<li>横向移动；</li>
<li>建立后门；</li>
<li>搜索高价值系统；</li>
<li>长期窃取数据。</li>
</ul><p>到了这里，单个漏洞已经不再是整个攻击过程的中心。</p><p>它只是整个攻击链的入口之一。</p><p>APT 也让“攻防战”的不对称性变得更加明显。</p><p>防守方需要保护整个组织中的账号、终端、服务器、网络边界和内部系统；攻击者却只需要在其中找到一条能够继续深入的路径。</p><p>因此现代防御逐渐不再假设：</p><blockquote><p>“只要把入口堵住，攻击就结束了。”</p></blockquote><p>而是开始强调：</p><blockquote><p><strong>即使攻击者已经进入，也要尽早发现异常、限制横向移动，并阻止攻击继续扩大。</strong></p></blockquote><p>这就是后来纵深防御、零信任、EDR 和持续监控等思路越来越重要的背景之一。</p><div><div><div></div><div>Tip</div></div><div><p>“零信任”并不是“任何人都不可信”的口号，而是强调：<strong>不能仅因为设备或用户已经进入内网，就默认后续访问全部可信。</strong> 身份、设备状态、权限和访问行为仍然需要持续验证。</p></div></div><hr /></section>
<section><h1>2014：Heartbleed，基础组件漏洞影响整个互联网<a href="#2014heartbleed基础组件漏洞影响整个互联网"><span>#</span></a></h1><p>2014 年，**Heartbleed（心脏滴血漏洞）**曝光。</p><p>它存在于被大量服务器使用的 OpenSSL 中。</p><p>漏洞与 TLS Heartbeat 功能有关。这个功能原本只是为了让通信双方确认连接仍然存活：客户端发送一小段数据，服务器再把这段数据原样返回。</p><p>问题出在长度检查。</p><p>可以把一次正常请求理解成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>我发送了 5 个字节，</span></div></div><div><div><div>2</div></div><div><span>请把这 5 个字节返回给我。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而存在漏洞的实现没有认真核对“实际收到多少数据”和“对方声称有多少数据”是否一致。</p><p>于是攻击者可以实际只发送很短的数据，却声明：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>我发送了 5 个字节，</span></div></div><div><div><div>2</div></div><div><span>但请返回 65535 个字节。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>服务器为了凑够这个长度，就可能继续读取相邻内存中的其他内容并一并返回。</p><p>因此，Heartbleed 本质上不是“攻进去执行代码”，而是一种<strong>越界读取</strong>：攻击者让程序读出了原本不应该暴露给它的内存。</p><p>泄露的数据可能包含 Cookie、用户名、密码甚至其他敏感信息。</p><p>Heartbleed 最重要的意义，不只在于漏洞本身。</p><p>它揭示了现代互联网的另一个事实：</p><blockquote><p>很多看起来彼此独立的网站，其实依赖着相同的底层基础组件。</p></blockquote><p>于是，一个基础库中的漏洞，就可能同时影响数量庞大的服务器。</p><p>这为后来受到广泛关注的软件供应链安全问题埋下了伏笔。</p><hr /></section>
<section><h1>2016：Mirai，把摄像头和路由器变成攻击武器<a href="#2016mirai把摄像头和路由器变成攻击武器"><span>#</span></a></h1><p>随着摄像头、路由器、电视、打印机和各种智能设备接入互联网，一个新的攻击面开始出现：</p><p><strong>IoT——物联网设备。</strong></p><p>很多设备为了降低成本和简化部署，存在默认密码、无法更新固件、开放不必要服务、硬编码凭据等问题。</p><p>2016 年，<strong>Mirai Botnet</strong> 让这一问题彻底进入公众视野。</p><p>Mirai 会扫描互联网上的设备，并尝试使用一批常见默认用户名和密码登录。</p><p>这里并不一定存在复杂的“程序漏洞”。</p><p>很多设备出厂时预设了固定账号，用户又从未修改密码；有些设备甚至把管理服务直接暴露在公网。于是攻击者只需要不断尝试常见凭据，就可能直接获得远程登录权限。</p><p>它的逻辑更像：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>扫描设备</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>发现远程管理服务</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>尝试默认账号 / 弱口令</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>登录成功</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>植入 Mirai</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这说明安全问题并不总来自代码错误。</p><p><strong>不安全的默认配置和身份认证设计，同样可以形成大规模攻击面。</strong></p><p>一旦成功，这些摄像头、路由器和 DVR 就会被纳入僵尸网络。</p><p>之后，它们可以被统一用于发动大规模 DDoS。</p><p>Mirai 最值得记住的一点是：</p><blockquote><p>互联网攻击使用的“计算资源”，已经不再局限于电脑和服务器。</p></blockquote><p>一台无人关注的摄像头，也可能成为攻击网络的一部分。</p><hr /></section>
<section><h1>2017：WannaCry，把网络蠕虫和勒索软件合二为一<a href="#2017wannacry把网络蠕虫和勒索软件合二为一"><span>#</span></a></h1><p>2017 年，**WannaCry（想哭）**爆发。</p><p>它成为现代互联网安全史上最著名的恶意软件之一。</p><p>WannaCry 利用了 Windows SMB 协议漏洞，其中最广为人知的漏洞利用方式就是 <strong>EternalBlue（永恒之蓝）</strong>。</p><div><div><div></div><div>Note</div></div><div><p>这里也很容易混淆概念：<strong>445 是端口，SMB 是协议，漏洞存在于具体实现中，而 EternalBlue 是针对其中漏洞的利用方式之一。</strong> 因此，“445 端口就是永恒之蓝漏洞”并不是严谨的说法。</p></div></div><p>SMB 常见于 TCP 445 端口。</p><p>SMB 本身是 Windows 中用于文件和资源共享的重要协议，问题并不是“445 端口天然有毒”，而是旧版 SMB 实现中曾存在严重的内存处理缺陷。</p><p>EternalBlue（永恒之蓝）利用的核心思路，可以简单理解为：</p><blockquote><p>向存在缺陷的 SMB 服务发送经过特殊构造的网络数据，让系统在处理这些数据时发生内存破坏，最终获得远程代码执行能力。</p></blockquote><p>也就是说，攻击者并不需要先登录系统。</p><p>只要目标主机的 SMB 服务暴露在网络中、系统版本存在漏洞并且没有安装补丁，就可能在处理恶意网络请求时被直接攻破。</p><p>因此 WannaCry 真正可怕的地方，不只是“它会加密文件”，而是 EternalBlue 给了它一个能够自动进入下一台机器的传播入口。</p><p>WannaCry 的危险之处在于，它同时具有两种能力：</p><p>第一种是<strong>蠕虫传播</strong>。</p><p>它会扫描其他存在漏洞的 Windows 主机并尝试自动感染。</p><p>第二种是<strong>勒索</strong>。</p><p>感染之后，它会加密用户文件并索要赎金。</p><p>于是传播流程变成了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>扫描 445</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>利用 SMB 漏洞</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>进入系统</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>加密文件</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>继续扫描下一台设备</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它把两个时代的恶意软件特征结合到了一起：</p><p><strong>2000 年代的高速网络蠕虫 + 2010 年代的勒索软件。</strong></p><p>也正因如此，WannaCry 能够在短时间内对全球大量医院、企业、学校和机构造成影响。</p><p>从攻防角度看，WannaCry 还有一个格外值得注意的地方：</p><p><strong>防守方其实已经拥有补丁。</strong></p><p>Microsoft 在 WannaCry 大规模爆发之前已经发布了针对相关 SMB 漏洞的安全更新，但现实中的大量设备并没有及时安装。</p><p>于是，这场攻防并不是“攻击者掌握了防守方完全不知道的秘密”，而是变成了另一种竞赛：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>防守方发布补丁</span></div></div><div><div><div>2</div></div><div><span><span>       </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>用户是否及时更新？</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>攻击者开始大规模扫描</span></div></div><div><div><div>6</div></div><div><span><span>       </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>尚未更新的设备被感染</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这说明补丁发布并不等于漏洞风险自动消失。</p><p>在很多现实事件中，防守方真正需要争取的是：</p><blockquote><p><strong>能否在攻击者完成大规模武器化和扫描之前，把修复真正部署到足够多的设备上。</strong></p></blockquote><hr /></section>
<section><h1>NotPetya：看起来像勒索软件，但更接近破坏程序<a href="#notpetya看起来像勒索软件但更接近破坏程序"><span>#</span></a></h1><p>不久之后，又出现了 <strong>NotPetya</strong>。</p><p>它表面上看起来很像勒索软件。</p><p>系统被感染后，会显示勒索信息。</p><p>但 NotPetya 的核心行为并不只是“加密几个用户文件”，而会破坏磁盘相关数据结构，并利用企业网络中的凭据和漏洞继续横向传播。</p><section><h3>NotPetya 付款后也几乎无法恢复数据<a href="#notpetya-付款后也几乎无法恢复数据"><span>#</span></a></h3><p>NotPetya 看起来很像传统勒索软件：</p><p>系统被感染后会显示勒索信息，并要求受害者支付比特币。</p><p>但研究人员分析其加密与密钥恢复机制后发现了一个关键问题：</p><blockquote><p>它并没有保留一套正常、可行的解密流程。</p></blockquote><p>传统勒索软件如果真的以“收钱”为目的，就必须保留某种方式，让攻击者在收到付款后能够为受害者恢复数据，否则商业模式无法成立。</p><p>也就是说：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>生成加密密钥</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>加密受害者数据</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>保留恢复密钥所需的信息</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>受害者付款</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>攻击者提供解密能力</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但 NotPetya 并不是这样。</p><p>其勒索界面中显示的所谓安装 ID，并没有像正常勒索软件那样保存恢复密钥所需要的有效信息，而更接近随机生成的数据。</p><div><div><div></div><div>Note</div></div><div><p>这也是为什么 NotPetya 通常不被简单理解成“一个写坏了的勒索软件”。更常见的判断是：<strong>它从设计上就更接近破坏程序，勒索界面只是外衣。</strong></p><p>受害者即使支付赎金，也不能够完全恢复数据。它的勒索界面更像是一层伪装，而不是一个真正可以在付款后完成恢复的商业勒索流程。</p></div></div><p>结果就是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>数据被破坏 / 加密</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>显示勒索信息</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>受害者付款</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>攻击者也无法据此还原正确密钥</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>数据依然无法正常恢复</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也是为什么 NotPetya 常被归类为 <strong>Wiper——数据擦除 / 破坏型恶意软件</strong>，而不是普通意义上的勒索软件。</p><p>这个事件提醒人们：</p><blockquote><p>恶意软件表面展示的“目的”，不一定是真正目的。不要轻易相信黑客给出的条件。</p></blockquote><p>它可能只是把纯粹的“破坏”行为美其名曰“勒索”，或者说，它只是单纯想要受害者交钱，至于之后的事情概不负责。</p><hr /></section></section>
<section><h1>勒索软件从单一恶意程序升级成企业级入侵行动<a href="#勒索软件从单一恶意程序升级成企业级入侵行动"><span>#</span></a></h1><p>WannaCry 之后，勒索软件继续快速演化。</p><p>早期勒索程序更依赖大规模随机感染。</p><p>现代勒索攻击则越来越倾向于：</p><p>先进入企业网络，再进行长期准备。</p><p>攻击者可能通过：</p><ul>
<li>VPN 漏洞；</li>
<li>RDP 弱口令；</li>
<li>钓鱼邮件；</li>
<li>Web 漏洞；</li>
<li>被盗账号；</li>
</ul><p>获得最初访问权限。</p><p>随后不会立即加密文件，而是先横向移动、获取管理员权限、寻找备份服务器、窃取数据并关闭安全软件。</p><p>最后再同时加密大量设备。</p><p>这时，勒索软件已经不再只是一个孤立运行的恶意程序。</p><p>它变成了整个网络入侵行动的最后阶段。</p><p>同时，“双重勒索”也开始流行：</p><blockquote><p>不付款，不仅不给解密工具，还可能公开已经窃取的数据。</p></blockquote><hr /></section>
<section><h1>供应链攻击：恶意代码开始藏进“正常软件”<a href="#供应链攻击恶意代码开始藏进正常软件"><span>#</span></a></h1><p>现代软件开发越来越依赖第三方组件。</p><p>一个普通应用背后可能依赖：</p><ul>
<li>npm 软件包；</li>
<li>Python 库；</li>
<li>Java 组件；</li>
<li>Docker 镜像；</li>
<li>CI/CD 插件；</li>
<li>第三方 SDK。</li>
</ul><p>于是攻击者开始采用另一种思路：</p><p>如果一千家公司都使用同一个软件，那么不一定要逐个攻击这一千家公司。</p><p>只要攻击它们共同信任的软件供应链即可。</p><p>2020 年曝光的 SolarWinds 事件就是这一领域极具代表性的案例。</p><p>攻击者影响了软件构建或更新链路，使恶意代码进入正常软件版本。</p><p>以 SolarWinds 事件为例，真正进入受害环境的恶意后门后来常被称为 <strong>SUNBURST</strong>。它并不是靠用户下载“可疑恶意文件”进入系统，而是跟随正常软件更新一起被安装。</p><p>这使供应链攻击和传统恶意软件传播方式形成鲜明对比：</p><blockquote><p>传统恶意软件努力伪装成“可信软件”，而供应链攻击则试图直接进入“可信软件本身”。</p></blockquote><p>传统攻击往往发生在用户下载安装软件之后，而供应链攻击则把目标向前移动到了“软件是如何被生产出来的”这一环节。</p><p>可以把正常链路理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>源代码</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>构建系统</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>签名 / 打包</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>官方更新服务器</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>用户安装</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果攻击者控制了其中某个关键环节，就可能让恶意代码在发布前进入最终版本。</p><p>这样一来，用户看到的可能依然是：</p><ul>
<li>官方网站；</li>
<li>正常版本号；</li>
<li>合法更新程序；</li>
<li>正常安装流程。</li>
</ul><p>但实际安装包中已经包含后门。</p><p>用户通过官方渠道安装更新，却可能同时把后门带入企业网络。</p><p>这种攻击最危险的地方在于，它利用的是：<strong>信任。</strong></p><p>陌生程序容易被警惕，但官方更新通常会被认为可靠。</p><p>供应链攻击恰恰试图进入这个可信链条内部。</p><div><div><div></div><div>Tip</div></div><div><p>供应链攻击最值得警惕的一点，并不是“用户下载了来历不明的软件”，恰恰相反：<strong>用户可能完全遵循正常流程，从官方渠道安装了看起来可信的更新。</strong> 问题发生在信任链更上游的位置。</p></div></div><hr /></section>
<section><h1>2021：Log4Shell，一个日志组件震动互联网<a href="#2021log4shell一个日志组件震动互联网"><span>#</span></a></h1><p>2021 年出现的 <strong>Log4Shell（CVE-2021-44228）</strong> 是现代漏洞史上的代表性事件。</p><p>问题来自 Java 生态中广泛使用的日志组件 Log4j。</p><p>很多服务器甚至不知道自己间接使用了它。</p><p>Log4Shell 的核心问题出在 Log4j 某些版本会对日志内容中的特殊表达式继续进行解析。</p><p>正常情况下，日志组件的职责只是“把字符串记下来”。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户登录失败：Alice</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>理论上，这段内容应该原样写进日志。</p><p>但存在漏洞的版本会把某些特殊格式的字符串理解成“需要继续解析的指令”，并可能进一步触发 JNDI 资源查找。</p><p>这就导致一个非常危险的边界被打破：</p><blockquote><p>原本只是要被记录的数据，开始影响程序接下来要做什么。</p></blockquote><p>在特定条件下，这种远程资源查找可能进一步导致攻击者控制代码执行流程。</p><p>因此，Log4Shell 和 SQL 注入在思想上其实有一点相似：都是<strong>本应作为数据处理的内容，被错误地赋予了指令能力</strong>。</p><p>漏洞曝光后，攻击者迅速开始自动扫描互联网上存在漏洞的服务器。</p><p>Log4Shell 很能体现现代攻防节奏究竟有多快。</p><p>漏洞公开之后，攻击者可以迅速把公开信息编写成扫描和利用程序；与此同时，防守方则需要完成资产排查、确认依赖关系、升级组件、临时缓解、监控异常请求等一系列工作。</p><p>于是双方争夺的不只是“谁懂这个漏洞”，而是：</p><blockquote><p><strong>谁能更快把知识转化成行动。</strong></p></blockquote><p>攻击方希望在补丁和缓解措施普及之前找到更多目标，防守方则希望在大规模利用发生之前确认自己究竟哪些系统受到影响。</p><p>一旦成功获得代码执行能力，攻击往往不会停止在“证明漏洞存在”。</p><p>攻击者可能进一步下载：</p><ul>
<li>挖矿程序；</li>
<li>远程控制木马；</li>
<li>僵尸网络程序；</li>
<li>勒索软件；</li>
<li>其他后门。</li>
</ul><p>这里也能看出现代恶意软件的一个重要特点：<strong>模块化。</strong></p><p>最先利用 Log4Shell 的代码可能只负责“打开入口”，真正的恶意功能则由后续下载的程序承担。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Log4Shell</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>获得初始执行能力</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>下载器 / Loader</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>木马、矿工、勒索程序、Botnet 客户端</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以在现代攻击中，“漏洞利用程序”和“最终恶意软件”往往已经不是同一个东西。</p><p>现代攻击链越来越像：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>漏洞</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>初始代码执行</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>下载器</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>木马 / 后门</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>横向移动</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>窃密 / 挖矿 / 勒索</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>漏洞只是入口。</p><p>恶意软件负责真正完成后续攻击。</p><p>Log4Shell 还暴露了另一个现实问题：</p><p>企业可能根本不知道自己是否使用了某个存在漏洞的组件。</p><p>即使业务应用可能没有直接引用 Log4j，却可能通过框架、库和依赖链间接引入它。</p><p>这也推动了 <strong>SBOM——Software Bill of Materials，软件物料清单</strong>等概念的发展。</p><hr /></section>
<section><h1>攻击面从软件扩展到云、身份系统和硬件<a href="#攻击面从软件扩展到云身份系统和硬件"><span>#</span></a></h1><p>今天谈到的安全问题，范围已经远远超出传统程序漏洞的范畴。</p><p>现代攻击面可能来自：</p><ul>
<li>云平台权限配置错误；</li>
<li>OAuth 与身份认证逻辑缺陷；</li>
<li>API 越权；</li>
<li>Kubernetes 与容器环境配置错误或逃逸漏洞；</li>
<li>CI/CD 凭据泄露；</li>
<li>浏览器沙箱逃逸；</li>
<li>虚拟化平台漏洞；</li>
<li>软件依赖漏洞；</li>
<li>CPU 微架构问题。</li>
</ul><p>2018 年公开的 Spectre（幽灵漏洞）和 Meltdown（熔断漏洞）就与现代处理器的推测执行机制有关。</p><p>现代 CPU 为了提高速度，会尝试提前执行“很可能接下来就要运行”的指令。这种机制叫做<strong>推测执行</strong>。</p><p>即使后来发现猜错了，CPU 也会在逻辑上撤销这些执行结果。</p><p>问题在于：</p><blockquote><p>逻辑结果虽然被撤销了，但某些微架构痕迹——例如缓存状态——可能已经留下。</p></blockquote><p>攻击者可以通过测量访问某些内存位置所需的细微时间差，间接推测之前发生过什么。</p><p>这就是所谓的<strong>侧信道攻击（Side-Channel Attack）</strong>。</p><div><div><div></div><div>Note</div></div><div><p>侧信道攻击并不等于“直接绕过权限读取秘密”。它更像是在观察程序运行时留下的时间、缓存、功耗等痕迹，再从这些痕迹中反推出原本不可见的信息。</p></div></div><section><h3>什么是侧信道攻击<a href="#什么是侧信道攻击"><span>#</span></a></h3><p>传统攻击通常会直接寻找一个“入口”：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>缓冲区溢出</span></div></div><div><div><div>2</div></div><div><span>SQL 注入</span></div></div><div><div><div>3</div></div><div><span>身份验证绕过</span></div></div><div><div><div>4</div></div><div><span>权限配置错误</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>攻击者通过这个入口，直接读取、修改或执行原本不允许进行的操作。</p><p>侧信道攻击的思路却完全不同。</p><p>它并不一定直接突破权限边界，也不一定让程序执行“错误的功能”。</p><p>它观察的是系统运行过程中无意泄露出来的<strong>附加信息</strong>。</p><p>例如：</p><ul>
<li>执行某段代码用了多长时间；</li>
<li>某块数据是否已经进入 CPU 缓存；</li>
<li>芯片执行计算时消耗了多少电力；</li>
<li>设备工作时产生了怎样的电磁辐射；</li>
<li>某些硬件工作时发出了什么声音。</li>
</ul><p>这些信息本来都不是程序准备提供给用户的“输出”。</p><p>但攻击者可能通过反复测量和统计，从中推断出隐藏数据。</p><p>所以可以把侧信道理解为：</p><blockquote><p><strong>程序没有主动告诉你秘密，但它运行时留下的“痕迹”泄露了秘密。</strong></p></blockquote><p>它和传统漏洞最大的区别，也正在这里。</p><p>正常的信息通道可能是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>程序输入</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>程序处理</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>程序输出</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而侧信道则更像：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>程序输入</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>程序处理</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>├──→ 正常输出</span></div></div><div><div><div>5</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>└──→ 时间 / 缓存 / 功耗 / 电磁 / 声音等痕迹</span></div></div><div><div><div>7</div></div><div><span><span>                         </span></span><span>↓</span></div></div><div><div><div>8</div></div><div><span><span>                    </span></span><span>攻击者观察</span></div></div><div><div><div>9</div></div><div><span><span>                         </span></span><span>↓</span></div></div><div><div><div>10</div></div><div><span><span>                    </span></span><span>推测隐藏信息</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>时间侧信道：程序“快一点”还是“慢一点”也可能泄密<a href="#时间侧信道程序快一点还是慢一点也可能泄密"><span>#</span></a></h3><p>最直观的一类侧信道是<strong>时间侧信道（Timing Side Channel）</strong>。</p><p>假设一个程序在比较密码时，从第一个字符开始逐个比较：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>输入：abcdef</span></div></div><div><div><div>2</div></div><div><span>正确：abcxyz</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果前 3 个字符正确，程序就会比“第 1 个字符就错误”的情况多执行几次比较，多消耗一点时间。</p><p>理论上，这种差异可能只有极短的时间。</p><p>但如果攻击者能够重复请求很多次，并进行统计，就可能发现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>猜错第 1 位 → 平均响应很快</span></div></div><div><div><div>2</div></div><div><span>猜对第 1 位 → 平均稍慢</span></div></div><div><div><div>3</div></div><div><span>猜对前 2 位 → 又稍慢一点</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样一来，虽然程序从来没有直接告诉攻击者“你猜对了几个字符”，执行时间本身却变成了一种额外的信息通道。</p><p>密码、身份验证和密码学实现中都可能出现类似问题。</p><p>这也是为什么很多安全敏感代码会采用**恒定时间（Constant-Time）**设计：尽量让不同秘密数据对应的执行路径和耗时保持一致，减少可被观察的差异。</p></section><section><h3>缓存侧信道：CPU 的“记忆”也会留下痕迹<a href="#缓存侧信道cpu-的记忆也会留下痕迹"><span>#</span></a></h3><p>现代 CPU 的运行速度远高于主内存。</p><p>为了避免每次都去较慢的内存中读取数据，处理器会把最近使用的数据保存在速度更快的 <strong>Cache——高速缓存</strong> 中。</p><p>于是，同一份数据可能出现两种情况：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>数据已经在 Cache 中</span></div></div><div><div><div>2</div></div><div><span>→ 读取很快</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>数据不在 Cache 中</span></div></div><div><div><div>5</div></div><div><span>→ 需要访问更慢的内存</span></div></div><div><div><div>6</div></div><div><span>→ 读取较慢</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种时间差对正常用户来说几乎无法察觉，但对程序来说却可以测量。</p><p>攻击者因此可能通过观察：</p><blockquote><p>“哪些内存位置现在访问得特别快？”</p></blockquote><p>来反推出：</p><blockquote><p>“受害程序刚才可能访问了哪些数据？”</p></blockquote><p>这就是缓存侧信道的基本思想。</p><p>它并不是直接读取另一个程序的秘密，而是观察秘密数据在 CPU 缓存中留下的痕迹。</p><p>简化来看：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>受害程序访问秘密数据</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>某些数据进入 Cache</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>攻击者测量不同位置的访问时间</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>发现某些位置异常快速</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>推测受害程序刚刚访问过什么</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>很多现代微架构攻击都会利用这种原理。</p></section><section><h3>Spectre 与 Meltdown 为什么都离不开侧信道<a href="#spectre-与-meltdown-为什么都离不开侧信道"><span>#</span></a></h3><p>回到 Spectre 和 Meltdown。</p><p>现代 CPU 为了提高性能，会进行<strong>推测执行</strong>：</p><blockquote><p>如果处理器认为某条分支很可能会走某个方向，它可能提前执行后面的指令。</p></blockquote><p>如果预测正确，就节省了等待时间。</p><p>如果预测错误，CPU 会在逻辑上撤销这些结果。</p><p>问题在于：</p><p><strong>逻辑结果可以撤销，但微架构中的痕迹未必完全消失。</strong></p><p>例如，一段本不应该真正完成的推测执行，可能短暂访问了某个敏感数据，并根据这个数据访问了不同的缓存位置。</p><p>即使之后 CPU 宣布：</p><blockquote><p>“刚才那段推测执行无效，结果作废。”</p></blockquote><p>缓存状态可能已经发生变化。</p><p>于是就出现这样的过程：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CPU 推测执行</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>短暂接触敏感数据</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>根据秘密值访问不同数据</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>秘密相关的数据进入 Cache</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>CPU 发现预测错误</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>逻辑执行结果被撤销</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>13</div></div><div><span>Cache 状态仍然存在差异</span></div></div><div><div><div>14</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>15</div></div><div><span>攻击者测量访问时间</span></div></div><div><div><div>16</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>17</div></div><div><span>反推出秘密数据</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这一步非常关键：</p><blockquote><p><strong>推测执行负责让“不应该被正常读取的数据”短暂参与计算，侧信道负责把这个秘密带出权限边界。</strong></p></blockquote><p>如果没有侧信道，即使 CPU 在内部短暂接触过秘密，攻击者也未必能够得到它。</p><p>因此，Spectre 与 Meltdown 并不能简单理解成“CPU 直接把内存交给攻击者”。</p><p>它们真正巧妙的地方，在于：</p><p><strong>瞬态执行 + 微架构痕迹 + 侧信道读取。</strong></p></section><section><h3>Spectre 和 Meltdown 的思路并不完全相同<a href="#spectre-和-meltdown-的思路并不完全相同"><span>#</span></a></h3><p>虽然两者经常被放在一起讨论，但其核心问题并不完全一样。</p><p><strong>Meltdown</strong> 更接近于利用处理器在权限检查最终生效之前，短暂执行了本不应该执行的内存访问。</p><p>可以粗略理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>尝试读取高权限内存</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>CPU 短暂继续执行</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>秘密影响 Cache</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>权限检查失败</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>正常结果被丢弃</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>通过 Cache 侧信道恢复秘密</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而 <strong>Spectre</strong> 更偏向于欺骗 CPU 的分支预测机制，让受害程序在推测执行阶段沿着一个本来不应该走的路径执行，并留下与秘密相关的缓存痕迹。</p><p>可以简化为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>训练 CPU 分支预测</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>诱导错误推测</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>受害程序短暂执行错误路径</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>访问与秘密相关的数据</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>Cache 状态改变</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>攻击者测量 Cache</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>13</div></div><div><span>推测秘密</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以，Meltdown 更突出“权限隔离被瞬态执行绕过”，Spectre 更突出“诱导受害程序错误推测执行”。</p><p>但两者最终把秘密带出来时，都严重依赖侧信道。</p></section><section><h3>侧信道并不只有“时间”和“缓存”<a href="#侧信道并不只有时间和缓存"><span>#</span></a></h3><p>侧信道攻击远比 CPU 缓存更加宽泛。</p><p>如果一个设备在处理秘密数据时，会在物理世界中留下可测量差异，就可能形成侧信道。</p><p>例如：</p><section><h4>功耗侧信道<a href="#功耗侧信道"><span>#</span></a></h4><p>芯片进行不同运算时，耗电量会略有不同。</p><p>如果攻击者能够精确测量智能卡、嵌入式设备或加密芯片的功耗波形，就可能分析其内部正在进行什么运算。</p><p>在密码学硬件研究中，这类技术尤其重要。</p></section><section><h4>电磁侧信道<a href="#电磁侧信道"><span>#</span></a></h4><p>电子设备运行时会产生电磁辐射。</p><p>不同计算活动可能形成不同的电磁特征。</p><p>在非常精细的测量条件下，攻击者可能利用这些差异分析设备内部操作。</p></section><section><h4>声学侧信道<a href="#声学侧信道"><span>#</span></a></h4><p>有些设备在不同工作状态下会产生不同声音。</p><p>键盘敲击、硬盘活动，甚至某些电子元件在高负载时产生的微弱噪声，都可能成为信息来源。</p><p>这类攻击听起来很像科幻，但其背后的思想和缓存侧信道完全一样：</p><blockquote><p><strong>不观察程序“告诉你什么”，而观察程序运行时“无意间泄露了什么”。</strong></p></blockquote></section></section><section><h3>为什么侧信道攻击特别难防<a href="#为什么侧信道攻击特别难防"><span>#</span></a></h3><p>传统漏洞往往可以定位到一段明确的错误代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>这里少了长度检查</span></div></div><div><div><div>2</div></div><div><span>这里没有转义输入</span></div></div><div><div><div>3</div></div><div><span>这里错误地开放了权限</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修复时，只需要改变相应逻辑。</p><p>而侧信道往往来自更深层的系统行为。</p><p>例如：</p><ul>
<li>缓存本来就是为了让 CPU 更快；</li>
<li>分支预测本来就是为了提升性能；</li>
<li>功耗是芯片运行必然产生的现象；</li>
<li>执行时间不可能真正做到完全没有任何波动。</li>
</ul><p>也就是说，侧信道经常利用的不是一个单纯的“Bug”，而是：</p><blockquote><p><strong>性能优化、物理规律和安全隔离之间的矛盾。</strong></p></blockquote><p>因此防御也更加复杂。</p><p>常见思路包括：</p><ul>
<li>使用恒定时间算法；</li>
<li>避免秘密数据影响可观察的控制流；</li>
<li>在不同安全域之间加强缓存隔离；</li>
<li>对推测执行增加限制；</li>
<li>更新 CPU 微码和操作系统；</li>
<li>在密码学硬件中加入功耗和电磁防护；</li>
<li>对高度敏感环境进行物理隔离。</li>
</ul><p>但这些措施往往都会付出成本。</p><p>最典型的成本就是：<strong>性能。</strong></p><p>很多侧信道之所以存在，恰恰是因为计算机为了跑得更快，采用了缓存、预测执行、乱序执行等优化。</p><p>而为了安全重新限制这些优化，就可能牺牲一部分速度。</p><p>因此 Spectre 和 Meltdown 之所以具有里程碑意义，不只是因为它们“能偷数据”。</p><p>更重要的是，它们向整个计算机行业展示了一个新的安全边界：</p><blockquote><p><strong>即使软件权限检查完全正确，硬件为了提高性能而留下的微小痕迹，也可能成为跨越隔离边界的信息通道。</strong></p></blockquote><p>这类问题已经不再只是“某个程序员写错了一行代码”。</p><p>从攻防演化的角度看，这又是一次边界变化。</p><p>当软件层面的权限检查、内存保护和沙箱越来越完善时，攻击研究并没有停止，而是继续向更底层寻找可以观察和利用的差异；防守方也不得不把原本只被视为“性能实现细节”的缓存、推测执行和微架构状态纳入安全设计。</p><p>于是攻防双方争夺的边界，从“程序允许做什么”，进一步深入到了：</p><blockquote><p><strong>硬件在执行这些程序时，会不会无意间泄露什么。</strong></p></blockquote><p>由此，安全研究关注的范围也从应用程序和操作系统，进一步延伸到 CPU 微架构、硬件实现以及现实世界中的物理现象。</p><hr /></section></section>
<section><h1>防御也在不断进化<a href="#防御也在不断进化"><span>#</span></a></h1><p>攻击技术在进化，防御机制同样如此。</p><p>而且两者并不是彼此独立发展的。</p><p>很多今天习以为常的安全机制，本身就是过去一次次攻击留下的结果；反过来，每增加一道防线，也会改变攻击者下一步选择的路径。</p><p>早期程序几乎没有今天常见的内存保护措施。</p><p>后来逐渐出现：</p><ul>
<li>DEP / NX；</li>
<li>ASLR；</li>
<li>Stack Canary；</li>
<li>CFG；</li>
<li>浏览器沙箱；</li>
<li>权限隔离。</li>
</ul><p>这让现代漏洞利用越来越难依靠单个问题完成整个攻击。</p><p>很多攻击需要组合多个漏洞和技术：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>信息泄露</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>绕过 ASLR</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>获得代码执行</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>逃逸沙箱</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>提升权限</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这就是所谓的 <strong>Exploit Chain——漏洞利用链</strong>。</p><p>单个漏洞可能被防御机制限制，但多个漏洞组合起来，仍然可能形成完整攻击能力。</p><p>这也很好地体现了攻防双方长期演化的关系：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>缓冲区溢出普及</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Stack Canary / DEP 出现</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>攻击者寻找绕过方式</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>ASLR 提高地址预测难度</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>攻击者结合信息泄露绕过 ASLR</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>沙箱与权限隔离继续加强</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>13</div></div><div><span>攻击者组合多个漏洞形成 Exploit Chain</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>安全机制很少能“一劳永逸”地终结某类攻击。</p><p>更常见的结果是：</p><blockquote><p><strong>防御提高攻击成本，攻击者再寻找新的组合方式，而新的攻击又推动下一代防御出现。</strong></p></blockquote><hr /></section>
<section><h1>病毒、蠕虫、木马和勒索软件，到底有什么区别<a href="#病毒蠕虫木马和勒索软件到底有什么区别"><span>#</span></a></h1><p>随着恶意软件种类越来越多，几个常见概念也有必要区分。</p><section><h2>病毒 Virus<a href="#病毒-virus"><span>#</span></a></h2><p>病毒最大的特点是：</p><p><strong>依附其他文件或对象，并进行自我复制。</strong></p><p>CIH 就是非常典型的文件感染型病毒。</p></section><section><h2>蠕虫 Worm<a href="#蠕虫-worm"><span>#</span></a></h2><p>蠕虫强调：</p><p><strong>独立运行，并通过网络或其他渠道主动传播。</strong></p><p>典型代表包括 Morris Worm、Code Red、Slammer、Blaster、Conficker，以及 WannaCry 中的蠕虫传播能力。</p></section><section><h2>木马 Trojan<a href="#木马-trojan"><span>#</span></a></h2><p>木马通常不强调自我复制。</p><p>它更重视：</p><p><strong>伪装、远程控制、长期驻留和窃取数据。</strong></p></section><section><h2>勒索软件 Ransomware<a href="#勒索软件-ransomware"><span>#</span></a></h2><p>勒索软件的核心目标是：</p><p><strong>让受害者失去数据或系统访问能力，并索取赎金。</strong></p><p>现代勒索攻击还经常在加密之前先窃取数据。</p></section><section><h2>僵尸网络 Botnet<a href="#僵尸网络-botnet"><span>#</span></a></h2><p>僵尸网络并不是一种单独病毒，而是一种控制模式。</p><p>大量感染设备被统一管理，并用于 DDoS、挖矿、发送垃圾邮件或继续传播恶意软件。</p><hr /></section></section>
<section><h1>从 CIH 到熊猫烧香：为什么这两个案例值得放在一起看<a href="#从-cih-到熊猫烧香为什么这两个案例值得放在一起看"><span>#</span></a></h1><p>如果单独看 CIH 和熊猫烧香，它们都可以被大众简单地称作“电脑病毒”。</p><p>但放进整个恶意软件发展史里，它们代表的其实是两个完全不同的时代。</p><p>CIH 代表的是：</p><blockquote><p><strong>破坏型文件病毒时代。</strong></p></blockquote><p>攻击者更关注感染、隐藏、复制和破坏效果。</p><p>而熊猫烧香代表的则是：</p><blockquote><p><strong>恶意软件开始明显黑产化的时代。</strong></p></blockquote><p>攻击者逐渐发现，保持一台受害计算机继续运行，比直接毁掉它更有价值。</p><p>于是目标发生了变化：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CIH：</span></div></div><div><div><div>2</div></div><div><span>计算机 = 被破坏的目标</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>熊猫烧香时代：</span></div></div><div><div><div>5</div></div><div><span>计算机 = 可以持续利用的资产</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>再往后，恶意软件继续沿着这条路线演化：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>文件病毒</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>网络蠕虫</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>盗号木马</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>僵尸网络</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>勒索软件</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>企业级勒索</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>13</div></div><div><span>完整网络犯罪产业链</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>从这个角度看，CIH 和熊猫烧香之间相隔的几年，其实正好见证了恶意软件目的从“破坏”向“控制和获利”的转变。</p><hr /></section>
<section><h1>四十年的演化，可以浓缩成一条时间线<a href="#四十年的演化可以浓缩成一条时间线"><span>#</span></a></h1><p>如果把整个发展过程压缩成一条时间线，大致可以看到：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1980s</span></div></div><div><div><div>2</div></div><div><span>早期网络漏洞</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>4</div></div><div><span>1988</span></div></div><div><div><div>5</div></div><div><span>Morris Worm</span></div></div><div><div><div>6</div></div><div><span>漏洞利用开始支持自动传播</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>8</div></div><div><span>1990s</span></div></div><div><div><div>9</div></div><div><span>文件病毒、宏病毒、缓冲区溢出逐渐成熟</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>1998–1999</span></div></div><div><div><div>12</div></div><div><span>CIH</span></div></div><div><div><div>13</div></div><div><span>文件感染 + 强破坏型病毒</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>15</div></div><div><span>1999</span></div></div><div><div><div>16</div></div><div><span>Melissa</span></div></div><div><div><div>17</div></div><div><span>宏病毒 + 邮件自动传播</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>19</div></div><div><span>2000</span></div></div><div><div><div>20</div></div><div><span>ILOVEYOU</span></div></div><div><div><div>21</div></div><div><span>社会工程 + 邮件传播</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>23</div></div><div><span>2001–2004</span></div></div><div><div><div>24</div></div><div><span>Code Red / Nimda / Slammer / Blaster / Sasser</span></div></div><div><div><div>25</div></div><div><span>网络漏洞驱动的高速蠕虫时代</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>27</div></div><div><span>2005–2007</span></div></div><div><div><div>28</div></div><div><span>木马、盗号、僵尸网络与黑产兴起</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>30</div></div><div><span>2006–2007</span></div></div><div><div><div>31</div></div><div><span>熊猫烧香</span></div></div><div><div><div>32</div></div><div><span>文件感染 + 网络传播 + 黑产化</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>34</div></div><div><span>2008</span></div></div><div><div><div>35</div></div><div><span>Conficker</span></div></div><div><div><div>36</div></div><div><span>漏洞 + 弱口令 + 僵尸网络</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>38</div></div><div><span>2010</span></div></div><div><div><div>39</div></div><div><span>Stuxnet</span></div></div><div><div><div>40</div></div><div><span>0day 漏洞 + 工业控制攻击</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>42</div></div><div><span>2010s</span></div></div><div><div><div>43</div></div><div><span>APT、木马、勒索软件持续发展</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>45</div></div><div><span>2016</span></div></div><div><div><div>46</div></div><div><span>Mirai</span></div></div><div><div><div>47</div></div><div><span>IoT 僵尸网络</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>49</div></div><div><span>2017</span></div></div><div><div><div>50</div></div><div><span>WannaCry / NotPetya</span></div></div><div><div><div>51</div></div><div><span>漏洞利用 + 网络传播 + 勒索 / 破坏</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>53</div></div><div><span>2020s</span></div></div><div><div><div>54</div></div><div><span>供应链攻击、企业级勒索、云与身份攻击</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>56</div></div><div><span>2021</span></div></div><div><div><div>57</div></div><div><span>Log4Shell</span></div></div><div><div><div>58</div></div><div><span>基础组件漏洞 + 自动化后续投毒</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>60</div></div><div><span>今天</span></div></div><div><div><div>61</div></div><div><span>漏洞利用链 + 恶意软件模块化 + 完整攻击产业链</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这条时间线里最明显的变化，并不是“病毒越来越厉害”这么简单。</p><p>真正发生变化的是攻击者的目标。</p><p>最早：</p><blockquote><p>病毒想复制自己。</p></blockquote><p>后来：</p><blockquote><p>蠕虫想传播得更快。</p></blockquote><p>再后来：</p><blockquote><p>木马想长期控制机器。</p></blockquote><p>接着：</p><blockquote><p>僵尸网络想把机器变成资源。</p></blockquote><p>而今天：</p><blockquote><p>攻击者往往希望控制整个企业网络，并从数据、勒索、资源或情报中持续获利。</p></blockquote><hr /></section>
<section><h1>结语：漏洞是入口，恶意软件决定“进去之后做什么”<a href="#结语漏洞是入口恶意软件决定进去之后做什么"><span>#</span></a></h1><p>如果只研究漏洞，很容易把网络安全理解成“程序员写错了代码”。</p><p>如果只研究病毒，又容易把安全理解成“不要运行陌生文件”。</p><p>但互联网安全真正的发展过程，其实是两者不断结合。</p><p>漏洞决定：</p><blockquote><p>攻击者能不能进去。</p></blockquote><p>恶意软件决定：</p><blockquote><p>进去之后能做什么。</p></blockquote><p>Morris Worm 让人们第一次看到利用网络弱点实现自动传播的可能。</p><p>CIH 展示了传统文件病毒惊人的破坏能力。</p><p>Melissa 和 ILOVEYOU 证明邮件与社会工程学同样能够制造大规模传播。</p><p>Code Red、Slammer、Blaster 和 Sasser 把利用网络漏洞实现的自动化感染推向高峰。</p><p>熊猫烧香则见证了恶意软件从“破坏电脑”逐渐走向“控制电脑并获利”的转折。</p><p>Conficker 让感染设备成为长期控制的僵尸网络节点。</p><p>Stuxnet 将恶意代码的影响带入工业世界。</p><p>Mirai 让摄像头和路由器成为攻击资源。</p><p>WannaCry 把高速蠕虫与勒索软件结合起来。</p><p>而到了 Log4Shell 和现代供应链攻击时代，一个漏洞往往只是完整攻击链中的第一步。</p><p>因此，现代安全的目标早已不再只是：</p><blockquote><p>“不要中病毒。”</p></blockquote><p>而是：</p><blockquote><p>即使某个漏洞被利用、某台设备被感染，也要让攻击难以继续扩散，并尽早发现和阻断整条攻击链。</p></blockquote><p>如果把这四十多年的演化史，重新放在“攻防”视角下观察，就会发现几乎所有重要节点都具有相似的结构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>新的攻击方式出现</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>暴露原有防线的不足</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>防守方增加新的安全机制</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>攻击成本提高</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>攻击者寻找新的入口或绕过方法</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>下一轮攻防开始</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Morris Worm 推动了事件响应体系的发展，网络蠕虫推动补丁和自动更新普及，内存漏洞推动 DEP、ASLR 等保护机制成熟，APT 又推动防守方从“守住入口”转向持续监控和纵深防御，而 Spectre、Meltdown 甚至把这场博弈带到了 CPU 微架构层面。</p><p>所以，这段“演化史”说到最后，就是一场“攻防战”：</p><blockquote><p><strong>攻击者不断证明旧防线哪里还不够，而防守方则不断把这些教训变成下一层防线。</strong></p></blockquote><p>这或许才是过去四十多年互联网漏洞与恶意软件发展史留给今天最重要的一条经验。</p></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考 Microsoft、CERT/CC、OWASP、CISA、NIST、Apache 等机构发布的安全公告、漏洞说明与技术资料：</p><ol>
<li>
<p>Carnegie Mellon University Software Engineering Institute — Fostering Growth in Professional Cyber Incident Management<br />
介绍 1988 年 Morris Worm 事件以及 CERT/CC 成立的历史背景。<br />
<a href="https://www.sei.cmu.edu/history-of-innovation/fostering-growth-in-professional-cyber-incident-management/?utm_source=chatgpt.com" target="_blank">SEI：Morris Worm 与 CERT/CC 历史</a></p>
</li>
<li>
<p>OWASP Cheat Sheet Series — SQL Injection Prevention Cheat Sheet<br />
介绍 SQL 注入的形成原因、参数化查询以及数据库输入与 SQL 指令混淆的问题。<br />
<a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html?utm_source=chatgpt.com" target="_blank">OWASP：SQL Injection Prevention Cheat Sheet</a></p>
</li>
<li>
<p>OWASP Cheat Sheet Series — Cross Site Scripting Prevention Cheat Sheet<br />
介绍 XSS 的基本原理以及输出编码、HTML 上下文和浏览器脚本执行之间的关系。<br />
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html?utm_source=chatgpt.com" target="_blank">OWASP：Cross Site Scripting Prevention Cheat Sheet</a></p>
</li>
<li>
<p>OWASP Cheat Sheet Series — OS Command Injection Defense Cheat Sheet<br />
介绍命令注入中用户输入被错误解释为操作系统命令或参数的原理。<br />
<a href="https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html?utm_source=chatgpt.com" target="_blank">OWASP：OS Command Injection Defense Cheat Sheet</a></p>
</li>
<li>
<p>Microsoft Security Bulletin MS08-067 — Vulnerability in Server Service Could Allow Remote Code Execution<br />
Conficker 等恶意程序所利用的重要 Windows Server Service 远程代码执行漏洞。<br />
<a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-067?utm_source=chatgpt.com" target="_blank">Microsoft：MS08-067</a></p>
</li>
<li>
<p>Microsoft Security Intelligence — Win32/Conficker threat description<br />
介绍 Conficker 的网络传播、弱口令、可移动设备传播以及关闭安全服务等行为。<br />
<a href="https://www.microsoft.com/en-us/wdsi/threats/malware-encyclopedia-description?Name=Win32%2FConficker&amp;utm_source=chatgpt.com" target="_blank">Microsoft：Win32/Conficker</a></p>
</li>
<li>
<p>Heartbleed — The Heartbleed Bug<br />
介绍 OpenSSL Heartbeat 长度校验错误及其导致的越界内存读取问题。<br />
<a href="https://www.heartbleed.com/?utm_source=chatgpt.com" target="_blank">Heartbleed Bug 官方说明</a></p>
</li>
<li>
<p>Microsoft Security Bulletin MS17-010 — Security Update for Microsoft Windows SMB Server<br />
介绍 SMBv1 中可导致远程代码执行的信息处理漏洞，是理解 EternalBlue 与 WannaCry 传播机制的重要资料。<br />
<a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2017/ms17-010?utm_source=chatgpt.com" target="_blank">Microsoft：MS17-010</a></p>
</li>
<li>
<p>Microsoft Security Blog — WannaCrypt ransomware worm targets out-of-date systems<br />
Microsoft 对 WannaCry/WannaCrypt 的传播机制、SMB 漏洞利用以及蠕虫行为的技术分析。<br />
<a href="https://www.microsoft.com/en-us/security/blog/2017/05/12/wannacrypt-ransomware-worm-targets-out-of-date-systems/?utm_source=chatgpt.com" target="_blank">Microsoft Security Blog：WannaCrypt ransomware worm targets out-of-date systems</a></p>
</li>
<li>
<p>Microsoft Security Blog — New ransomware, old techniques: Petya adds worm capabilities<br />
介绍 2017 年 Petya/NotPetya 事件中的网络传播、漏洞利用以及破坏行为。<br />
<a href="https://www.microsoft.com/en-us/security/blog/2017/06/27/new-ransomware-old-techniques-Petya-adds-worm-capabilities/?utm_source=chatgpt.com" target="_blank">Microsoft Security Blog：Petya adds worm capabilities</a></p>
</li>
<li>
<p>CISA — Active Exploitation of SolarWinds Software<br />
介绍 SolarWinds Orion 供应链攻击事件以及 SUNBURST 后门相关情况。<br />
<a href="https://www.cisa.gov/news-events/alerts/2020/12/13/active-exploitation-solarwinds-software?utm_source=chatgpt.com" target="_blank">CISA：Active Exploitation of SolarWinds Software</a></p>
</li>
<li>
<p>Apache Log4j — Release Notes<br />
Apache 官方关于 CVE-2021-44228、Message Lookup、JNDI 功能以及后续安全修复的说明。<br />
<a href="https://logging.apache.org/log4j/2.x/release-notes.html?utm_source=chatgpt.com" target="_blank">Apache Log4j：Release Notes</a></p>
</li>
<li>
<p>CISA — Mitigating Log4Shell and Other Log4j-Related Vulnerabilities<br />
CISA、FBI、NSA 等机构联合发布的 Log4Shell 技术说明与缓解建议。<br />
<a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-356a?utm_source=chatgpt.com" target="_blank">CISA：Mitigating Log4Shell and Other Log4j-Related Vulnerabilities</a></p>
</li>
<li>
<p>NIST Computer Security Resource Center — Side-Channel Attack<br />
NIST 对侧信道攻击的定义，涵盖执行时间、功耗、电磁辐射和声学信息等侧信道。<br />
<a href="https://csrc.nist.gov/glossary/term/side_channel_attack?utm_source=chatgpt.com" target="_blank">NIST：Side-Channel Attack</a></p>
</li>
<li>
<p>NIST CSRC — Side Channels: Attacks, Defences, and Evaluation Schemes<br />
介绍时间侧信道、功耗分析、电磁侧信道及相关防御方法。<br />
<a href="https://csrc.nist.gov/presentations/2021/side-channels-attacks-defences-and-evaluation-sche?utm_source=chatgpt.com" target="_blank">NIST：Side Channels — Attacks and Defences</a></p>
</li>
<li>
<p>Intel — Intel Analysis of Speculative Execution Side Channels<br />
Intel 对 Spectre、Meltdown 以及推测执行侧信道问题的技术说明。<br />
<a href="https://www.intel.com/content/dam/develop/external/us/en/documents/180204-sgx-sdk-developer-guidance-v1-0.pdf?utm_source=chatgpt.com" target="_blank">Intel：Analysis of Speculative Execution Side Channels</a></p>
</li>
<li>
<p>Microsoft Learn — SMB security hardening in Windows Server and Windows Client<br />
介绍现代 Windows 中 SMB 协议的安全强化措施。<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security-hardening?utm_source=chatgpt.com" target="_blank">Microsoft Learn：SMB security hardening</a></p>
</li>
<li>
<p>Microsoft Learn — Control SMB signing behavior<br />
介绍 SMB Signing 的工作方式、配置方法以及其在防止中间人攻击中的作用。<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-signing?utm_source=chatgpt.com" target="_blank">Microsoft Learn：Control SMB signing behavior</a></p>
</li>
<li>
<p>Microsoft Learn — SMB Security Enhancements<br />
介绍较新 Windows Server 与 Windows 客户端中的 SMB 安全机制和协议增强。<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security?utm_source=chatgpt.com" target="_blank">Microsoft Learn：SMB Security Enhancements</a></p>
</li>
</ol><div><div><div></div><div>Note</div></div><div><p>本文以互联网安全技术的发展脉络为主，并非针对单一漏洞或恶意软件的完整技术分析。部分历史事件存在多个病毒变种、漏洞编号或不同厂商命名，因此正文对其工作机制进行了适当简化。若需要进一步研究某一具体漏洞，建议以对应厂商安全公告、CVE 记录及 CERT/CISA 等机构发布的技术资料为准。</p></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260919-tcp-445-history-and-security/</id>
      <title type="text">从文件共享到蠕虫风暴：TCP 445 端口的前世与今生</title>
      <published>2026-09-19T00:00:00.000Z</published>
      <updated>2026-09-19T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260919-tcp-445-history-and-security/"/>
      <summary type="text">445 端口？永恒之蓝？从 NetBIOS 与 SMB 的早期时代，到 Conficker、EternalBlue、WannaCry，再到现代 SMB 签名、加密与 NTLM 防护：回顾 TCP 445 端口二十余年的变迁。</summary>
      <content type="html"><![CDATA[<p>如果你曾经查看过服务器的防火墙日志、安全组记录，或者接触过 Windows 文件共享，那么大概率见过一个非常醒目的端口：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>TCP 445</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在很多网络安全文章里，445 几乎总是伴随着“高危”“永恒之蓝”“勒索病毒”“不要开放公网”等关键词出现，令人谈虎色变。久而久之，很多人逐渐产生了一种刻板印象：<strong>445 端口本身就是一个漏洞。</strong></p>
<p>但事实并非如此。</p>
<p><strong>445 端口本身并不是漏洞。</strong> 真正需要关注的是运行在它之上的 SMB 服务，以及 SMB、Windows Server Service、RPC、NTLM 等组件中曾经出现的漏洞与配置风险。</p>
<p>从早期 Windows 局域网中的文件共享，到 2008 年的 Conficker，再到 2017 年震动全球的 WannaCry 与 NotPetya，445 几乎见证了 Windows 网络安全从“局域网互信”走向“默认不信任”的整个过程。</p>
<div><div><div></div><div>Note</div></div><div><p>本文讨论的是漏洞历史、安全机制与防御思路，不提供漏洞利用代码、攻击载荷或实际入侵步骤。</p></div></div>
<hr />
<section><h1>从 NetBIOS 到 TCP 445：SMB 是如何走到这里的<a href="#从-netbios-到-tcp-445smb-是如何走到这里的"><span>#</span></a></h1><p>TCP 445 最常见的用途，是承载 <strong>SMB 协议</strong>。SMB 是 Windows 网络中非常核心的一种协议，主要负责文件共享、文件服务器访问、网络打印机共享、远程共享目录访问，以及一部分远程管理和域环境通信。</p><p>例如，在 Windows 资源管理器里输入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>\\192.168.1.10\share</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>背后通常就是 SMB 在工作。</p><p>不过，SMB 并不是从一开始就直接运行在 445 端口上的。早期 Windows 网络大量依赖 <strong>NetBIOS over TCP/IP（NetBT）</strong>，SMB 通常通过 NetBIOS 会话服务传输，因此会看到一组经典端口：</p>

<table><thead><tr><th>端口</th><th>协议</th><th>常见用途</th></tr></thead><tbody><tr><td>UDP 137</td><td>NetBIOS Name Service</td><td>名称解析</td></tr><tr><td>UDP 138</td><td>NetBIOS Datagram Service</td><td>数据报服务</td></tr><tr><td>TCP 139</td><td>NetBIOS Session Service</td><td>会话与 SMB</td></tr><tr><td>TCP 445</td><td>Direct-hosted SMB</td><td>SMB 直接运行于 TCP</td></tr></tbody></table><p>随着 Windows 2000 时代的到来，微软开始广泛使用 <strong>Direct-hosted SMB</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>SMB</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>TCP</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>445</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这意味着 SMB 不再必须依赖 TCP 139，而可以直接运行在 TCP 445 上。从网络结构上看，这是一次简化；但从安全角度看，445 也由此逐渐成为 Windows 网络服务中最重要、最敏感的入口之一。</p><p>很多人第一次接触 SMB 时，会把它理解成单纯的“传文件协议”。实际上，Windows 对 SMB 的使用远不止文件共享。SMB 还可以承载 <strong>命名管道（Named Pipe）</strong>，而部分 Windows RPC 服务又可以通过命名管道工作。</p><p>可以粗略理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>TCP 445</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>▼</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>SMB</span></div></div><div><div><div>5</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>├── 文件共享</span></div></div><div><div><div>7</div></div><div><span><span>   </span></span><span>├── 打印共享</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>├── IPC$</span></div></div><div><div><div>9</div></div><div><span><span>   </span></span><span>├── Named Pipe</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>│      │</span></div></div><div><div><div>11</div></div><div><span><span>   </span></span><span>│      └── RPC</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>13</div></div><div><span><span>   </span></span><span>└── 身份认证</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也是为什么历史上一些看起来属于“RPC”或“Server Service”的漏洞，最终仍然经常与 139/445 联系在一起。</p><p>更重要的是，SMB 诞生和成长的年代与今天的网络安全环境完全不同。早期企业网络通常更强调局域网内的资源互通，内部网络往往被默认视为相对可信；而今天的安全理念则越来越接近“任何网络连接都需要验证”。这种设计时代的差异，后来成为许多安全问题的重要背景。</p><hr /></section>
<section><h1>2008：MS08-067 与 Conficker，445 开始成为“蠕虫入口”的代名词<a href="#2008ms08-067-与-conficker445-开始成为蠕虫入口的代名词"><span>#</span></a></h1><p>在 445 的安全历史中，2008 年是一个无法绕开的时间点。</p><p>2008 年 10 月 23 日，微软发布安全公告 <strong>MS08-067</strong>，修复了 Windows Server Service 中的一个远程代码执行漏洞：</p><p><strong>CVE-2008-4250</strong></p><p>漏洞位于 Windows Server Service 对特制 RPC 请求的处理中。在部分 Windows 2000、Windows XP 与 Windows Server 2003 系统上，未经身份认证的攻击者可能通过网络触发内存破坏并获得远程代码执行能力。</p><p>攻击链可以简化为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>无需身份认证</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>发送特制 RPC 请求</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>触发内存破坏</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>远程执行代码</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>微软当时已经明确指出，这个漏洞可能被制作成蠕虫化攻击。很快，这个判断就成为现实。</p><p>2008 年末，著名的 <strong>Conficker</strong> 蠕虫开始大规模传播。它利用的核心漏洞之一，正是 MS08-067。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>感染主机 A</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>├── 扫描其他 Windows 主机</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>├── 发现可达服务</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>├── 尝试利用 MS08-067</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>│</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>▼</span></div></div><div><div><div>8</div></div><div><span>感染主机 B</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>│</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>└── 再次扫描</span></div></div><div><div><div>11</div></div><div><span><span>           </span></span><span>↓</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>感染 C、D、E……</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Conficker 与传统依赖邮件附件或用户点击的恶意软件有一个非常明显的区别：<strong>它不需要每一台机器上的用户都主动做错一件事。</strong> 只要目标系统存在漏洞，同时网络路径可达，就有机会被自动传播。</p><p>这正是网络蠕虫最危险的地方。</p><p>Conficker 并不是第一个网络蠕虫，但它再次证明了一个后来反复出现的事实：局域网里的基础服务一旦出现可远程利用、又适合自动传播的漏洞，一台失陷主机就可能迅速演变成整个网段的问题。从这个阶段开始，139/445 在企业防火墙和边界策略中的地位也越来越敏感。</p><p>与此同时，SMB 本身也在继续演进：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>SMB 1.0</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>SMB 2.0</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>SMB 2.1</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>SMB 3.0</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>SMB 3.02</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>SMB 3.1.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中，<strong>SMBv1</strong> 是历史最悠久、兼容包袱也最沉重的版本。它的问题并不在于“只要使用 SMBv1 就一定会被攻击”，而是它设计年代久远、协议复杂、长期维护成本高，而且缺乏后来 SMB 版本拥有的许多现代安全能力。</p><p>在相当长的一段时间里，Windows 为了兼容旧设备和旧网络环境仍然保留 SMBv1。到了 2017 年，这种兼容策略终于遭遇了代价极高的一次集中爆发。</p><hr /></section>
<section><h1>2017：MS17-010、EternalBlue 与 Shadow Brokers<a href="#2017ms17-010eternalblue-与-shadow-brokers"><span>#</span></a></h1><p>2017 年 3 月 14 日，微软发布了后来极其著名的安全公告：</p><p><strong>MS17-010</strong></p><p>它修复的是一组 Windows SMB Server 漏洞，其中最严重的问题可使攻击者通过向 SMBv1 服务发送特制消息来实现远程代码执行。</p><p>这是理解后续事件最关键的时间节点之一。因为后来席卷全球的灾难，并不是发生在“微软还不知道漏洞存在”的时候，而是在补丁已经发布之后。</p><div><div><div></div><div>Tip</div></div><div><p><strong>补丁“已经发布”不等于风险“已经消失”。</strong> 只有补丁真正部署到受影响的系统上，漏洞带来的风险才会实质下降。2017 年后续发生的 WannaCry，正是这一点最典型的案例之一。</p></div></div><p>说到这里，就必须解释另一个常被混淆的名词：<strong>EternalBlue（永恒之蓝）</strong>。</p><p>网络上经常可以看到“445 端口存在永恒之蓝漏洞”这样的说法，虽然方便理解，但技术上并不严谨。更准确的关系应该是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>TCP 445</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>SMB 服务</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>SMBv1 实现漏洞</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>MS17-010 修复</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>EternalBlue 等利用技术</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>这里最容易混淆四个概念：<strong>445 是端口，SMB 是协议，MS17-010 是微软针对一组 SMB 漏洞发布的安全更新，而 EternalBlue 是针对其中漏洞的利用技术之一。</strong> 因此，“445 端口就是永恒之蓝漏洞”并不是一个严谨的说法。</p></div></div><p>EternalBlue 通常与 <strong>CVE-2017-0144</strong> 联系在一起。它危险的地方在于，攻击者并不是简单地猜用户名和密码，而是在尝试攻击 SMB 协议实现本身。因此，即使账户密码非常复杂，也无法替代系统补丁。</p><p>2017 年 4 月，事情进一步恶化。名为 <strong>Shadow Brokers</strong> 的组织公开了一批攻击工具，其中包含：</p><ul>
<li>EternalBlue；</li>
<li>EternalRomance；</li>
<li>EternalChampion；</li>
<li>EternalSynergy。</li>
</ul><p>这些工具与 Windows SMB 漏洞密切相关。</p><p>整个局势于是变成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>2017-03-14</span></div></div><div><div><div>2</div></div><div><span>Microsoft 发布 MS17-010</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>4</div></div><div><span>2017-04</span></div></div><div><div><div>5</div></div><div><span>Shadow Brokers 公开相关攻击工具</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>攻击能力迅速扩散</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>原本掌握在少数人手中的高级攻击能力，一旦被公开，就意味着全世界都可以开始研究、修改和集成它。而此时，仍有大量 Windows 机器没有完成更新。</p><p>一场灾难正在酝酿。</p><hr /></section>
<section><h1>WannaCry 与 NotPetya：445 从此“恶名昭彰”<a href="#wannacry-与-notpetya445-从此恶名昭彰"><span>#</span></a></h1><p>2017 年 5 月 12 日，<strong>WannaCry / WannaCrypt</strong> 大规模爆发。</p><p>它真正令人恐惧的地方，并不只是“勒索”，而是把 <strong>勒索软件</strong> 与 <strong>网络蠕虫</strong> 结合到了一起。</p><p>传统勒索软件往往更依赖用户交互，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>邮件附件</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>用户点击</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>恶意程序运行</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>文件被加密</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而 WannaCry 获得 SMB 蠕虫传播能力之后，传播过程变成了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>一台机器感染</span></div></div><div><div><div>2</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>扫描其他可达主机</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>寻找存在漏洞的 SMBv1</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>利用漏洞传播</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>新的机器感染</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>11</div></div><div><span>继续扫描</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这意味着病毒不需要等待每一个用户主动犯错。一旦进入一个防护薄弱、机器补丁状态又不一致的局域网，它就可能自行扩散。</p><p>更值得注意的是时间差：</p>

<table><thead><tr><th>事件</th><th>日期</th></tr></thead><tbody><tr><td>MS17-010 发布</td><td>2017-03-14</td></tr><tr><td>WannaCry 大规模爆发</td><td>2017-05-12</td></tr></tbody></table><p>两者之间已经过去了接近两个月。</p><div><div><div></div><div>Note</div></div><div><p>WannaCry 留下的一个经典教训是：<strong>补丁存在，并不代表补丁已经真正进入生产环境。</strong> 漏洞修复、补丁分发、终端实际完成更新，是三个不同的环节。</p></div></div><p>然而，2017 年关于 SMB 的故事还没有结束。</p><p>仅仅一个多月后，<strong>NotPetya</strong> 再次爆发。它表面上看起来像勒索软件，但后来更多被视为一种高度破坏性的恶意软件。与 WannaCry 相比，NotPetya 的传播方式更加复杂，它并不只依赖单一 SMB 漏洞，而是组合使用了：</p><ul>
<li>EternalBlue；</li>
<li>EternalRomance；</li>
<li>SMB；</li>
<li>被窃取的凭据；</li>
<li>PsExec；</li>
<li>WMIC；</li>
<li>Windows 管理机制。</li>
</ul><p>它的传播逻辑更接近：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>目标存在 SMB 漏洞？</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>┌───┴───┐</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>YES      NO</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>│        │</span></div></div><div><div><div>6</div></div><div><span>漏洞传播    是否掌握有效凭据？</span></div></div><div><div><div>7</div></div><div><span><span>             </span></span><span>│</span></div></div><div><div><div>8</div></div><div><span><span>          </span></span><span>┌──┴──┐</span></div></div><div><div><div>9</div></div><div><span><span>         </span></span><span>YES    NO</span></div></div><div><div><div>10</div></div><div><span><span>          </span></span><span>│</span></div></div><div><div><div>11</div></div><div><span><span>       </span></span><span>使用合法</span></div></div><div><div><div>12</div></div><div><span><span>       </span></span><span>管理机制</span></div></div><div><div><div>13</div></div><div><span><span>       </span></span><span>横向移动</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>NotPetya 把问题又向前推进了一步：<strong>系统已经打补丁，并不等于整个网络已经安全。</strong> 一旦管理员凭据被窃取，攻击者仍可能借助合法的远程管理机制继续横向移动。</p></div></div><p>如果攻击者已经获得管理员凭据，那么即使某台机器不存在对应漏洞，也可能通过正常的远程管理能力继续横向移动。于是安全问题不再只是“有没有漏洞”，而开始扩展到凭据、权限、网络隔离、最小权限和服务暴露范围。</p><p>从这一点开始，445 的安全史也从“单纯修漏洞”逐渐进入现代安全体系的阶段。</p><hr /></section>
<section><h1>从淘汰 SMBv1 到 SMBGhost：协议升级并不等于绝对安全<a href="#从淘汰-smbv1-到-smbghost协议升级并不等于绝对安全"><span>#</span></a></h1><p>经过 2017 年的一系列事件后，SMBv1 的风险已经无法继续被忽视。微软开始更积极地推动 SMBv1 退出历史舞台，现代 Windows 系统逐渐默认不安装 SMBv1，并推荐使用 SMB2 / SMB3 以及更严格的身份认证、签名和加密机制。</p><p>但这并不意味着“只要使用 SMB2 或 SMB3 就绝对安全”。</p><p>2020 年，Windows SMBv3 又出现了一个广受关注的漏洞：</p><p><strong>CVE-2020-0796，也就是 SMBGhost。</strong></p><p>该漏洞与 SMB 3.1.1 的压缩功能有关。它的出现再次提醒人们：<strong>淘汰旧协议只是降低风险，并不能让整个协议族从此不存在漏洞。</strong></p><p>真正可靠的安全思路始终应该是多层防护：</p><ul>
<li>使用现代协议；</li>
<li>及时安装系统更新；</li>
<li>限制服务暴露范围；</li>
<li>做好访问控制；</li>
<li>对内网进行合理隔离。</li>
</ul><p>因此，445 的安全演进并不是“SMBv1 很危险，换成 SMB3 就结束了”，而是从旧协议问题逐渐转向更加系统化的身份认证、完整性保护与横向移动防护。</p><hr /></section>
<section><h1>现代 445：风险重点从远程代码执行转向认证与横向移动<a href="#现代-445风险重点从远程代码执行转向认证与横向移动"><span>#</span></a></h1><p>今天再讨论 445，还是只能想到 EternalBlue 吗？——它已经是将近十年前的东西了。</p><p>在一个及时更新的现代 Windows 网络中，更现实的风险通常包括 NTLM Relay、凭据窃取、弱密码、错误的共享权限、SMB Signing 未启用、匿名或 Guest 访问、横向移动，以及第三方 NAS 或 Samba 的实现漏洞。</p><p>攻击者关心的问题，也逐渐从“有没有一个能直接打穿系统的 SMB 漏洞”，变成了“能不能利用 SMB 扩大已经获得的权限”。</p><p>其中很典型的一类问题，就是 <strong>NTLM Relay</strong>。</p><p>正常情况下，客户端可能向服务器发起 NTLM 身份认证：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>客户端 A</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>│ NTLM Authentication</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>▼</span></div></div><div><div><div>5</div></div><div><span>服务器 B</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而在某些不安全的配置下，攻击者可能尝试把一次认证过程转发给另一个服务：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>客户端 A</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>▼</span></div></div><div><div><div>4</div></div><div><span>攻击者</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>│</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>│ Relay</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>▼</span></div></div><div><div><div>8</div></div><div><span>服务器 B</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的关键在于，攻击者并不一定需要知道用户的明文密码，而是试图利用一段正在发生的认证过程。</p><p>因此，NTLM Relay 和 EternalBlue 本质上是两类完全不同的问题：前者主要与身份认证和配置有关，后者属于软件漏洞与远程代码执行。</p><p>为了降低这类风险，现代 Windows 不断强化 <strong>SMB Signing、Kerberos、NTLM 限制以及更严格的默认策略</strong>。</p><section><h2>SMB Signing 与 SMB Encryption<a href="#smb-signing-与-smb-encryption"><span>#</span></a></h2><p><strong>SMB Signing（SMB 签名）</strong> 可以理解为给 SMB 消息附加一个基于会话密钥计算的签名，用于验证数据在传输过程中是否被篡改。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>原始消息</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>攻击者修改</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>签名验证失败</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>拒绝</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它能够显著降低消息篡改、中间人攻击以及一部分 SMB Relay 风险。</p><p>而 <strong>SMB Encryption（SMB 加密）</strong> 解决的是另一个问题：数据内容是否会被旁观者直接读取。简单来说，签名更强调完整性与真实性，加密则进一步保护内容机密性。</p><p>现代 Windows 还逐步加入或强化了：</p><ul>
<li>更严格的 SMB Signing 策略；</li>
<li>SMB Encryption；</li>
<li>NTLM Blocking；</li>
<li>SMB Authentication Rate Limiter；</li>
<li>更现代的签名算法；</li>
<li>更严格的防火墙和访问策略。</li>
</ul><p>445 端口依旧存在，但今天运行在它上面的 SMB，与二十年前已然不可同日而语。</p><hr /></section></section>
<section><h1>为什么今天仍然不建议把 445 直接暴露到公网？<a href="#为什么今天仍然不建议把-445-直接暴露到公网"><span>#</span></a></h1><p>看到这里，可能有人想问：既然现代 SMB 已经拥有 SMB3、签名、加密和更严格的认证机制，为什么仍然普遍不建议把 445 直接暴露到公网？</p><p>原因其实很简单：<strong>没有必要承担的攻击面，就不应该承担。</strong></p><p>如果结构是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Internet</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>公网 IP:445</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那么意味着整个互联网都可以尝试与你的 SMB 服务建立连接。</p><p>即使系统已经完全更新，仍然要面对自动化端口扫描、SMB 指纹识别、用户名与服务枚举、密码爆破、NTLM 相关攻击、配置错误、第三方实现漏洞，以及可能尚未公开的新漏洞。</p><p>对于绝大多数个人服务器和企业服务器而言，并没有充分的理由或者必要性让整个互联网直接访问 SMB。更合理的结构通常是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Internet</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>VPN / WireGuard / Tailscale</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>可信内部网络</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>TCP 445 / SMB</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Tip</div></div><div><p><strong>不要把“系统目前没有已知漏洞”等同于“这个服务适合直接暴露公网”。</strong> 对于没有公网访问必要的 SMB 服务，减少暴露面本身就是最直接、最有效的防护之一。</p></div></div><hr /></section>
<section><h1>今天看到“有人扫描我的 445”，意味着什么？<a href="#今天看到有人扫描我的-445意味着什么"><span>#</span></a></h1><p>如果你在服务器日志、安全组日志或防火墙里看到陌生 IP 不断访问 445，并不能直接得出“有人正在用永恒之蓝攻击我”这样的结论。</p><p>更准确地说，这通常只说明：<strong>有主机正在探测你的 445 端口，确认这里是否存在 SMB 服务，以及是否值得继续识别。</strong></p><p>互联网中存在大量自动化扫描系统，它们可能同时扫描很多常见端口，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>22</span></div></div><div><div><div>2</div></div><div><span>23</span></div></div><div><div><div>3</div></div><div><span>80</span></div></div><div><div><div>4</div></div><div><span>443</span></div></div><div><div><div>5</div></div><div><span>445</span></div></div><div><div><div>6</div></div><div><span>3389</span></div></div><div><div><div>7</div></div><div><span>6379</span></div></div><div><div><div>8</div></div><div><span>9200</span></div></div><div><div><div>9</div></div><div><span>27017</span></div></div><div><div><div>10</div></div><div><span>……</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>445 只是其中极受关注的一个。</p><p>扫描者可能寻找的是老旧 Windows、SMBv1、Samba、NAS、弱口令、匿名共享、错误配置，也可能是在寻找新出现的漏洞。</p><p>因此需要区分三件事：</p><ul>
<li><strong>445 被扫描</strong>，不等于有人还在尝试使用 EternalBlue 等等攻击你；</li>
<li><strong>445 被扫描</strong>，不等于攻击已经成功；</li>
<li><strong>445 被扫描</strong>，甚至不等于你的服务器真的开放了 445。</li>
</ul><p>如果防火墙或云安全组已经正确阻断，那么扫描往往就只会停留在扫描本身。</p><hr /></section>
<section><h1>445 的历史真正留下了什么？<a href="#445-的历史真正留下了什么"><span>#</span></a></h1><p>如果最后只把这段历史总结成“445 很危险，所以关掉它”，其实反而会错过其中最有价值的部分。</p><p>445 与 SMB 的二十多年安全史，真正留下的是几条非常现代的安全原则。</p><p>首先，<strong>补丁必须真正“部署”，而不是仅仅“发布”</strong>。WannaCry 最经典的地方，就在于 MS17-010 早已发布，但仍有大量系统没有及时更新。</p><p>其次，<strong>内网并不天然安全</strong>。Conficker、WannaCry、NotPetya 都展示过同一种模式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>攻击进入一台机器</span></div></div><div><div><div>2</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>局域网内部横向传播</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>整个网络受到影响</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p><strong>“内网”与“可信网”从来都不是同义词。</strong> Conficker、WannaCry 与 NotPetya 都说明，一旦攻击者进入内部网络，横向传播往往比最初的入口本身更值得警惕。</p></div></div><p>第三，<strong>密码安全与漏洞修复不能相互替代</strong>。复杂密码挡不住无需认证的远程代码执行漏洞，而系统补丁也解决不了凭据泄露、弱密码和错误权限配置。</p><p>第四，<strong>减少服务暴露本身就是一种安全措施</strong>。如果服务器根本不需要 SMB，那么关闭服务或限制访问范围，通常比长期把它暴露在公网再依赖各种规则补救更加可靠。</p><p>最后，也是最重要的一点：Windows 网络安全设计正在从“为了兼容，尽可能允许连接”，逐渐转向“<strong>为了安全，连接必须证明自己可信</strong>”。</p><p>从 SMBv1 到 SMB3，从可选签名到更严格的签名要求，从 NTLM 到 Kerberos，从裸露服务到 VPN 与零信任架构，这种变化贯穿了整个 445 的历史。</p><hr /></section>
<section><h1>一张时间线看完 445 的前世今生<a href="#一张时间线看完-445-的前世今生"><span>#</span></a></h1><p>最后，可以把这二十多年的变化压缩成一条时间线：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>早期 Windows</span></div></div><div><div><div>2</div></div><div><span>│</span></div></div><div><div><div>3</div></div><div><span>├── SMB over NetBIOS</span></div></div><div><div><div>4</div></div><div><span>│      └── TCP 139</span></div></div><div><div><div>5</div></div><div><span>│</span></div></div><div><div><div>6</div></div><div><span>├── Direct-hosted SMB</span></div></div><div><div><div>7</div></div><div><span>│      └── TCP 445</span></div></div><div><div><div>8</div></div><div><span>│</span></div></div><div><div><div>9</div></div><div><span>▼</span></div></div><div><div><div>10</div></div><div><span>2008</span></div></div><div><div><div>11</div></div><div><span>MS08-067</span></div></div><div><div><div>12</div></div><div><span>│</span></div></div><div><div><div>13</div></div><div><span>├── 远程代码执行</span></div></div><div><div><div>14</div></div><div><span>│     │</span></div></div><div><div><div>15</div></div><div><span>│     ▼</span></div></div><div><div><div>16</div></div><div><span>│  Conficker</span></div></div><div><div><div>17</div></div><div><span>│     │</span></div></div><div><div><div>18</div></div><div><span>│     └── 大规模网络蠕虫</span></div></div><div><div><div>19</div></div><div><span>│</span></div></div><div><div><div>20</div></div><div><span>▼</span></div></div><div><div><div>21</div></div><div><span>SMB2 / SMB3 持续发展</span></div></div><div><div><div>22</div></div><div><span>│</span></div></div><div><div><div>23</div></div><div><span>▼</span></div></div><div><div><div>24</div></div><div><span>2017-03-14</span></div></div><div><div><div>25</div></div><div><span>MS17-010 发布</span></div></div><div><div><div>26</div></div><div><span>│</span></div></div><div><div><div>27</div></div><div><span>▼</span></div></div><div><div><div>28</div></div><div><span>2017-04</span></div></div><div><div><div>29</div></div><div><span>Shadow Brokers 泄露攻击工具</span></div></div><div><div><div>30</div></div><div><span>│</span></div></div><div><div><div>31</div></div><div><span>├── EternalBlue</span></div></div><div><div><div>32</div></div><div><span>├── EternalRomance</span></div></div><div><div><div>33</div></div><div><span>├── ...</span></div></div><div><div><div>34</div></div><div><span>│</span></div></div><div><div><div>35</div></div><div><span>▼</span></div></div><div><div><div>36</div></div><div><span>2017-05</span></div></div><div><div><div>37</div></div><div><span>WannaCry</span></div></div><div><div><div>38</div></div><div><span>│</span></div></div><div><div><div>39</div></div><div><span>├── 勒索软件 + SMB 蠕虫</span></div></div><div><div><div>40</div></div><div><span>│</span></div></div><div><div><div>41</div></div><div><span>▼</span></div></div><div><div><div>42</div></div><div><span>2017-06</span></div></div><div><div><div>43</div></div><div><span>NotPetya</span></div></div><div><div><div>44</div></div><div><span>│</span></div></div><div><div><div>45</div></div><div><span>├── SMB 漏洞</span></div></div><div><div><div>46</div></div><div><span>├── 凭据</span></div></div><div><div><div>47</div></div><div><span>├── Windows 管理工具</span></div></div><div><div><div>48</div></div><div><span>│</span></div></div><div><div><div>49</div></div><div><span>▼</span></div></div><div><div><div>50</div></div><div><span>SMBv1 加速退出历史舞台</span></div></div><div><div><div>51</div></div><div><span>│</span></div></div><div><div><div>52</div></div><div><span>▼</span></div></div><div><div><div>53</div></div><div><span>2020</span></div></div><div><div><div>54</div></div><div><span>SMBGhost</span></div></div><div><div><div>55</div></div><div><span>│</span></div></div><div><div><div>56</div></div><div><span>├── SMB 3.1.1 漏洞</span></div></div><div><div><div>57</div></div><div><span>│</span></div></div><div><div><div>58</div></div><div><span>▼</span></div></div><div><div><div>59</div></div><div><span>现代 Windows</span></div></div><div><div><div>60</div></div><div><span>│</span></div></div><div><div><div>61</div></div><div><span>├── SMB 3.x</span></div></div><div><div><div>62</div></div><div><span>├── SMB Signing</span></div></div><div><div><div>63</div></div><div><span>├── SMB Encryption</span></div></div><div><div><div>64</div></div><div><span>├── Kerberos</span></div></div><div><div><div>65</div></div><div><span>├── NTLM Blocking</span></div></div><div><div><div>66</div></div><div><span>├── Authentication Rate Limiter</span></div></div><div><div><div>67</div></div><div><span>└── 更严格的默认安全策略</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>从某种意义上说，445 从来没有“变成一个危险端口”。真正发生变化的，是它所处的网络环境，以及我们对网络信任的理解。</p><p>二十年前，SMB 更多被看作局域网里的基础工具；到了 2008 年，它开始频繁出现在蠕虫传播的讨论中；2017 年，EternalBlue、WannaCry 和 NotPetya 又让 445 成为了网络安全史上最具辨识度的端口之一；而到了今天，SMB 本身已经拥有远比过去完善的安全机制，但安全策略反而变得更加谨慎。</p><p><strong>能不暴露，就不要暴露；能隔离，就不要直接互通；能使用现代认证，就不要继续依赖历史兼容机制。</strong></p><p>也许这才是 445 端口二十多年历史里最值得记住的东西。</p><hr /></section>
<section><h1>参考资料<a href="#参考资料"><span>#</span></a></h1><p>本文主要参考 Microsoft 官方安全公告与技术文档：</p><ol>
<li>
<p>Microsoft Security Bulletin MS08-067 — Vulnerability in Server Service Could Allow Remote Code Execution<br />
<a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-067" target="_blank">https://learn.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-067</a></p>
</li>
<li>
<p>Microsoft Security Bulletin MS17-010 — Security Update for Microsoft Windows SMB Server<br />
<a href="https://learn.microsoft.com/en-us/security-updates/securitybulletins/2017/ms17-010" target="_blank">https://learn.microsoft.com/en-us/security-updates/securitybulletins/2017/ms17-010</a></p>
</li>
<li>
<p>Microsoft Security Blog — WannaCrypt ransomware worm targets out-of-date systems<br />
<a href="https://www.microsoft.com/en-us/security/blog/2017/05/12/wannacrypt-ransomware-worm-targets-out-of-date-systems/" target="_blank">https://www.microsoft.com/en-us/security/blog/2017/05/12/wannacrypt-ransomware-worm-targets-out-of-date-systems/</a></p>
</li>
<li>
<p>Microsoft Security Blog — New ransomware, old techniques: Petya adds worm capabilities<br />
<a href="https://www.microsoft.com/en-us/security/blog/2017/06/27/new-ransomware-old-techniques-Petya-adds-worm-capabilities/" target="_blank">https://www.microsoft.com/en-us/security/blog/2017/06/27/new-ransomware-old-techniques-Petya-adds-worm-capabilities/</a></p>
</li>
<li>
<p>Microsoft Learn — SMB security hardening in Windows Server and Windows Client<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security-hardening" target="_blank">https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security-hardening</a></p>
</li>
<li>
<p>Microsoft Learn — Control SMB signing behavior<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-signing" target="_blank">https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-signing</a></p>
</li>
<li>
<p>Microsoft Learn — SMB Security Enhancements<br />
<a href="https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security" target="_blank">https://learn.microsoft.com/en-us/windows-server/storage/file-server/smb-security</a></p>
</li>
</ol><hr /><div><div><div></div><div>Tip</div></div><div><p>如果你是在公网服务器的安全日志里第一次注意到 445，最重要的并不是去判断每一个扫描 IP 究竟在运行什么工具，而是先确认：<strong>你的服务器是否真的有必要向公网提供 SMB，以及防火墙或安全组是否已经阻止了不必要的访问。</strong></p></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/tech/260918-presence-component-guide/</id>
      <title type="text">客栈的“营业状态”—— Presence 小组件</title>
      <published>2026-09-18T00:00:00.000Z</published>
      <updated>2026-09-18T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/tech/260918-presence-component-guide/"/>
      <summary type="text">让静态网站“活起来”！记录我如何为静态 Astro 博客设计一套实时 Presence 系统，以及具体的部署教程！</summary>
      <content type="html"><![CDATA[<p>我给客栈做了一个小小的“营业状态”面板。打开网站，你可以看到我此刻是在写代码、逛网页、聊天，还是暂时离开了电脑。</p>
<p>它显示的内容很简单：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>What am I doing?</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>正在写代码</span></div></div><div><div><div>4</div></div><div><span>Visual Studio Code</span></div></div><div><div><div>5</div></div><div><span>工作中</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>● Online · 12 分钟</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>如果你也想把它放到自己的网站，这篇文章会带你从源码开始，把前端、服务端和 Windows 客户端接起来。前面用一节说明它的工作原理，后面直接进入操作步骤。</p>
<section><h1>工作原理：三个部分，两个接口<a href="#工作原理三个部分两个接口"><span>#</span></a></h1><p>Presence 分成三层，各自只负责一件事：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>你的 Windows 电脑</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>Agent 检测前台程序和空闲时间</span></div></div><div><div><div>3</div></div><div><span><span>          </span></span><span>│</span></div></div><div><div><div>4</div></div><div><span><span>          </span></span><span>│ HTTPS POST，携带 Bearer Token</span></div></div><div><div><div>5</div></div><div><span><span>          </span></span><span>▼</span></div></div><div><div><div>6</div></div><div><span>Presence 服务端</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>接收状态，保存到 presence.json</span></div></div><div><div><div>8</div></div><div><span><span>          </span></span><span>▲</span></div></div><div><div><div>9</div></div><div><span><span>          </span></span><span>│ HTTPS GET，公开读取</span></div></div><div><div><div>10</div></div><div><span><span>          </span></span><span>│</span></div></div><div><div><div>11</div></div><div><span>访客的浏览器</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>前端组件轮询 API，显示状态</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里检测的是<strong>站长自己的电脑</strong>。访客只是在浏览器里读取结果，不需要安装客户端。</p><p>Agent 默认每 5 秒检查一次前台程序，按规则把 <code>Code.exe</code>、<code>msedge.exe</code>、<code>QQ.exe</code> 等进程名映射成 <code>coding</code>、<code>surfing</code>、<code>chating</code>。如果 10 分钟没有键盘或鼠标输入，就优先显示 <code>idle</code>。它不会采集窗口标题、文件名、网址或聊天内容；“听音乐”等状态也只是根据前台播放器进程推测，并不检测实际播放情况。</p><p>状态变化时，Agent 立即上报；状态不变时，每 30 秒发送一次心跳。服务端提供两个接口：</p>

<table><thead><tr><th>接口</th><th>调用者</th><th>用途</th><th>是否需要 Token</th></tr></thead><tbody><tr><td><code>GET /api/presence/</code></td><td>网站前端</td><td>读取当前状态</td><td>否</td></tr><tr><td><code>POST /api/presence/update/</code></td><td>Windows Agent</td><td>更新状态或发送心跳</td><td>是</td></tr></tbody></table><p>返回数据里的 <code>since</code> 表示<strong>当前状态开始的时间</strong>，<code>updatedAt</code> 表示<strong>服务端最后收到上报的时间</strong>，单位都是 Unix 时间戳毫秒。同一状态的心跳只刷新 <code>updatedAt</code>；只有状态 key 改变时才重新设置 <code>since</code>。因此，在两个编辑器之间切换但都属于 <code>coding</code> 时，持续时间仍然接着计算。</p><p>前端默认每 30 秒读取一次 API。如果 <code>updatedAt</code> 已经超过 90 秒没有更新，就显示 Offline。这个判断发生在浏览器里，不会把服务器存储的状态改成 <code>offline</code>；电脑突然关机或网络断开时，也能自然转为离线。页面进入后台后会跳过网络请求，重新可见时立即刷新。</p><p>这样，网站仍然只是静态 HTML、CSS 和 JavaScript，实时状态由浏览器在运行时获取。</p></section>
<section><h1>准备源码与运行环境<a href="#准备源码与运行环境"><span>#</span></a></h1><p>先从我的 <a href="https://github.com/FireflyTornado/Firefly-FTver" target="_blank">Firefly 源码仓库</a>取得包含 Presence 的版本。本文使用的主要文件如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>src/</span></div></div><div><div><div>2</div></div><div><span>├── components/widget/Presence.astro    # 前端面板</span></div></div><div><div><div>3</div></div><div><span>├── config/presenceConfig.ts            # 状态、进程映射、默认参数</span></div></div><div><div><div>4</div></div><div><span>├── types/presenceConfig.ts             # 配置类型</span></div></div><div><div><div>5</div></div><div><span>├── utils/presence-store.ts             # 数据类型及本地开发状态存储</span></div></div><div><div><div>6</div></div><div><span>└── integrations/presence-dev-api.ts    # 可选：本地开发 API</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>server/presence/                        # Fastify 服务端</span></div></div><div><div><div>9</div></div><div><span>scripts/presence-agent/                 # Windows Agent 与托盘源码</span></div></div><div><div><div>10</div></div><div><span>deploy/presence/                        # 环境变量、systemd、Nginx 模板</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>构建服务端时，<code>server/presence/types.ts</code> 还会引用 <code>src/config/presenceConfig.ts</code> 中的类型；Agent 则直接读取这份配置。第一次部署可以保留完整仓库作为构建目录，再把生成的产物分别安装到服务器和电脑上。</p>

<table><thead><tr><th>环境</th><th>需要准备的内容</th></tr></thead><tbody><tr><td>网站项目</td><td>Astro；本文的前端组件按 Astro 写法接入</td></tr><tr><td>Linux 服务器</td><td>Node.js 24 或更新且兼容的版本、pnpm、Nginx、systemd、OpenSSL</td></tr><tr><td>Windows 构建电脑</td><td>Windows x64、Node.js、pnpm、.NET 8 SDK</td></tr><tr><td>Windows 使用电脑</td><td>能运行发布版的 Windows x64 环境，以及普通用户可写的安装目录</td></tr></tbody></table><p>版本要求以你取得的源码为准。本文对应的服务端声明 <code>node &gt;=24.0.0</code>，仓库根目录声明 <code>node &gt;=22.23.0</code>、<code>pnpm@11.22.0</code>；使用 Node.js 24 可以同时满足两边的要求。托盘构建脚本会使用 .NET SDK，发布产物包含运行时，使用 EXE 时无需另外安装 Node.js、pnpm 或 .NET Runtime。</p><p>以下示例统一把网站写成 <code>https://example.com</code>。请替换成自己的域名，并提前让 DNS、网站 HTTPS 和证书正常工作。</p><section><h2>把前端组件移植到网站<a href="#把前端组件移植到网站"><span>#</span></a></h2></section><section><h2>1. 复制组件与配置<a href="#1-复制组件与配置"><span>#</span></a></h2><p>如果目标也是 Astro 项目，先按相同路径复制这四个文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>src/components/widget/Presence.astro</span></div></div><div><div><div>2</div></div><div><span>src/config/presenceConfig.ts</span></div></div><div><div><div>3</div></div><div><span>src/types/presenceConfig.ts</span></div></div><div><div><div>4</div></div><div><span>src/utils/presence-store.ts</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>Presence.astro</code> 对 <code>presence-store.ts</code> 的引用使用的是 <code>import type</code>，前端只需要其中的 <code>PresenceData</code> 类型，不会因此拥有服务端存储功能。你也可以把这个接口单独移到自己的类型文件，再修改组件顶部和 <code>&lt;script&gt;</code> 内的两处类型导入。</p><p>源码中的 <code>@/</code> 指向 <code>src/</code>。目标项目如果没有这个别名，在原有 <code>tsconfig.json</code> 的 <code>compilerOptions.paths</code> 中合并：</p><div><figure><figcaption><span>tsconfig.json</span></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"@/*"</span><span>: [</span><span>"./src/*"</span><span>]</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以把组件里的导入改成目标项目对应的相对路径。配置文件与类型文件之间的相对路径需要一起保持正确。</p></section><section><h2>2. 处理卡片外壳与样式<a href="#2-处理卡片外壳与样式"><span>#</span></a></h2><p>面板使用了 Firefly 的 <code>WidgetLayout.astro</code>。如果你的网站已经有这个组件，可以直接使用。</p><p>如果是其他 Astro 主题，可以换成自己的卡片容器：</p><p>在 <code>Presence.astro</code> 中删除这一行：</p><div><figure><figcaption><span>src/components/widget/Presence.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>WidgetLayout</span><span> </span><span>from</span><span> </span><span>"@/components/common/WidgetLayout.astro"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把包裹 <code>&lt;presence-widget&gt;</code> 的 <code>&lt;WidgetLayout ...&gt;</code> 和 <code>&lt;/WidgetLayout&gt;</code> 分别替换为：</p><div><figure><figcaption><span>src/components/widget/Presence.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>section</span><span> </span><span>id</span><span>=</span><span>"presence-widget"</span><span> </span><span>class:list</span><span>=</span><span>{</span><span>[</span><span>"presence-card"</span><span><span>, </span><span>className</span><span>]</span></span><span>}</span><span> </span><span>style</span><span>=</span><span>{</span><span>style</span><span>}</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span>  </span><span>&lt;!-- 原有 presence-widget 及内部内容全部保留 --&gt;</span></div></div><div><div><div>3</div></div><div><span>&lt;/</span><span>section</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上面展示的是外层替换位置，不要把组件内部内容替换成注释。原有 <code>&lt;script&gt;</code> 和 <code>&lt;style&gt;</code> 也要保留。</p><p>随后在组件原有 <code>&lt;style&gt;</code> 中加入卡片样式，补上它使用的 CSS 变量和工具类：</p><div><div><div><figure><figcaption><span>src/components/widget/Presence.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>.presence-card</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>--radius-large</span><span>: </span><span><span>1</span><span>rem</span></span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>--primary</span><span>: </span><span>#</span><span>2563eb</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>--btn-regular-bg</span><span>: </span><span>#</span><span>f1f5f9</span><span>;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>--btn-regular-bg-hover</span><span>: </span><span>#</span><span>e2e8f0</span><span>;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>--line-divider</span><span>: </span><span>#</span><span>e5e7eb</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>padding: </span><span><span>1</span><span>rem</span></span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>border: </span><span><span>1</span><span>px</span></span><span><span> </span><span>solid</span><span> </span></span><span>var</span><span>(</span><span>--line-divider</span><span>);</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>border-radius: </span><span>var</span><span>(</span><span>--radius-large</span><span>);</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>background: </span><span>#</span><span>fff</span><span>;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>:global(</span><span>.dark</span><span>) </span><span>.presence-card</span><span> {</span></div></div><div><div><div>14</div></div><div><span>  </span><span>--primary</span><span>: </span><span>#</span><span>93c5fd</span><span>;</span></div></div><div><div><div>15</div></div><div><span>  </span><span>--btn-regular-bg</span><span>: </span><span>#</span><span>262626</span><span>;</span></div></div><div><div><div>16</div></div><div><span>  </span><span>--btn-regular-bg-hover</span><span>: </span><span>#</span><span>404040</span><span>;</span></div></div><div><div><div>17</div></div><div><span>  </span><span>--line-divider</span><span>: </span><span>#</span><span>404040</span><span>;</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>background: </span><span>#</span><span>171717</span><span>;</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>.sr-only</span><span> {</span></div></div><div><div><div>22</div></div><div><span><span>  </span></span><span>position: </span><span>absolute</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>width: </span><span><span>1</span><span>px</span></span><span>;</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>height: </span><span><span>1</span><span>px</span></span><span>;</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>padding: </span><span>0</span><span>;</span></div></div><div><div><div>26</div></div><div><span><span>  </span></span><span>margin: </span><span><span>-1</span><span>px</span></span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>overflow: </span><span>hidden</span><span>;</span></div></div><div><div><div>28</div></div><div><span><span>  </span></span><span>clip: </span><span>rect</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>0</span><span>, </span><span>0</span><span>);</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>white-space: </span><span>nowrap</span><span>;</span></div></div><div><div><div>30</div></div><div><span><span>  </span></span><span>border: </span><span>0</span><span>;</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>原组件的深色样式根据祖先元素上的 <code>.dark</code> 类切换。如果你的主题用 <code>data-theme</code> 等方式控制配色，需要同步调整相关选择器。直接复制 Firefly 的 <code>WidgetLayout.astro</code> 则会继续带入 <code>astro-icon</code>、国际化模块和主题工具类依赖，移植时也要处理它们。</p></section><section><h2>3. 在页面或侧边栏中挂载<a href="#3-在页面或侧边栏中挂载"><span>#</span></a></h2><p>在自己的 Astro 布局或侧边栏组件里导入并渲染：</p><div><figure><figcaption><span>src/components/layout/SideBar.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>---</span></div></div><div><div><div>2</div></div><div><span>import</span><span> </span><span>Presence</span><span> </span><span>from</span><span> </span><span>"@/components/widget/Presence.astro"</span><span>;</span></div></div><div><div><div>3</div></div><div><span>---</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>&lt;</span><span>aside</span><span>&gt;</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>&lt;</span><span>Presence</span><span> /&gt;</span></div></div><div><div><div>7</div></div><div><span>&lt;/</span><span>aside</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这个组件的交互由内置 <code>&lt;script&gt;</code> 注册自定义元素完成，直接使用 <code>&lt;Presence /&gt;</code> 即可。</p><p>如果你使用的是带组件注册表的 Firefly 版本，则在 <code>src/components/layout/SideBar.astro</code> 的 <code>componentMap</code> 中注册 <code>presence: Presence</code>，在 <code>src/types/sidebarConfig.ts</code> 的 <code>WidgetComponentType</code> 中加入 <code>"presence"</code>，再向 <code>src/config/sidebarConfig.ts</code> 对应的侧栏数组添加：</p><div><figure><figcaption><span>src/config/sidebarConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>type</span><span>: </span><span>"presence"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>enable</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>position</span><span>: </span><span>"sticky"</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>showOnPostPage</span><span>: </span><span>true</span><span>,</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>本文源码已包含注册项，使用这份源码时只需检查是否启用。手机端由 <code>mobileBottomComponents</code> 单独控制；其中没有 <code>position</code> 字段，需要按移动端配置单独添加。</p></section><section><h2>4. 确认读取地址<a href="#4-确认读取地址"><span>#</span></a></h2><p>组件的 <code>loadPresence()</code> 默认请求：</p><div><figure><figcaption><span>src/components/widget/Presence.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>"/api/presence/"</span><span>, {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>cache</span><span>:</span><span> </span><span>"no-store"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>signal</span><span>:</span><span> </span><span>this</span><span>.</span><span>listeners</span><span>?.</span><span>signal</span><span>,</span></div></div><div><div><div>4</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这意味着浏览器访问的是<strong>网站当前域名下的 API</strong>。下面的部署步骤会让 Nginx 把这个路径转发给 Presence 服务。</p><p>如果网站托管在其他平台，例如静态托管服务，你仍然需要在网站域名下设置相应的 API 转发，或改成自己的独立 API 地址。现有 Fastify 服务没有配置 CORS，单纯把这里换成另一个域名，并不能保证浏览器成功读取；跨域方案还需要在服务端或代理层配置允许的网站来源。使用 HTTPS 网站时，API 也应使用 HTTPS。</p><p>组件使用根路径 <code>/api/presence/</code>。即使网站放在 <code>/blog/</code> 子目录，它仍请求域名根目录的 API，代理路径需要与之对应。</p><p>前端只需要 GET 地址，<strong>不要在组件、网页脚本或公开环境变量中填写 Bearer Token</strong>。</p></section><section><h2>5. 可选：先在本地预览<a href="#5-可选先在本地预览"><span>#</span></a></h2><p>在完整源码仓库中执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>pnpm install </span><span>--</span><span>frozen</span><span>-</span><span>lockfile</span></span></div></div><div><div><div>2</div></div><div><span>pnpm dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>开发模式下，组件会显示 DEV 状态切换按钮，方便预览不同状态的外观，生产构建不会显示这些按钮。源码中的 <code>USE_MOCK_PRESENCE</code> 默认是 <code>false</code>；如需持续使用模拟状态预览，可以临时改为 <code>true</code>，完成后改回 <code>false</code>。保持 <code>false</code> 时，后续真实 API 轮询会覆盖按钮设置的预览状态。</p><p>如果要在其他 Astro 项目里使用本地开发 API，还要复制 <code>src/integrations/presence-dev-api.ts</code>，并把下面的集成合并到原有 <code>astro.config.mjs</code>：</p><div><figure><figcaption><span>astro.config.mjs</span></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> </span><span>presenceDevApi</span><span> </span><span>from</span><span> </span><span>"./src/integrations/presence-dev-api"</span><span>;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>// 在已有 integrations 数组中追加：</span></div></div><div><div><div>4</div></div><div><span>presenceDevApi</span><span>(),</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这套开发接口仅保存内存状态，POST 不要求 Token，只用于本地调试。它不会随静态构建变成生产 API，正式上线仍需要下面的独立服务端。</p></section></section>
<section><h1>部署 Presence 服务端<a href="#部署-presence-服务端"><span>#</span></a></h1><p>下面采用 Linux 原生构建，再通过 systemd 常驻运行。命令中的 <code>&lt;源码仓库地址&gt;</code> 是包含 Presence 的完整仓库地址。</p><section><h2>1. 在 Linux 上构建发布目录<a href="#1-在-linux-上构建发布目录"><span>#</span></a></h2><p>先检查环境：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>-v</span></div></div><div><div><div>2</div></div><div><span>pnpm</span><span> </span><span>-v</span></div></div><div><div><div>3</div></div><div><span>nginx</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用一个专门的源码构建目录：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>-p</span><span> </span><span>"</span><span>$HOME</span><span>/presence-build"</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>"</span><span>$HOME</span><span>/presence-build"</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>&lt;源码仓库地址&gt;</span><span> </span><span>Firefly</span></div></div><div><div><div>4</div></div><div><span>cd</span><span> </span><span>Firefly</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>pnpm</span><span> </span><span>install</span><span> </span><span>--frozen-lockfile</span></div></div><div><div><div>7</div></div><div><span>pnpm</span><span> </span><span>presence:server:build</span></div></div><div><div><div>8</div></div><div><span>pnpm</span><span> </span><span>--filter</span><span> </span><span>@nightbug/presence</span><span> </span><span>--prod</span><span> </span><span>deploy</span><span> </span><span>./release-presence</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>ls</span><span> </span><span>-l</span><span> </span><span>release-presence/dist/index.js</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>release-presence</code> 在首次部署时应是一个尚未使用的输出目录。再次构建时可以换一个新目录名，避免与已有产物混在一起。</p><p><code>pnpm deploy</code> 会带上生产依赖。不要直接把 Windows 项目中的 <code>node_modules</code> 复制到 Linux；网站的静态构建产物和这个 API 发布目录也需要分别部署。</p></section><section><h2>2. 创建运行用户并安装程序<a href="#2-创建运行用户并安装程序"><span>#</span></a></h2><p>仍在 Linux 的源码根目录执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>useradd</span><span> </span><span>--system</span><span> </span><span>--user-group</span><span> </span><span>--home-dir</span><span> </span><span>/var/lib/nightbug-presence</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>--no-create-home</span><span> </span><span>--shell</span><span> </span><span>/usr/sbin/nologin</span><span> </span><span>presence</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>sudo</span><span> </span><span>install</span><span> </span><span>-d</span><span> </span><span>-o</span><span> </span><span>root</span><span> </span><span>-g</span><span> </span><span>root</span><span> </span><span>-m</span><span> </span><span>755</span><span> </span><span>/opt/nightbug-presence</span></div></div><div><div><div>5</div></div><div><span>sudo</span><span> </span><span>cp</span><span> </span><span>-a</span><span> </span><span>release-presence/.</span><span> </span><span>/opt/nightbug-presence/</span></div></div><div><div><div>6</div></div><div><span>sudo</span><span> </span><span>chown</span><span> </span><span>-R</span><span> </span><span>root:root</span><span> </span><span>/opt/nightbug-presence</span></div></div><div><div><div>7</div></div><div><span>sudo</span><span> </span><span>chmod</span><span> </span><span>-R</span><span> </span><span>a+rX</span><span> </span><span>/opt/nightbug-presence</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>sudo</span><span> </span><span>install</span><span> </span><span>-d</span><span> </span><span>-o</span><span> </span><span>presence</span><span> </span><span>-g</span><span> </span><span>presence</span><span> </span><span>-m</span><span> </span><span>750</span><span> </span><span>/var/lib/nightbug-presence</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是首次安装流程。如果 <code>presence</code> 用户已经存在，不需要重复创建。程序文件放在 <code>/opt/nightbug-presence</code>，状态文件放在 <code>/var/lib/nightbug-presence</code>；后者必须允许 <code>presence</code> 用户写入。</p></section><section><h2>3. 设置服务端 Token<a href="#3-设置服务端-token"><span>#</span></a></h2><p>生成一份随机密钥：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>openssl</span><span> </span><span>rand</span><span> </span><span>-hex</span><span> </span><span>32</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>妥善保存输出，稍后在 Windows 客户端中输入同一份值。</p><p>从仓库安装环境变量模板：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>install</span><span> </span><span>-o</span><span> </span><span>root</span><span> </span><span>-g</span><span> </span><span>root</span><span> </span><span>-m</span><span> </span><span>600</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>deploy/presence/nightbug-presence.env.example</span><span> </span><span>/etc/nightbug-presence.env</span></div></div><div><div><div>3</div></div><div><span>sudoedit</span><span> </span><span>/etc/nightbug-presence.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改为：</p><div><figure><figcaption><span>deploy/presence/nightbug-presence.env.example</span></figcaption><pre><code><div><div><div>1</div></div><div><span>PRESENCE_TOKEN</span><span><span>=</span><span>替换为刚生成的随机密钥</span></span></div></div><div><div><div>2</div></div><div><span>PRESENCE_HOST</span><span><span>=</span><span>127.0.0.1</span></span></div></div><div><div><div>3</div></div><div><span>PRESENCE_PORT</span><span><span>=</span><span>8765</span></span></div></div><div><div><div>4</div></div><div><span>PRESENCE_STATE_FILE</span><span><span>=</span><span>/var/lib/nightbug-presence/presence.json</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 Token 是服务端用于验证 Agent 的秘密。不要把真实值提交到仓库，也不要发给访客。</p></section><section><h2>4. 配置 systemd 并启动<a href="#4-配置-systemd-并启动"><span>#</span></a></h2><p>安装仓库中的服务模板：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>install</span><span> </span><span>-o</span><span> </span><span>root</span><span> </span><span>-g</span><span> </span><span>root</span><span> </span><span>-m</span><span> </span><span>644</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>deploy/presence/nightbug-presence.service</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>/etc/systemd/system/nightbug-presence.service</span></div></div><div><div><div>4</div></div><div><span>command</span><span> </span><span>-v</span><span> </span><span>node</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>模板默认使用：</p><div><figure><figcaption><span>deploy/presence/nightbug-presence.service</span></figcaption><pre><code><div><div><div>1</div></div><div><span>User</span><span><span>=</span><span>presence</span></span></div></div><div><div><div>2</div></div><div><span>Group</span><span><span>=</span><span>presence</span></span></div></div><div><div><div>3</div></div><div><span>WorkingDirectory</span><span><span>=</span><span>/opt/nightbug-presence</span></span></div></div><div><div><div>4</div></div><div><span>EnvironmentFile</span><span><span>=</span><span>/etc/nightbug-presence.env</span></span></div></div><div><div><div>5</div></div><div><span>ExecStart</span><span><span>=</span><span>/usr/bin/node /opt/nightbug-presence/dist/index.js</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果 Node.js 实际位于其他位置，使用 <code>sudoedit /etc/systemd/system/nightbug-presence.service</code> 修改 <code>ExecStart</code>。路径必须让 <code>presence</code> 用户可执行；模板开启了 <code>ProtectHome=true</code>，因此不要直接指向个人 home 目录里的 Node.js。</p><p>启动并检查：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>daemon-reload</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>enable</span><span> </span><span>--now</span><span> </span><span>nightbug-presence</span></div></div><div><div><div>3</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>status</span><span> </span><span>nightbug-presence</span><span> </span><span>--no-pager</span></div></div><div><div><div>4</div></div><div><span>curl</span><span> </span><span>-i</span><span> </span><span>http://127.0.0.1:8765/api/presence/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首次启动、尚无上报时，GET 应返回 <code>200</code>，内容类似：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"state"</span><span>: </span><span>"offline"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"title"</span><span>: </span><span>"当前离线"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"detail"</span><span>: </span><span>"下次见"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"updatedAt"</span><span>: </span><span>0</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>服务读取到已有的合法状态文件时，会恢复此前保存的数据。GET 不会自动把过期状态改成离线，这是前端负责的判断。</p></section><section><h2>5. 用 Nginx 对外提供 HTTPS API<a href="#5-用-nginx-对外提供-https-api"><span>#</span></a></h2><p>在<strong>网站域名对应、监听 HTTPS 的 <code>server</code> 块内部</strong>加入下面的配置，或把它保存到该站点已经 include 的扩展配置文件里：</p><div><figure><figcaption><span>deploy/presence/nginx-presence.conf</span></figcaption><pre><code><div><div><div>1</div></div><div><span>location</span><span> ~ </span><span>^/api/presence(?:/|/update/?)?$ </span><span>{</span></div></div><div><div><div>2</div></div><div><span>   <span> </span></span><span>proxy_pass </span><span>http://127.0.0.1:8765;</span></div></div><div><div><div>3</div></div><div><span>   <span> </span></span><span>proxy_http_version </span><span>1.1</span><span>;</span></div></div><div><div><div>4</div></div><div><span>   <span> </span></span><span>proxy_set_header </span><span>Host $</span><span>host</span><span>;</span></div></div><div><div><div>5</div></div><div><span>   <span> </span></span><span>proxy_set_header </span><span>X-Real-IP $</span><span>remote_addr</span><span>;</span></div></div><div><div><div>6</div></div><div><span>   <span> </span></span><span>proxy_set_header </span><span>X-Forwarded-For $</span><span>proxy_add_x_forwarded_for</span><span>;</span></div></div><div><div><div>7</div></div><div><span>   <span> </span></span><span>proxy_set_header </span><span>X-Forwarded-Proto $</span><span>scheme</span><span>;</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>   <span> </span></span><span>client_max_body_size </span><span>16k</span><span>;</span></div></div><div><div><div>10</div></div><div><span>   <span> </span></span><span>add_header </span><span>Cache-Control </span><span>"no-store"</span><span> always;</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>proxy_pass</code> 保留原请求路径。配置应覆盖 <code>/api/presence/</code> 和 <code>/api/presence/update/</code>，并且不能被站点已有的更高优先级规则截走。</p><p>使用宝塔面板时，可以通过站点配置编辑对应的 vhost，或使用它已 include 的站点扩展目录。不要把这段 <code>location</code> 放到 Nginx 顶层，也不要覆盖整份网站配置。</p><p>检查语法后重载：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>nginx</span><span> </span><span>-t</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>reload</span><span> </span><span>nginx</span></div></div><div><div><div>3</div></div><div><span>curl</span><span> </span><span>-i</span><span> </span><span>https://example.com/api/presence/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>宝塔等环境如果由面板管理 Nginx，应使用面板对应的检查与重载方式。公网 GET 应返回与本机接口一致的 JSON，并包含 <code>Cache-Control: no-store</code>。如果前面还有 CDN，也要让这两个 API 路径跳过缓存。</p><p>API 只监听 <code>127.0.0.1:8765</code>，不需要向公网开放 8765 端口。公网请求通过已有的 HTTPS 入口进入。</p></section><section><h2>6. 验证上报与鉴权<a href="#6-验证上报与鉴权"><span>#</span></a></h2><p>先测试未携带 Token 的请求：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-i</span><span> </span><span>https://example.com/api/presence/update/</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>-H</span><span> </span><span>'Content-Type: application/json'</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>--data</span><span> </span><span>'{"state":"coding","title":"正在写代码"}'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应返回 <code>401 Unauthorized</code>。</p><p>再测试正确 Token。下面使用隐藏输入和标准输入传递密钥，避免把真实值直接写进命令历史或 curl 的参数：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>read</span><span> </span><span>-rsp</span><span> </span><span>'Presence Token: '</span><span> </span><span>presence_token</span></div></div><div><div><div>2</div></div><div><span>printf</span><span> </span><span>'\n'</span></div></div><div><div><div>3</div></div><div><span>printf</span><span> </span><span>'header = "Authorization: Bearer %s"\n'</span><span> </span><span>"</span><span>$presence_token</span><span>"</span><span> | </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>curl</span><span> </span><span>--config</span><span> </span><span>-</span><span> </span><span>-i</span><span> </span><span>https://example.com/api/presence/update/</span><span> </span><span>\</span></div></div><div><div><div>5</div></div><div><span>    </span><span>-H</span><span> </span><span>'Content-Type: application/json'</span><span> </span><span>\</span></div></div><div><div><div>6</div></div><div><span>    </span><span>--data</span><span> </span><span>'{"state":"coding","title":"正在写代码","app":"Visual Studio Code","detail":"部署测试"}'</span></div></div><div><div><div>7</div></div><div><span>unset</span><span> </span><span>presence_token</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应返回 <code>200</code>。再次 GET，确认 <code>state</code>、<code>title</code>、<code>app</code> 等字段已经变化，并且包含数值形式的 <code>since</code>、<code>updatedAt</code>。</p><p>重复发送相同 <code>state</code> 后，<code>since</code> 应保持不变，<code>updatedAt</code> 则继续刷新。至此，API 已经具备接收客户端上报的条件。</p></section></section>
<section><h1>安装和配置 Windows 客户端<a href="#安装和配置-windows-客户端"><span>#</span></a></h1><p>网页不会自己读取你的电脑状态。接下来需要在希望展示状态的 Windows 电脑上运行 Agent。</p><section><h2>1. 构建客户端<a href="#1-构建客户端"><span>#</span></a></h2><p>在 Windows x64 构建电脑的完整源码根目录执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>node </span><span>-</span><span>v</span></span></div></div><div><div><div>2</div></div><div><span><span>pnpm </span><span>-</span><span>v</span></span></div></div><div><div><div>3</div></div><div><span><span>dotnet </span><span>--</span><span>list</span><span>-</span><span>sdks</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>pnpm install </span><span>--</span><span>frozen</span><span>-</span><span>lockfile</span></span></div></div><div><div><div>6</div></div><div><span>pnpm presence:agent:exe</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认已安装 .NET 8 SDK。成功后，发布目录是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>release/presence-agent/</span></div></div><div><div><div>2</div></div><div><span>├── NightbugPresence.exe</span></div></div><div><div><div>3</div></div><div><span>├── NightbugPresenceTray.exe</span></div></div><div><div><div>4</div></div><div><span>├── config.example.json</span></div></div><div><div><div>5</div></div><div><span>└── README.md</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果已经拿到与你所用配置对应的发布版，可以跳过构建。两个 EXE 要一起保留：Tray 负责托盘和交互，另一个负责检测与上报。</p></section><section><h2>2. 放入固定目录并设置 Token<a href="#2-放入固定目录并设置-token"><span>#</span></a></h2><p>将发布目录中的文件放到普通用户可写的位置，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>D:\Apps\NightbugPresence\</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>进入该目录并设置 Token：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Set-Location</span><span> D:\Apps\NightbugPresence</span></div></div><div><div><div>2</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span></span><span>set-token</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输入服务端 <code>/etc/nightbug-presence.env</code> 中的那份 Token。程序会通过 Windows DPAPI 的 <code>CurrentUser</code> 范围加密，保存为同目录的 <code>token.dat</code>。</p><p>这个命令也会读取或生成普通配置。不要把 Token 写进 <code>config.json</code>，也不要直接把 <code>token.dat</code> 当成可以跨机器、跨账户复用的凭据；更换 Windows 账户或电脑后，应重新设置 Token。</p></section><section><h2>3. 编辑客户端配置<a href="#3-编辑客户端配置"><span>#</span></a></h2><p>在 EXE 同级的 <code>config.json</code> 中填写：</p><div><figure><figcaption><span>scripts/presence-agent/config.example.json</span></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"apiUrl"</span><span>: </span><span>"https://example.com/api/presence/update/"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"detectInterval"</span><span>: </span><span>5000</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"heartbeatInterval"</span><span>: </span><span>30000</span><span>,</span></div></div><div><div><div>5</div></div><div><span>  </span><span>"idleTimeout"</span><span>: </span><span>600000</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>字段</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td><code>apiUrl</code></td><td>POST 上报接口，包含 <code>/update/</code></td><td><code>https://example.com/api/presence/update/</code></td></tr><tr><td><code>detectInterval</code></td><td>检查前台程序的间隔</td><td>5 秒</td></tr><tr><td><code>heartbeatInterval</code></td><td>状态不变时的心跳间隔</td><td>30 秒</td></tr><tr><td><code>idleTimeout</code></td><td>多久没有输入就显示 Idle</td><td>10 分钟</td></tr></tbody></table><p>配置中的所有时间单位都是<strong>毫秒</strong>。这里填写的是上报地址，不能填成前端使用的 GET 地址。</p><p>普通配置优先级为“合法的环境变量 → <code>config.json</code> → 内置默认值”。对应环境变量是 <code>PRESENCE_API_URL</code>、<code>PRESENCE_DETECT_INTERVAL</code>、<code>PRESENCE_HEARTBEAT_INTERVAL</code>、<code>PRESENCE_IDLE_TIMEOUT</code>。Token 则优先读取 <code>PRESENCE_TOKEN</code>，没有配置时再读取 <code>token.dat</code>。</p><p>如果曾经设置过这些环境变量，修改 JSON 后仍可能被覆盖。执行下面的命令检查最终值及其来源：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span></span><span>show-config</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认 API 是自己的 HTTPS 地址，Token 显示为已配置。修改配置或 Token 后，需要重新启动 Agent，让 Worker 重新读取。</p></section><section><h2>4. 启动托盘与登录自启<a href="#4-启动托盘与登录自启"><span>#</span></a></h2><p>双击 <code>NightbugPresenceTray.exe</code>，也可以通过 PowerShell 启动：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Start-Process</span><span><span> </span><span>-</span><span>FilePath .\</span></span><span>NightbugPresenceTray.exe</span><span><span> </span><span>-</span><span>WindowStyle </span></span><span>Hidden</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Tray 会启动后台 Worker。右键托盘图标可以查看状态、立即同步、暂停或恢复上报，也可以重新启动 Agent。</p><p>需要登录 Windows 后自动运行时，执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span></span><span>enable-autostart</span></div></div><div><div><div>2</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span><span>autostart</span><span>-</span><span>status</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是当前用户的<strong>登录自启动</strong>。以后移动安装目录，应先禁用旧路径，再从新目录启用一次：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span></span><span>disable-autostart</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>日常入口是 Tray。只启动其中一个后台 EXE，无法得到完整的托盘交互体验。</p></section></section>
<section><h1>个性化<a href="#个性化"><span>#</span></a></h1><p>运行参数保存在 <code>config.json</code>，但<strong>状态文案和进程映射来自源码中的 <code>src/config/presenceConfig.ts</code></strong>。两者的修改方式不同。</p><section><h2>修改状态显示内容<a href="#修改状态显示内容"><span>#</span></a></h2><p>例如，把已有的 <code>coding</code> 改成自己的文案：</p><div><figure><figcaption><span>src/config/presenceConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>coding</span><span>: {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>icon</span><span>: </span><span>"💻"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>title</span><span>: </span><span>"正在写代码"</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>app</span><span>: </span><span>"Visual Studio Code"</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>detail</span><span>: </span><span>"今天也在修 Bug"</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>status</span><span>: </span><span>"Online"</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>availability</span><span>: </span><span>"online"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>},</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>title</code>、<code>app</code>、<code>detail</code> 是 Agent 上报时使用的默认内容，进程规则还可以覆盖这些值；前端显示真实 API 返回的正文。<code>icon</code>、<code>illustration</code>、<code>status</code> 和 <code>availability</code> 则用于前端表现。只修改网站上的默认正文，不会覆盖旧 Agent 继续上报的文案。</p></section><section><h2>添加或修改进程映射<a href="#添加或修改进程映射"><span>#</span></a></h2><p>在 <code>processRules</code> 中，为已有状态增加程序：</p><div><figure><figcaption><span>src/config/presenceConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>state</span><span>: </span><span>"coding"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>processes</span><span>: [</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>{ </span><span>process</span><span>:</span><span> </span><span>"Code.exe"</span><span>, </span><span>app</span><span>:</span><span> </span><span>"Visual Studio Code"</span><span> },</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>{ </span><span>process</span><span>:</span><span> </span><span>"Cursor.exe"</span><span>, </span><span>app</span><span>:</span><span> </span><span>"Cursor"</span><span> },</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>],</span></div></div><div><div><div>7</div></div><div><span>},</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过 Windows 任务管理器的“详细信息”确认实际进程名。这里匹配的是前台程序的可执行文件名，忽略大小写，但需要精确匹配；规则从上往下检查，首次命中即停止。示例程序名仍应以你电脑上的实际值为准。</p><p>例如，将 <code>java.exe</code> 映射为 Minecraft，会把其他使用同名 Java 进程的前台程序也归到这个状态。规则只能反映进程级信息，需要根据自己的使用习惯取舍。</p><p>现有源码把聊天状态写成 <code>chating</code>。使用时保持这个 key 一致；如果要改为其他拼写，需要同步修改 <code>states</code> 和对应的 <code>processRules</code>。</p></section><section><h2>新增一种状态<a href="#新增一种状态"><span>#</span></a></h2><p>先在 <code>states</code> 中增加配置，再在 <code>processRules</code> 中引用同一个 key：</p><div><div><div><figure><figcaption><span>src/config/presenceConfig.ts</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 添加到 states：</span></div></div><div><div><div>2</div></div><div><span><span>drawing</span><span>: {</span></span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>icon</span><span>: </span><span>"🎨"</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>title</span><span>: </span><span>"正在画画"</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>detail</span><span>: </span><span>"慢慢涂两笔"</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>status</span><span>: </span><span>"Online"</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>availability</span><span>: </span><span>"online"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>},</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>// 添加到 processRules 数组：</span></div></div><div><div><div>11</div></div><div><span>{</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>state</span><span>: </span><span>"drawing"</span><span>,</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>processes</span><span>: [</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>{ </span><span>process</span><span>:</span><span> </span><span>"Photoshop.exe"</span><span>, </span><span>app</span><span>:</span><span> </span><span>"Adobe Photoshop"</span><span> },</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>],</span></div></div><div><div><div>16</div></div><div><span>},</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这两段是添加到不同位置的配置片段，不要合并成一个对象。普通状态的 TypeScript 类型和开发接口状态列表会从 <code>states</code> 自动派生。</p><p>生产 API 目前按字符格式校验状态 key，允许字母、数字、下划线和连字符，长度最多 64；它不使用固定状态白名单。因此新增合法 key 通常无需修改服务端，但<strong>前端必须先具备对应展示配置</strong>，否则读取新状态时可能无法渲染。新增状态可以先发布网站，再更新 Agent。</p></section><section><h2>使用自己的插画<a href="#使用自己的插画"><span>#</span></a></h2><p>例如把图片放到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public/assets/presence/coding.webp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 <code>ui</code> 中打开插画，并在相应状态配置中填写路径：</p><div><figure><figcaption><span>src/config/presenceConfig.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// ui 中：</span></div></div><div><div><div>2</div></div><div><span><span>useIllustration</span><span>: </span></span><span>true</span><span>,</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// coding 中：</span></div></div><div><div><div>5</div></div><div><span><span>illustration</span><span>: </span></span><span>"/assets/presence/coding.webp"</span><span>,</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>文件路径里的 <code>public</code> 不会出现在访问 URL 中。原组件在没有插画或图片加载失败时会回退为 emoji。</p></section><section><h2>修改后需要重新构建什么<a href="#修改后需要重新构建什么"><span>#</span></a></h2>

<table><thead><tr><th>修改内容</th><th>后续操作</th></tr></thead><tbody><tr><td>图标、插画、底部状态文本、前端计时参数</td><td>重新构建并部署网站</td></tr><tr><td>状态正文、进程映射、Agent 内置默认值</td><td>重新构建 Agent，替换两个 EXE；网站也同步使用这份配置</td></tr><tr><td>客户端 <code>config.json</code> 或 <code>token.dat</code></td><td>重新启动 Agent</td></tr><tr><td>服务端环境变量文件</td><td>重启 <code>nightbug-presence</code></td></tr><tr><td>systemd 服务定义</td><td><code>daemon-reload</code> 后重启服务</td></tr><tr><td>Nginx 配置</td><td>检查语法后重载 Nginx</td></tr></tbody></table><p>更新 Agent 前先退出 Tray，保留已有的 <code>config.json</code> 和 <code>token.dat</code>，替换两个 EXE 后再启动。已经存在的 <code>config.json</code> 会优先于新的内置默认值；想修改现有安装的检测间隔，应直接修改 JSON。</p><p>前端的 <code>offlineTimeout</code> 应明显大于 Agent 的 <code>heartbeatInterval</code>。默认 90 秒对 30 秒留出了网络波动空间；如果把心跳改为 60 秒，也要相应增加离线阈值。</p></section></section>
<section><h1>不是 Astro 网站如何接入<a href="#不是-astro-网站如何接入"><span>#</span></a></h1><p><code>Presence.astro</code> 是 Astro 组件，不能直接塞进普通 HTML 页面。不过，服务端和 Windows Agent 不依赖你的网站框架，你只需要按同样的字段读取 GET API。</p><p>下面是一份可以放进普通页面的最小展示示例，默认仍走同域 API。先在页面中加入容器和脚本引用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>div</span><span> </span><span>id</span><span>=</span><span>"presence"</span><span> </span><span>aria-live</span><span>=</span><span>"polite"</span><span>&gt;正在读取状态……&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>script</span><span> </span><span>src</span><span>=</span><span>"/presence.js"</span><span> </span><span>defer</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>再把下面的 JavaScript 保存为网站根路径可访问的 <code>presence.js</code>（使用 Astro 时可放在 <code>public/presence.js</code>）：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>  </span><span>const</span><span> </span><span>panel</span><span> </span><span>=</span><span><span> </span><span>document</span><span>.</span></span><span>querySelector</span><span>(</span><span>"#presence"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>  </span><span>let</span><span><span> </span><span>lastPresence</span><span>;</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>let</span><span><span> </span><span>loading</span><span> </span></span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>function</span><span> </span><span>renderPresence</span><span>() {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>lastPresence</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>if</span><span> (</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>lastPresence</span><span>.</span><span>state</span><span> </span><span>===</span><span> </span><span>"offline"</span><span> </span><span>||</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>Date</span><span>.</span><span>now</span><span>() </span><span>-</span><span><span> </span><span>lastPresence</span><span>.</span></span><span>updatedAt</span><span> </span><span>&gt;</span><span> </span><span>90_000</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>) {</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>panel</span><span>.</span><span>textContent</span><span> </span><span>=</span><span> </span><span>"🌙 当前离线"</span><span>;</span></div></div><div><div><div>13</div></div><div><span>      </span><span>return</span><span>;</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>    </span><span>const</span><span> </span><span>parts</span><span> </span><span>=</span><span><span> [</span><span>lastPresence</span><span>.</span></span><span>title</span><span><span>, </span><span>lastPresence</span><span>.</span></span><span>app</span><span><span>, </span><span>lastPresence</span><span>.</span></span><span>detail</span><span>];</span></div></div><div><div><div>17</div></div><div><span>    </span><span>if</span><span> (</span><span>typeof</span><span><span> </span><span>lastPresence</span><span>.</span></span><span>since</span><span> </span><span>===</span><span> </span><span>"number"</span><span>) {</span></div></div><div><div><div>18</div></div><div><span>      </span><span>const</span><span> </span><span>minutes</span><span> </span><span>=</span><span><span> </span><span>Math</span><span>.</span></span><span>max</span><span>(</span></div></div><div><div><div>19</div></div><div><span>        </span><span>1</span><span>,</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>Math</span><span>.</span><span>floor</span><span><span>((</span><span>Date</span><span>.</span></span><span>now</span><span>() </span><span>-</span><span><span> </span><span>lastPresence</span><span>.</span></span><span>since</span><span>) </span><span>/</span><span> </span><span>60_000</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>      </span></span><span>);</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>parts</span><span>.</span><span>push</span><span>(</span><span>`</span><span>${</span><span>minutes</span><span>}</span><span> 分钟`</span><span>);</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>panel</span><span>.</span><span>textContent</span><span> </span><span>=</span><span><span> </span><span>parts</span><span>.</span></span><span>filter</span><span><span>(</span><span>Boolean</span><span>).</span></span><span>join</span><span>(</span><span>" · "</span><span>);</span></div></div><div><div><div>25</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>  </span><span>async</span><span> </span><span>function</span><span> </span><span>refreshPresence</span><span>() {</span></div></div><div><div><div>28</div></div><div><span>    </span><span>if</span><span><span> (</span><span>document</span><span>.</span></span><span>hidden</span><span> </span><span>||</span><span><span> </span><span>loading</span><span>) </span></span><span>return</span><span>;</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>loading</span><span> </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>31</div></div><div><span>      </span><span>const</span><span> </span><span>response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span>(</span><span>"/api/presence/"</span><span>, { </span><span>cache</span><span>:</span><span> </span><span>"no-store"</span><span> });</span></div></div><div><div><div>32</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span><span>response</span><span>.</span></span><span>ok</span><span>) </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>`HTTP </span><span>${</span><span>response</span><span>.</span><span>status</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>33</div></div><div><span><span>      </span></span><span>lastPresence</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>response</span><span>.</span></span><span>json</span><span>();</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span> {</span></div></div><div><div><div>35</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span><span>lastPresence</span><span>) </span><span>panel</span><span>.</span></span><span>textContent</span><span> </span><span>=</span><span> </span><span>"暂时无法读取状态"</span><span>;</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>} </span><span>finally</span><span> {</span></div></div><div><div><div>37</div></div><div><span><span>      </span></span><span>loading</span><span> </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>38</div></div><div><span>      </span><span>renderPresence</span><span>();</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>  </span><span>refreshPresence</span><span>();</span></div></div><div><div><div>43</div></div><div><span>  </span><span>setInterval</span><span><span>(</span><span>refreshPresence</span><span>, </span></span><span>30_000</span><span>);</span></div></div><div><div><div>44</div></div><div><span>  </span><span>setInterval</span><span><span>(</span><span>renderPresence</span><span>, </span></span><span>15_000</span><span>);</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>document</span><span>.</span><span>addEventListener</span><span>(</span><span>"visibilitychange"</span><span>, () </span><span>=&gt;</span><span> {</span></div></div><div><div><div>46</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>document</span><span>.</span></span><span>hidden</span><span>) </span><span>refreshPresence</span><span>();</span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这里用 <code>textContent</code> 写入内容。你可以在此基础上改成自己的卡片，或用 React、Vue、Svelte 等框架重新实现；上报端和服务端都可以继续沿用。</p></section>
<section><h1>上线前验证<a href="#上线前验证"><span>#</span></a></h1><p>在部署了前端的网站里打开开发者工具，检查 GET 请求是否返回 JSON。然后在 Windows 上打开一个已配置的程序，并保持它为前台窗口。</p><p>默认检测间隔是 5 秒，前端轮询间隔是 30 秒，所以网页不会在切换窗口的同一瞬间变化。也可以用托盘的“立即同步”并刷新网页来快速确认。</p><p>部署成功后，应能观察到：</p><ul>
<li>公网 GET 返回 <code>200</code> 和 <code>Cache-Control: no-store</code>。</li>
<li>未认证的 POST 返回 <code>401</code>，正确 Token 的 POST 返回 <code>200</code>。</li>
<li>Agent 运行时，GET 数据中的 <code>updatedAt</code> 随心跳持续刷新。</li>
<li>切换到已配置的前台程序时，网页显示相应状态。</li>
<li>持续不操作超过 Idle 阈值后，网页显示暂时离开。</li>
<li>退出 Tray 或暂停上报后，超过 90 秒加上本地刷新等待时间，网页转为 Offline。</li>
<li>重新启动或恢复 Agent 后，网页在下一次读取时恢复显示。</li>
</ul><p>完成这些检查后，再执行网站项目自己的诊断和构建流程。Firefly 对应的是：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pnpm check</span></div></div><div><div><div>2</div></div><div><span><span>pnpm type</span><span>-</span><span>check</span></span></div></div><div><div><div>3</div></div><div><span>pnpm build</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将网站构建产物部署到原来的静态托管位置即可。<code>pnpm build</code> 不会替你安装 Linux API 服务或启动 Windows Agent，这三个部分要分别完成。</p></section>
<section><h1>常见问题与日常维护<a href="#常见问题与日常维护"><span>#</span></a></h1>

<table><thead><tr><th>现象</th><th>先检查什么</th></tr></thead><tbody><tr><td>GET 返回网页或 <code>404</code></td><td>请求路径是否正确，Nginx 配置是否属于当前域名且已经加载</td></tr><tr><td>公网 <code>502</code>，本机 GET 也失败</td><td>systemd 服务是否运行，Node 路径和依赖是否正确</td></tr><tr><td>公网 <code>502</code>，本机 GET 正常</td><td>Nginx 的代理地址及对应的站点配置</td></tr><tr><td>POST 返回 <code>401</code></td><td>Agent 与服务器 Token 是否一致，环境变量是否覆盖了 <code>token.dat</code></td></tr><tr><td>POST 返回 <code>400</code></td><td>JSON 格式、<code>state</code> 和非空 <code>title</code> 是否符合要求</td></tr><tr><td>API 更新正常，网页总是旧状态</td><td>CDN 缓存、组件的 <code>USE_MOCK_PRESENCE</code>、GET 地址和浏览器控制台</td></tr><tr><td>程序被归为普通 Online</td><td>实际前台进程名是否匹配，映射修改后是否重新构建 Agent</td></tr><tr><td>新增状态后页面报错</td><td>网站是否已经部署包含该状态的配置</td></tr><tr><td>改了 JSON 却没有生效</td><td>Worker 是否已重启，是否存在更高优先级环境变量</td></tr><tr><td>状态偶尔误判离线</td><td>心跳与离线阈值是否匹配，服务器与电脑时间是否准确</td></tr></tbody></table><p>服务端日志和状态可以这样查看：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>journalctl</span><span> </span><span>-u</span><span> </span><span>nightbug-presence</span><span> </span><span>-n</span><span> </span><span>100</span><span> </span><span>--no-pager</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>status</span><span> </span><span>nightbug-presence</span><span> </span><span>--no-pager</span></div></div><div><div><div>3</div></div><div><span>curl</span><span> </span><span>-i</span><span> </span><span>http://127.0.0.1:8765/api/presence/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>客户端则先检查：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span></span><span>show-config</span></div></div><div><div><div>2</div></div><div><span>.\</span><span>NightbugPresence.exe</span><span><span> </span><span>--</span><span>autostart</span><span>-</span><span>status</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更换 Token 时，先修改服务端环境变量并重启服务，再在 Windows 端重新执行 <code>--set-token</code>，最后重新启动 Agent。</p><p>想暂时停止显示在线状态，可以退出 Tray 或暂停上报；前端会在超时后显示离线。准备停用整套功能时，先移除网站组件，再禁用客户端登录自启，并停止、禁用服务端：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>disable</span><span> </span><span>--now</span><span> </span><span>nightbug-presence</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>服务器更新、宝塔部署和完整卸载的详细说明，可以在源码的 <code>server/presence/</code> 目录中找到：</p><ul>
<li><code>nightbug-presence-server-deployment-guide-merged.md</code></li>
<li><code>nightbug-presence-bt-panel-deployment-guide-merged.md</code></li>
<li><code>presence-windows-agent-deployment-guide.md</code></li>
</ul><p>装好以后，访客看到的仍然只是一块很小的状态卡片。它不需要透露你正在处理哪份文件，只用一句“正在写代码”或“稍后回来”，就能在这个静态页面上展示你的动态喔。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/essay/260915-from-local-to-online/</id>
      <title type="text">写在成功备案后</title>
      <published>2026-09-15T00:00:00.000Z</published>
      <updated>2026-09-15T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/essay/260915-from-local-to-online/"/>
      <summary type="text">备案完成了就万事大吉了吗？明明才刚刚开始好吧。</summary>
      <content type="html"><![CDATA[<p>决定做一个网站之后，事情好像并没有立刻变得多么复杂。</p>
<p>好像只是先改改模板里的参数，然后再写一点文章而已。</p>
<p>那时的目标很简单：先让它看起来像一个“属于我”的网站。自己觉得舒服一点，就已经很开心了。</p>
<p>那时候的它还只是存在于我的电脑里。</p>
<p>没有域名，没有服务器，也没有谁能够在互联网上访问它。</p>
<section><h1>先给它一个名字<a href="#先给它一个名字"><span>#</span></a></h1><p>页面做好之后，我开始考虑域名。</p><p>这一步比我想象中有意思。</p><p>比起页面上的 title，我感觉它才更像网站的名字。</p><p>我会在意它好不好记，会不会太长，和网站本身搭不搭。</p><p>我开始思考我的网站现在是什么样子，以后又会拿来做什么。</p><p>思来想去，便有了你在地址栏看到的那个名字。</p><p>如你所见，我在后面加了一个“club”。</p><p>一个社团里面有着各种各样的分工，承担着各种各样的任务。这个域名或许也是这样，我也会分给它很多任务，拿来做更多有意思的事情！</p><p>虽然这时候网站依然没有真正上线，但它从一个只存在于本地的文件夹，开始慢慢变成一个“有名字的地方”。</p></section>
<section><h1>服务器要放在哪里<a href="#服务器要放在哪里"><span>#</span></a></h1><p>接下来就是服务器。</p><p>刚开始看的时候，我先思考的并不是配置问题，而是一个更基础的问题：</p><p>到底选国内服务器，还是国外服务器？</p><p>国外服务器看起来省事一些，根本不需要走备案流程，便可以立即上线；国内服务器对国内访问更友好，但相应地也意味着要认真处理备案这件事。</p><p>我来回比较了一阵。</p><p>一边看价格，一边看访问体验，也一边考虑着后面的规划。</p><p>最后，我还是决定选择国内服务器。</p><p>做都做了，为什么不多做一点没做过的事情呢？一次性走完这全套流程，后面也会更得心应手一些。</p><p>方向确定以后，接下来才轮到配置。</p><p>CPU、内存、硬盘、带宽、流量……</p><p>这些参数以前看起来都只是一些数字，可当它们真的变成需要自己选择的时候，我才开始认真理解它们分别意味着什么。</p><p>一开始我会下意识觉得：</p><p>配置是不是太低了？</p><p>带宽会不会不够用？</p><p>万一以后有更多需求怎么办？</p><p>但查了不少资料以后，我慢慢意识到，一个刚起步的个人网站其实没有必要一开始就把所有东西都堆到很高。</p><p>最后，我便选择了一台这样的服务器：2核，2G，3M带宽。</p><p>配置很低，但对现在这个网站来说已经足够了。（以后的事以后再说）</p><p>从这一刻开始，这个网站终于有了一个可以“住进去”的地方。</p></section>
<section><h1>在正式上线之前，先把一些概念弄明白<a href="#在正式上线之前先把一些概念弄明白"><span>#</span></a></h1><p>买完服务器之后，我没有立刻开始把域名解析过去，也没有马上接入 CDN。</p><p>一方面是因为备案还没有完成，另一方面也是因为我自己心里很清楚：如果只是照着教程一步一步点过去，却不知道自己到底在做什么，那么后面一旦出问题，还是会一下子懵掉。</p><p>所以在这段时间里，我先去学习了一些最基础的东西。</p><p>比如 DNS 解析。</p><p>以前我只知道，输入一个域名就可以打开网站。</p><p>但域名到底是怎么找到服务器的，我并没有真正想过。</p><p>后来才慢慢弄明白，域名本身更像是一个方便人记住的名字，而 DNS 的作用，是把这个名字和真正要访问的目标关联起来。</p><p>再比如 CDN。</p><p>我一开始很在意服务器本身的带宽，总觉得带宽小了，网站访问起来肯定会很卡。</p><p>后来查资料才知道，对于我这种以静态内容为主的网站来说，很多资源都可以交给 CDN 去分发。</p><p>这样一来，源站并不一定要承担所有访问压力。</p><p>这个认知对我来说还挺重要。</p><p>因为它让我意识到，遇到性能问题时，解决方式并不总是砸钱，“买更高的配置”。（虽然说砸钱确实能解决就是）</p><p>有时候真正需要做的，是先弄清楚问题出在哪里，再选择更合适的方式。（花钱怎么就不合适了？哦原来是我太穷了）</p><p>这期间我还顺带了解了一些 HTTPS、证书、缓存、回源之类的概念。</p><p>不算深入，但至少到了后面真正开始配置时，我知道每一步大概是在做什么。</p><p>那段时间有点像是在“预习”。</p><p>网站还没有真正上线，但我已经开始提前想象它上线以后会遇到哪些问题。</p></section>
<section><h1>9 月 8 日，提交备案<a href="#9-月-8-日提交备案"><span>#</span></a></h1><p>等域名、服务器这些东西都准备好之后，下一步就是 ICP 备案。</p><p>以前我当然见过备案号。</p><p>很多网站底部都会有一行很小的文字。</p><p>但以前从来没有认真想过，它到底是怎么来的。</p><p>直到轮到自己。</p><p>在真正提交之前，我其实已经提前做过一些功课。</p><p>知道首次备案需要准备哪些信息，也知道资料不合适时有可能被退回、要求修改，甚至重新提交。</p><p>所以到了真正提交备案的时候，我并没有一种“交上去以后等结果就好”的轻松感。</p><p>反而有点忐忑。</p><p>会担心自己是不是哪里填错了。</p><p>会想网站名称、主体信息、域名信息是不是都符合要求。</p><p>也会担心某个不起眼的地方，最后变成被退回的理由。</p><p>于是那几天，我其实经常会去看消息。</p><p>看看有没有新的短信。</p><p>看看邮箱里有没有通知。</p><p>（我甚至特地为手机卡充了50块钱以防止突然欠费了接不到电话）</p><p>也会忍不住打开备案进度页面，再确认一次现在到底走到哪一步了。</p><p>哪怕明知道审核本来就需要时间，也还是会偶尔想：</p><p>“不会真的被驳回吧？”</p><p>现在回头看，这种担心多少有点像第一次交一份自己很在意的申请。</p><p>理性上知道，资料没问题就继续等。</p><p>但感情上还是会反复确认，生怕哪里突然出了岔子。</p><p>那几天里，我一边等备案结果，一边继续在本地调整网站的细节。</p><p>偶尔改改页面，写点文章，再搞一搞我感兴趣的其他项目。</p><p>偶尔再看看服务器，研究研究要做的事情。</p></section>
<section><h1>9 月 14 日，备案通过<a href="#9-月-14-日备案通过"><span>#</span></a></h1><p>这一天，我的备案终于通过了。</p><p>时间比我想象的短太多了，也没有接到电话，只有一条短信通知。</p><p>看到通过消息的时候，我第一反应其实不是“终于可以继续配置了”。</p><p>而是：</p><p>我不是在做梦吧，怎么这么顺利？</p><p>那种一直悬着一点点的心情，终于放了下来。</p><p>也是从这一天开始，这个网站才正式从“准备上线”，进入“真正上线”的阶段。</p></section>
<section><h1>真正开始解析域名<a href="#真正开始解析域名"><span>#</span></a></h1><p>备案通过之后，我做的第一件事，就是把域名真正解析起来。</p><p>这时候之前提前了解过的 DNS 概念终于派上了用场。</p><p>最开始需要弄明白的，是域名到底应该指向哪里。</p><p>如果直接访问源站，就需要让相应的解析记录指向服务器；如果后面接入 CDN，则还会涉及 CNAME。</p><p>我开始学着去看解析记录，而不是只把它当成一个需要填空的表单。</p><p>当解析结果不对时，我也知道可以先检查记录是不是填错了，再用 <code>nslookup</code> 之类的方式看看域名现在到底解析到了哪里。</p><p>这和之前那种“为什么打不开”的感觉已经不太一样了。</p><p>因为我开始能顺着问题往下找。</p><p>先看域名有没有解析。</p><p>解析对了，再看后面的服务。</p><p>而不是一上来就怀疑整个网站都坏了。</p></section>
<section><h1>接入 CDN<a href="#接入-cdn"><span>#</span></a></h1><p>接下来就是 CDN。</p><p>之前我已经知道 CDN 大概是做什么的，但真正接入时，还是有一套实际流程要走。</p><p>先在 CDN 控制台里添加加速域名，再配置相应的源站IP。</p><p>然后根据 CDN 提供的地址，把域名解析改成对应的 CNAME，同时<strong>停用源站的 A 记录，让域名指向 CDN 服务器</strong>。</p><p>接入以后，我也开始碰到一些很典型的问题。</p><p>比如网站已经更新了，但访问时看到的还是旧内容。</p><p>一开始很容易怀疑是不是部署失败了。</p><p>后来才知道， CDN 的缓存不是实时的，需要一定时间来刷新。</p><p>我会先确认服务器上的文件是不是已经更新，再去判断是不是 CDN 还缓存着旧资源。</p><p>如果确认是缓存问题，就去刷新缓存，再重新访问。</p><p>这种问题解决过一次以后，我知道了：</p><p>“源站已经更新”和“用户已经看到更新”之间，并不总是等号。</p></section>
<section><h1>HTTPS 和证书<a href="#https-和证书"><span>#</span></a></h1><p>HTTPS 也是类似的过程。</p><p>以前我只是知道浏览器地址栏里有一把小锁，知道它代表连接更安全。</p><p>真正轮到自己配置时，我才开始理解证书到底放在哪里、CDN 和源站分别要怎么处理。</p><p>这一部分如果出了问题，我现在会优先检查几个和 HTTPS 本身直接相关的地方：</p><ol>
<li>证书有没有覆盖当前访问的域名；</li>
<li>CDN 端有没有正确启用 HTTPS；</li>
<li>CDN 的回源协议是不是和源站配置匹配。</li>
</ol><p>这样一来，问题就不会再变成一句很抽象的“网站打不开”。</p><p>至少我知道，这一节该查的是证书和 HTTPS，而不是把 DNS、CDN、服务器从头到尾重新检查一遍。</p><p>第一次自己处理这些事情时，还是需要一项一项核对。</p><p>但也正是在这种核对里，我慢慢发现：很多问题其实并没有想象中那么“玄学”，只是需要先把范围缩小。</p></section>
<section><h1>本地能跑，放到服务器上却不一定<a href="#本地能跑放到服务器上却不一定"><span>#</span></a></h1><p>还有一类问题也很典型。</p><p>本地运行一切正常，放到服务器以后却出了各种错误。</p><p>这时候我就不会再从域名、CDN 那一头重新查一遍，而是把范围直接缩小到服务器本身。</p><p>我现在通常会按这样一套顺序来：</p><ol>
<li>先确认 Web 服务有没有正常运行；</li>
<li>再检查 Nginx 配置文件有没有写错；</li>
<li>然后确认 80 和 443 端口有没有正常开放；</li>
<li>再看看服务器防火墙是不是把请求挡住了；</li>
<li>如果还是没有头绪，就直接去看日志。</li>
</ol><p>这些东西以前对我来说都很陌生。</p><p>但每解决一个问题，它们就不再只是一个名词。</p><p>比起最开始看到错误页面就完全不知道该从哪里下手，现在至少我已经知道：如果问题已经定位到服务器这一侧，就应该只查服务器这一侧真正相关的东西。</p><p>这种“先缩小范围，再去找原因”的思路，比把整条链路反复重查一遍有效得多。</p></section>
<section><h1>网站终于真的上线了<a href="#网站终于真的上线了"><span>#</span></a></h1><p>等域名解析、CDN、HTTPS、缓存这些事情都处理好以后，我又重新从头检查了一遍。</p><p>域名能不能正常访问。</p><p>HTTPS 是否正常。</p><p>静态资源有没有加载失败。</p><p>CDN 有没有正常回源。</p><p>页面更新以后缓存能不能正确刷新。</p><p>这些东西一项一项确认下来以后，我终于可以很确定地说：</p><p>这个网站真的上线了。</p><p>它不再只是本地预览。</p><p>不再只是服务器上的一堆文件。</p><p>它真正变成了一个可以通过域名被访问到的网站。</p><p>回头看，从开始写本地页面，到买域名、选服务器、了解 DNS 和 CDN，再到提交备案、等待审核，最后真正完成解析和上线，其实也没有经历什么特别轰轰烈烈的事情。</p><p>大多数时候，不过是遇到一个问题，就去查资料、看文档、试一个方法。</p><p>可能成功，也可能失败。</p><p>失败了，那就继续查资料，再换一个思路；解决了，就继续往前。</p><p><a></a></p></section>
<section><h1>一些“教程”？<a href="#一些教程"><span>#</span></a></h1><p>看到这儿，如果你和我一样，也想要从零开始搭建一个属于自己的网站，希望下面的流程可以对你有所帮助。</p><div><div><div></div><div>Note</div></div><div><p>本文适用于<strong>中国大陆服务器 + ICP 备案 + CDN + HTTPS</strong>这一类比较常见的个人网站部署方式。</p><p>不同云服务商、域名注册商、CDN 厂商和服务器系统的控制台名称可能不同，但整体思路基本一致。</p><p>如果使用境外服务器，通常不需要走与中国大陆服务器完全相同的 ICP 备案流程，后续步骤也会有所不同。</p></div></div><section><h2>1. 先把网站在本地跑通<a href="#1-先把网站在本地跑通"><span>#</span></a></h2><p>在购买服务器之前，建议先保证网站在本地已经可以正常运行。</p><p>至少确认：</p><ul>
<li>页面可以正常打开；</li>
<li>路由或页面跳转正常；</li>
<li>CSS、JavaScript、图片等静态资源路径没有问题；</li>
<li>构建命令可以正常执行；</li>
<li>如果是静态站点生成器，能够正常生成最终部署文件。</li>
</ul><p>如果使用 Astro、Vite 一类工具，还需要区分“开发环境能运行”和“生产构建能通过”这两件事。</p><p>例如开发模式下运行正常，并不代表执行生产构建后不会出现路径、资源引用或环境变量问题。</p><div><div><div></div><div>Tip</div></div><div><p>在上传服务器之前，最好先在本地完整执行一次生产构建，并检查最终产物。</p><p>这样可以尽量避免把“代码本身的问题”和“服务器部署的问题”混在一起排查。</p></div></div></section><section><h2>2. 购买域名并完成实名认证<a href="#2-购买域名并完成实名认证"><span>#</span></a></h2><p>确定域名后，先完成购买和实名认证。</p><p>如果你打算在国内备案，可以去诸如腾讯云、阿里云等处购买域名。</p><p>如果你的域名是在境外注册商处购买的，通常需要转入国内注册商。</p><p>购买时建议提前确认：</p><ul>
<li>域名后缀是否符合自己的用途；</li>
<li>域名是否方便记忆；</li>
<li>是否准备长期续费；</li>
</ul><div><div><div></div><div>Tip</div></div><div><p>建议去一些网站上提前检查一下这个域名有没有“黑历史”。</p></div></div><p>如果以后准备使用多个子域名，也可以提前规划。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>example.com</span></div></div><div><div><div>2</div></div><div><span>www.example.com</span></div></div><div><div><div>3</div></div><div><span>blog.example.com</span></div></div><div><div><div>4</div></div><div><span>api.example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不同子域名可以分别承担不同用途。</p><section><h3>常见问题<a href="#常见问题"><span>#</span></a></h3><p><strong>域名买下来后为什么不能直接访问网站？</strong></p><p>因为购买域名只相当于获得了这个名称的使用权，还需要通过 DNS 解析告诉网络“这个域名应该指向哪里”。</p><p><strong>域名实名认证和 ICP 备案是一回事吗？</strong></p><p>不是。</p><p>域名实名认证属于域名本身的实名管理；ICP 备案则是网站使用中国大陆服务器时涉及的另一套备案流程。</p></section></section><section><h2>3. 选择服务器：国内还是国外<a href="#3-选择服务器国内还是国外"><span>#</span></a></h2><p>服务器位置会直接影响后面的流程。</p><section><h3>中国大陆服务器<a href="#中国大陆服务器"><span>#</span></a></h3><p>优点通常包括：</p><ul>
<li>国内访问延迟较低；</li>
<li>访问线路相对更稳定；</li>
<li>更适合主要面向国内用户的网站。</li>
</ul><p>需要考虑：</p><ul>
<li>ICP 备案；</li>
<li>域名、备案主体、接入商等信息的一致性；</li>
<li>后续 CDN 服务可能也会要求域名已备案。</li>
</ul></section><section><h3>境外服务器<a href="#境外服务器"><span>#</span></a></h3><p>通常不需要走中国大陆服务器同样的备案流程，上线速度更快，但国内访问体验可能受到机房位置、线路质量等因素影响。</p><p>在决定好服务器位置后，可以进行配置的选择。选择时不必只看 CPU 和内存，还应同时考虑：</p><ul>
<li>带宽；</li>
<li>月流量；</li>
<li>硬盘；</li>
<li>线路；</li>
<li>是否方便升级；</li>
<li>是否支持快照或备份。</li>
</ul><p>对于静态个人网站，配置通常不需要一开始就买得很高。</p><p>CDN 可以承担大量静态资源分发，因此源站本身未必需要很大的公网带宽。</p><div><div><div></div><div>Tip</div></div><div><p>要区分开“专用带宽”和“峰值带宽”。</p><p>一般来说，2M、3M、4M这样的都是“专用带宽”，但也有像是200M这样的“峰值带宽”，并且这两者的月付似乎还差不多。</p><p>“专用带宽”虽然带宽较小，往往还有月流量限制，但是胜在稳定，你可以在任何时候访问到自己的服务器。</p><p>“峰值带宽”则是多台服务器共享带宽。首先，不太可能跑满这个带宽；其次，在高峰期，你需要和你的“邻居”争抢带宽，你很有可能无法访问自己的服务器。</p><p>对个人博客、静态站点这类更重视稳定访问而不是大吞吐量的场景，“专用带宽”通常更合适；如果业务更看重突发带宽、大量下载，则可以考虑“峰值带宽”。</p></div></div></section></section><section><h2>4. 购买服务器并完成最基础的初始化<a href="#4-购买服务器并完成最基础的初始化"><span>#</span></a></h2><p>购买服务器后，先完成基础设置。</p><p>通常包括：</p><ol>
<li>选择或安装操作系统；</li>
<li>设置登录方式；</li>
<li>更新系统软件包；</li>
<li>安装 Web 服务；</li>
<li>配置防火墙或安全组；</li>
<li>上传网站文件；</li>
<li>确认网站在源站本身可以正常访问。</li>
</ol><p>以常见 Linux 服务器为例，通常会使用 Nginx 提供静态文件。</p><p>这时不要急着接 CDN，先确认源站本身是正常的。</p><div><div><div></div><div>Tip</div></div><div><p>关于操作系统，Windows Server操作起来显然更简单，但是图形界面会占用大量的服务器性能，一般更推荐使用Linux系统。</p></div></div><section><h3>应优先检查的几个地方<a href="#应优先检查的几个地方"><span>#</span></a></h3><ul>
<li>Nginx 是否已经启动；</li>
<li>网站根目录是否配置正确；</li>
<li>文件权限是否允许 Nginx 读取；</li>
<li>80/443 端口是否开放；</li>
<li>云平台安全组是否放行相应端口；</li>
<li>本机防火墙是否拦截请求。</li>
</ul></section><section><h3>常见坑：安全组开了，但还是访问不了<a href="#常见坑安全组开了但还是访问不了"><span>#</span></a></h3><p>云服务器通常可能同时存在两层限制：</p><ul>
<li>云平台安全组；</li>
<li>操作系统自己的防火墙。</li>
</ul><p>只配置其中一层并不一定够。</p><p>另外，Nginx 没有启动、监听地址不正确，也会造成“端口明明放行了，但网站还是打不开”。</p></section></section><section><h2>5. 使用中国大陆服务器时，先完成 ICP 备案<a href="#5-使用中国大陆服务器时先完成-icp-备案"><span>#</span></a></h2><p>如果网站使用中国大陆服务器，需要按照接入商提供的流程完成 ICP 备案。</p><p>通常会涉及：</p><ul>
<li>主体信息；</li>
<li>域名信息；</li>
<li>网站名称；</li>
<li>网站服务内容；</li>
<li>负责人信息；</li>
<li>身份核验；</li>
<li>接入商初审；</li>
<li>管局审核。</li>
</ul><p>具体要求以当时的接入商和当地通信管理部门要求为准。</p><p>备案前最好仔细检查：</p><ul>
<li>域名是否已经完成实名认证；</li>
<li>域名实名信息是否与备案主体一致；</li>
<li>备案主体信息是否填写准确；</li>
<li>网站名称是否符合要求；</li>
<li>手机号是否能够正常接听电话和接收短信。</li>
</ul><div><div><div></div><div>Warning</div></div><div><p>不同地区、不同接入商的备案流程可能存在差异，建议专门查询一下。</p><p>备案期间通常应按照接入商和当地通信管理局要求，保持网站处于未开通或不可访问状态。</p><p>备案通过后，请及时在网站底部悬挂你的ICP备案号。网站开站后，再进行公安备案。</p></div></div><section><h3>常见坑：因为信息不一致被退回<a href="#常见坑因为信息不一致被退回"><span>#</span></a></h3><p>常见原因包括：</p><ul>
<li>域名实名信息与备案主体不一致；</li>
<li>联系方式填写错误；</li>
<li>网站名称或服务内容不符合要求；</li>
<li>材料照片或核验信息不符合规范。</li>
</ul><p>如果被退回，一般不要急着重新提交，先根据退回原因逐项修改。</p></section></section><section><h2>6. 备案通过后配置 DNS<a href="#6-备案通过后配置-dns"><span>#</span></a></h2><p>备案通过后，就可以开始正式配置域名解析。</p><p>如果直接访问源站，常见做法是添加 A 记录：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>主机记录：@</span></div></div><div><div><div>2</div></div><div><span>记录类型：A</span></div></div><div><div><div>3</div></div><div><span>记录值：服务器公网 IP</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果还需要 <code>www</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>主机记录：www</span></div></div><div><div><div>2</div></div><div><span>记录类型：A</span></div></div><div><div><div>3</div></div><div><span>记录值：服务器公网 IP</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果准备接入 CDN，则最终通常会使用 CDN 提供的 CNAME，而不是长期直接指向源站 IP。</p><section><h3>如何检查解析是否已经生效<a href="#如何检查解析是否已经生效"><span>#</span></a></h3><p>可以使用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nslookup</span><span> </span><span>example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ping</span><span> </span><span>example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不过需要注意，<code>ping</code> 只能辅助判断域名解析结果，并不能证明网站服务一定正常。</p><p>更可靠的思路是分别确认：</p><ol>
<li>域名有没有解析到预期目标；</li>
<li>目标服务器有没有正常提供 Web 服务。</li>
</ol></section><section><h3>常见坑：刚修改解析就反复刷新<a href="#常见坑刚修改解析就反复刷新"><span>#</span></a></h3><p>DNS 记录修改后不一定立即在所有地方生效。</p><p>这是因为：</p><ul>
<li>DNS 有缓存；</li>
<li>解析记录存在 TTL；</li>
<li>本地运营商 DNS 也可能有缓存。</li>
</ul><p>如果控制台配置确认无误，但本地仍然解析到旧地址，可以稍等一段时间再测试。</p></section></section><section><h2>7. 接入 CDN<a href="#7-接入-cdn"><span>#</span></a></h2><p>接入 CDN 的典型流程通常是：</p><ol>
<li>在 CDN 控制台添加加速域名；</li>
<li>填写源站地址；</li>
<li>等待 CDN 创建加速配置；</li>
<li>获取 CDN 提供的 CNAME；</li>
<li>回到 DNS 控制台；</li>
<li>将对应域名改为 CNAME 记录；</li>
<li>停用与该主机名冲突的 A 记录；</li>
<li>等待 DNS 生效；</li>
<li>检查 CDN 是否开始正常回源。</li>
</ol><p>一个简化后的访问链路大致是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>域名</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>DNS</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>CDN 节点</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>源站服务器</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用户命中缓存时，资源直接由 CDN 节点返回；只有未命中缓存或缓存失效时，CDN 才需要向源站请求资源。</p><section><h3>常见坑：A 记录和 CNAME 同时存在<a href="#常见坑a-记录和-cname-同时存在"><span>#</span></a></h3><p>同一个主机记录下，A 记录和 CNAME 往往不能按预期同时使用。</p><p>接入 CDN 后，应根据 CDN 服务商的要求调整记录，避免旧的 A 记录继续让流量绕过 CDN。</p></section><section><h3>常见坑：CDN 已接入，但访问的还是旧页面<a href="#常见坑cdn-已接入但访问的还是旧页面"><span>#</span></a></h3><p>优先按下面的顺序检查：</p><ol>
<li>源站文件是否真的已经更新；</li>
<li>CDN 是否仍然缓存旧文件；</li>
<li>是否需要主动刷新 CDN 缓存；</li>
<li>浏览器是否还保留旧缓存。</li>
</ol><p>不要一看到旧页面就立即重新部署。</p><p>很多时候部署本身没有问题，只是缓存尚未更新。</p></section></section><section><h2>8. 配置 HTTPS<a href="#8-配置-https"><span>#</span></a></h2><p>HTTPS 通常涉及两层：</p><ul>
<li>CDN 对用户提供 HTTPS；</li>
<li>CDN 回源时与源站通信。</li>
</ul><p>因此需要分别确认：</p><ol>
<li>CDN 上是否已经配置有效证书；</li>
<li>证书是否包含当前域名；</li>
<li>源站是否支持 HTTPS；</li>
<li>CDN 的回源协议是 HTTP 还是 HTTPS；</li>
<li>回源协议是否与源站实际配置一致。</li>
</ol><section><h3>常见坑：证书已经配置，但浏览器仍然报错<a href="#常见坑证书已经配置但浏览器仍然报错"><span>#</span></a></h3><p>可以重点检查：</p><ul>
<li>证书是否过期；</li>
<li>证书域名是否匹配；</li>
<li>是否漏掉某个子域名；</li>
<li>CDN 是否仍在使用旧证书；</li>
<li>HTTP 是否已经正确跳转 HTTPS。</li>
</ul></section><section><h3>常见坑：CDN 开 HTTPS 后出现 502 或回源失败<a href="#常见坑cdn-开-https-后出现-502-或回源失败"><span>#</span></a></h3><p>这类情况可能不是用户到 CDN 的 HTTPS 有问题，而是 CDN 到源站的回源协议不匹配。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CDN 回源方式：HTTPS</span></div></div><div><div><div>2</div></div><div><span>源站实际只监听：HTTP</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>就可能导致回源失败。</p><p>排查时要区分：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户 → CDN</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>和：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CDN → 源站</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是两段不同的连接。</p></section></section><section><h2>9. 检查缓存策略<a href="#9-检查缓存策略"><span>#</span></a></h2><p>静态网站接入 CDN 后，缓存策略会直接影响更新是否及时。</p><p>一般来说：</p><ul>
<li>带哈希值的 JS/CSS 文件可以缓存较长时间；</li>
<li>HTML 页面通常不适合无限期缓存；</li>
<li>经常变化的接口不应使用和静态资源相同的缓存策略。</li>
</ul><section><h3>常见坑：页面已经更新，但 CDN 一直显示旧版本<a href="#常见坑页面已经更新但-cdn-一直显示旧版本"><span>#</span></a></h3><p>可以依次检查：</p><ol>
<li>构建产物是否更新；</li>
<li>源站文件是否更新；</li>
<li>CDN 缓存是否刷新；</li>
<li>浏览器缓存是否清理。</li>
</ol><p>如果每次更新网站都必须手动清空所有缓存，通常说明缓存策略还有优化空间。</p></section></section><section><h2>10. 做一次完整的上线检查<a href="#10-做一次完整的上线检查"><span>#</span></a></h2><p>正式上线前，建议按用户真实访问路径完整检查一遍。</p><section><h3>域名<a href="#域名"><span>#</span></a></h3><ul>
<li>根域名是否正常；</li>
<li><code>www</code> 或其他子域名是否按预期工作；</li>
<li>DNS 是否已经指向 CDN。</li>
</ul></section><section><h3>HTTPS<a href="#https"><span>#</span></a></h3><ul>
<li>浏览器是否显示安全连接；</li>
<li>HTTP 是否可以正确跳转 HTTPS；</li>
<li>证书是否覆盖所有需要的域名。</li>
</ul></section><section><h3>页面<a href="#页面"><span>#</span></a></h3><ul>
<li>首页是否正常；</li>
<li>文章页是否正常；</li>
<li>页面刷新后是否出现 404；</li>
<li>图片、字体、CSS、JavaScript 是否加载正常。</li>
</ul></section><section><h3>CDN<a href="#cdn"><span>#</span></a></h3><ul>
<li>是否正常命中 CDN；</li>
<li>源站是否可以正常回源；</li>
<li>更新后的页面是否能够正确刷新缓存。</li>
</ul></section><section><h3>服务器<a href="#服务器"><span>#</span></a></h3><ul>
<li>Nginx 是否正常运行；</li>
<li>CPU、内存和磁盘是否正常；</li>
<li>日志中是否有大量异常；</li>
<li>防火墙和安全组规则是否符合预期。</li>
</ul></section></section><section><h2>11. 出问题时，先判断“是哪一层”<a href="#11-出问题时先判断是哪一层"><span>#</span></a></h2><p>真正上线后，最有用的不是记住所有命令，而是先判断问题属于哪一层。</p><p>可以大致按下面的思路分类：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>域名完全解析不到</span></div></div><div><div><div>2</div></div><div><span>→ 检查 DNS</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>域名能解析，但网站打不开</span></div></div><div><div><div>5</div></div><div><span>→ 检查 CDN / 源站 / Web 服务</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>CDN 报回源错误</span></div></div><div><div><div>8</div></div><div><span>→ 检查源站地址、端口、回源协议</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>HTTPS 报错</span></div></div><div><div><div>11</div></div><div><span>→ 检查证书和 HTTPS 配置</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>页面可以打开，但内容还是旧的</span></div></div><div><div><div>14</div></div><div><span>→ 检查源站文件、CDN 缓存、浏览器缓存</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>本地正常，服务器异常</span></div></div><div><div><div>17</div></div><div><span>→ 检查 Nginx、端口、防火墙、权限和日志</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>把问题先分层，再去检查对应配置，通常比从头把整套环境重新检查一遍更有效。</p></section></section>
<section><h1>写在最后<a href="#写在最后"><span>#</span></a></h1><p>写到这里，我发现我为这个网站好像已经做了很多事情。</p><p>域名买了，服务器也有了。</p><p>备案通过了，DNS 配好了，CDN 接上了，HTTPS 也终于正常工作。</p><p>如果只看这些，似乎可以很有仪式感地说一句：</p><p>“好了，网站建完了。”</p><p>但真的是这样吗？</p><p>现在回头看，ICP备案通过更像是一道真正的起跑线。</p><p>在它之前，我一直在为“让这个网站能够正式上线”做准备。</p><p>目标很明确：</p><p><strong>先让它能够真正出现在互联网上。</strong></p><p>可等到备案真的通过以后，我才发现，前面折腾了这么久，其实只是解决了“能不能开始”的问题。</p><p>紧随其后的是：</p><p>域名应该怎么规划？</p><p>服务器以后还要不要跑其他东西？</p><p>网站后面要更新什么样的内容？</p><p>页面还想不想继续改？</p><p>今天觉得挺好看的设计，半年以后会不会又看不顺眼？</p><p>还有那些现在根本想不到、以后大概一定还会遇到的问题。</p><p>这么一想，“网站上线”这件事本身，好像也没有我原先想象中的那种终点感。</p><p>它更像是从“我正在搭一个网站”，变成了“我开始维护一个网站”。</p><p>前者总还能说出一个大概的完成标准：</p><p>页面写好了，备案通过了，域名能访问了——差不多就算完成。</p><p>可后者大概没有。</p><p>只要这个网站还在，我就总会想往里面再放一点东西。</p><p>写几篇新的文章。</p><p>改一处不太顺眼的样式。</p><p>加一个突然想到的小功能。</p><p>偶尔折腾一下服务器。</p><p>再顺手解决一个以前完全没遇到过的问题。</p><p>是啊，只要这个网站还在，我大概就还会不断遇到新的问题。</p><p>也许以后再回头看，一开始能让我查半天资料的问题，都只是一些再基础不过的东西；现在觉得有些棘手的东西，过一段时间之后，也可能变成随手就能处理的小事。</p><p>但我想，这并不会让现在这些经历变得没有意义。</p><p>正因为它们曾经真的把我难住过，我才会去查资料、看文档、试一个又一个方法，然后在解决问题的过程中，慢慢弄明白那些原本只是见过名字、却不知道究竟有什么用的东西。</p><p>所以现在再看，这些问题到底“基础不基础”，好像已经没有那么重要了。</p><p>更值得记下来的，是我怎么从完全不知道，到慢慢知道一点。</p><p>我并不是因为先学会了这些东西，才开始建站。</p><p>恰恰相反。</p><p>是因为决定要把这个网站做出来，我才被一个又一个具体的问题推着往前走。</p><p>原本我以为，建站就是把网页写出来，然后把它送到某个网站上。</p><p>现在，我又开始觉得：</p><p>把一个网站送上互联网，大概也只是第一步。</p><p>网站上线了，然后呢？</p><p>那大概才是这个网站真正开始的地方。</p><p>至于它以后究竟会变成什么样子——</p><p>说实话，我也不知道。</p><p>不过没关系。</p><p>反正才刚刚开始。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260913-git-branch-commands-guide/</id>
      <title type="text">Git Branch 常用内容</title>
      <published>2026-09-13T00:00:00.000Z</published>
      <updated>2026-09-13T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260913-git-branch-commands-guide/"/>
      <summary type="text">本文整理 Git 中常用的分支相关命令。</summary>
      <content type="html"><![CDATA[<section><h1>1. 快速查找<a href="#1-快速查找"><span>#</span></a></h1>

<table><thead><tr><th>分类</th><th>操作</th><th>常用命令</th></tr></thead><tbody><tr><td><strong>查看与比较</strong></td><td><a href="#view-branches">查看分支</a></td><td><code>git branch</code> / <code>-r</code> / <code>-a</code> / <code>-vv</code></td></tr><tr><td></td><td><a href="#view-graph">查看分支图</a></td><td><code>git log --graph --oneline --decorate --all</code></td></tr><tr><td></td><td><a href="#compare-commits">比较分支提交</a></td><td><code>git log A..B --oneline</code></td></tr><tr><td></td><td><a href="#compare-diff">比较文件差异</a></td><td><code>git diff A..B</code></td></tr><tr><td></td><td><a href="#merge-base">查看共同祖先</a></td><td><code>git merge-base A B</code></td></tr><tr><td></td><td><a href="#reflog">查看操作历史</a></td><td><code>git reflog</code></td></tr><tr><td><strong>本地分支管理</strong></td><td><a href="#create-branch">创建分支</a></td><td><code>git branch &lt;branch&gt;</code> / <code>git switch -c &lt;branch&gt;</code></td></tr><tr><td></td><td><a href="#create-from-commit">从指定提交创建分支</a></td><td><code>git branch &lt;branch&gt; &lt;commit&gt;</code></td></tr><tr><td></td><td><a href="#switch-branch">切换分支</a></td><td><code>git switch &lt;branch&gt;</code> / <code>git checkout &lt;branch&gt;</code></td></tr><tr><td></td><td><a href="#rename-branch">重命名分支</a></td><td><code>git branch -m ...</code></td></tr><tr><td></td><td><a href="#delete-local">删除本地分支</a></td><td><code>git branch -d &lt;branch&gt;</code> / <code>-D</code></td></tr><tr><td><strong>分支整合与历史调整</strong></td><td><a href="#merge-branch">合并分支</a></td><td><code>git merge &lt;branch&gt;</code> / <code>--no-ff</code></td></tr><tr><td></td><td><a href="#rebase">Rebase</a></td><td><code>git rebase &lt;branch&gt;</code></td></tr><tr><td></td><td><a href="#cherry-pick">Cherry-pick</a></td><td><code>git cherry-pick &lt;commit&gt;</code></td></tr><tr><td></td><td><a href="#reset">Reset</a></td><td><code>git reset --soft</code> / <code>--mixed</code> / <code>--hard</code></td></tr><tr><td><strong>远程仓库与同步</strong></td><td><a href="#remote-repo">管理远程仓库</a></td><td><code>git remote add</code> / <code>git remote -v</code></td></tr><tr><td></td><td><a href="#fetch">获取远程更新</a></td><td><code>git fetch &lt;remote&gt;</code></td></tr><tr><td></td><td><a href="#pull">拉取远程更新</a></td><td><code>git pull</code> / <code>git pull --rebase</code></td></tr><tr><td></td><td><a href="#push">推送分支</a></td><td><code>git push</code> / <code>git push -u</code></td></tr><tr><td></td><td><a href="#upstream">设置上游关系</a></td><td><code>git branch --set-upstream-to</code> / <code>--unset-upstream</code></td></tr><tr><td></td><td><a href="#delete-remote">删除远程分支</a></td><td><code>git push &lt;remote&gt; --delete &lt;branch&gt;</code></td></tr><tr><td><strong>过程控制</strong></td><td><a href="#merge-control">Merge 的继续与取消</a></td><td><code>git merge --continue</code> / <code>--abort</code></td></tr><tr><td></td><td><a href="#rebase-control">Rebase 的继续、跳过与取消</a></td><td><code>git rebase --continue</code> / <code>--skip</code> / <code>--abort</code></td></tr><tr><td></td><td><a href="#cherry-pick-control">Cherry-pick 的继续与取消</a></td><td><code>git cherry-pick --continue</code> / <code>--abort</code></td></tr><tr><td><strong>临时保存</strong></td><td><a href="#stash">Stash</a></td><td><code>git stash</code> / <code>push</code> / <code>list</code> / <code>pop</code></td></tr></tbody></table><div><div><div></div><div>Note</div></div><div><p>速查表用于快速定位命令。后文按照与表格相同的分类和顺序展开，便于从索引直接跳转到对应说明。</p></div></div><hr /></section>
<section><h1>2. 查看与比较<a href="#2-查看与比较"><span>#</span></a></h1><p>本节用于查看当前分支状态、比较不同分支，以及检查提交历史。</p><hr /><p><a></a></p><section><h2>查看分支<a href="#查看分支"><span>#</span></a></h2><section><h3><code>git branch</code><a href="#git-branch"><span>#</span></a></h3><p>查看本地分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>* master</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>dev</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>feature/login</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p><code>*</code> 表示当前所在分支。</p></div></div><hr /></section><section><h3><code>git branch -r</code><a href="#git-branch--r"><span>#</span></a></h3><p>查看远程跟踪分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-r</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin/master</span></div></div><div><div><div>2</div></div><div><span>origin/dev</span></div></div><div><div><div>3</div></div><div><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git branch -a</code><a href="#git-branch--a"><span>#</span></a></h3><p>查看本地分支和远程跟踪分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-a</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git branch -vv</code><a href="#git-branch--vv"><span>#</span></a></h3><p>查看本地分支、当前提交、上游跟踪关系及领先或落后状态。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-vv</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>* master  2bd887e8 [origin/master] Merge upstream/master</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>dev     a1234567 [origin/dev: ahead 2] test</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>查看当前分支跟踪哪个远程分支</li>
<li>查看本地分支相对远程分支是否领先或落后</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>查看分支图<a href="#查看分支图"><span>#</span></a></h2><section><h3><code>git log --graph --oneline --decorate --all</code><a href="#git-log---graph---oneline---decorate---all"><span>#</span></a></h3><p>以图形方式查看所有分支的提交历史。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--graph</span><span> </span><span>--oneline</span><span> </span><span>--decorate</span><span> </span><span>--all</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>*   2bd887e8 (HEAD -&gt; master) Merge upstream/master</span></div></div><div><div><div>2</div></div><div><span>|\</span></div></div><div><div><div>3</div></div><div><span>| * 567313c1 (upstream/master) chore: update dependencies</span></div></div><div><div><div>4</div></div><div><span>| * ffd881d0 fix: resolve issue</span></div></div><div><div><div>5</div></div><div><span>* | 2aea090d feat: add new feature</span></div></div><div><div><div>6</div></div><div><span>|/</span></div></div><div><div><div>7</div></div><div><span>* abcdef12 previous commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>参数含义：</p>

<table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>--graph</code></td><td>显示提交关系图</td></tr><tr><td><code>--oneline</code></td><td>每个提交显示为一行</td></tr><tr><td><code>--decorate</code></td><td>显示分支、标签和 HEAD</td></tr><tr><td><code>--all</code></td><td>显示所有本地和远程跟踪分支</td></tr></tbody></table><hr /><hr /><p><a></a></p></section></section><section><h2>比较两个分支的提交<a href="#比较两个分支的提交"><span>#</span></a></h2><section><h3><code>git log A..B --oneline</code><a href="#git-log-ab---oneline"><span>#</span></a></h3><p>查看 <code>B</code> 中存在、但 <code>A</code> 中不存在的提交。</p><p>例如：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>master..upstream/master</span><span> </span><span>--oneline</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>表示查看：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>upstream/master 有、master 没有的提交</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>反过来：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>upstream/master..master</span><span> </span><span>--oneline</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>表示查看：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>master 有、upstream/master 没有的提交</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>比较两个分支的文件差异<a href="#比较两个分支的文件差异"><span>#</span></a></h2><section><h3><code>git diff A..B</code><a href="#git-diff-ab"><span>#</span></a></h3><p>比较两个分支最终文件内容的差异。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>master..upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>查看两个分支在文件内容上的差异</li>
<li>合并前检查修改内容</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>查看共同祖先<a href="#查看共同祖先"><span>#</span></a></h2><section><h3><code>git merge-base &lt;branch1&gt; &lt;branch2&gt;</code><a href="#git-merge-base-branch1-branch2"><span>#</span></a></h3><p>查找两个分支最近的共同祖先提交。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge-base</span><span> </span><span>master</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>         </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>          </span></span><span>E---F</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>如果两条分支从 <code>C</code> 开始分叉，则命令返回 <code>C</code> 的 Commit ID。</p></div></div><hr /><hr /><p><a></a></p></section></section><section><h2>查看操作历史<a href="#查看操作历史"><span>#</span></a></h2><section><h3><code>git reflog</code><a href="#git-reflog"><span>#</span></a></h3><p>查看 HEAD 和本地分支指针近期移动记录。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reflog</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>a1b2c3d HEAD@{0}: merge feature</span></div></div><div><div><div>2</div></div><div><span>d4e5f6a HEAD@{1}: commit: update implementation</span></div></div><div><div><div>3</div></div><div><span>7a8b9c0 HEAD@{2}: checkout: moving from feature to main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>查找 Reset 前的提交</li>
<li>查找 Rebase 前的提交</li>
<li>查找误删除分支之前的位置</li>
</ul><hr /><hr /></section></section></section>
<section><h1>3. 本地分支管理<a href="#3-本地分支管理"><span>#</span></a></h1><p>本节用于创建、切换、重命名和删除本地分支。</p><hr /><p><a></a></p><section><h2>创建分支<a href="#创建分支"><span>#</span></a></h2><section><h3><code>git branch &lt;branch&gt;</code><a href="#git-branch-branch"><span>#</span></a></h3><p>从当前提交创建一个新分支，但不切换过去。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>HEAD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>master, dev</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>HEAD -&gt; master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git switch -c &lt;branch&gt;</code><a href="#git-switch--c-branch"><span>#</span></a></h3><p>创建分支并立即切换到该分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>-c</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>HEAD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>master, dev</span></div></div><div><div><div>4</div></div><div><span><span>          </span></span><span>↑</span></div></div><div><div><div>5</div></div><div><span><span>         </span></span><span>HEAD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等价于：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>dev</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git switch -c &lt;branch&gt; &lt;start-point&gt;</code><a href="#git-switch--c-branch-start-point"><span>#</span></a></h3><p>从指定提交或分支创建新分支并切换。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>-c</span><span> </span><span>feature</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>upstream/master</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>feature</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>HEAD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>从远程最新分支创建开发分支</li>
<li>从指定历史提交创建修复分支</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>从指定提交重新创建分支<a href="#从指定提交重新创建分支"><span>#</span></a></h2><section><h3><code>git branch &lt;branch&gt; &lt;commit&gt;</code><a href="#git-branch-branch-commit"><span>#</span></a></h3><p>从指定提交创建分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>recovery</span><span> </span><span>abc1234</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>abc1234</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>recovery</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>根据 <code>git reflog</code> 找回误删除的分支</li>
<li>从历史提交创建临时修复分支</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>切换分支<a href="#切换分支"><span>#</span></a></h2><section><h3><code>git switch &lt;branch&gt;</code><a href="#git-switch-branch"><span>#</span></a></h3><p>切换到已有分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>master, dev</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>HEAD -&gt; master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>master, dev</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>HEAD -&gt; dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git checkout &lt;branch&gt;</code><a href="#git-checkout-branch"><span>#</span></a></h3><p>旧版 Git 中常用的分支切换写法。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>效果与以下命令相同：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git checkout -b &lt;branch&gt;</code><a href="#git-checkout--b-branch"><span>#</span></a></h3><p>旧版 Git 中用于创建并切换分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>效果与以下命令相同：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>-c</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>重命名分支<a href="#重命名分支"><span>#</span></a></h2><section><h3><code>git branch -m &lt;new-name&gt;</code><a href="#git-branch--m-new-name"><span>#</span></a></h3><p>重命名当前分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-m</span><span> </span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>       </span></span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git branch -m &lt;old-name&gt; &lt;new-name&gt;</code><a href="#git-branch--m-old-name-new-name"><span>#</span></a></h3><p>重命名指定分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-m</span><span> </span><span>master</span><span> </span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>删除本地分支<a href="#删除本地分支"><span>#</span></a></h2><section><h3><code>git branch -d &lt;branch&gt;</code><a href="#git-branch--d-branch"><span>#</span></a></h3><p>安全删除本地分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-d</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果该分支存在尚未合并的提交，Git 通常会拒绝删除。</p><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑   ↑</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>master dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>该命令仅删除分支引用，不会创建新的提交。</p></div></div><hr /></section><section><h3><code>git branch -D &lt;branch&gt;</code><a href="#git-branch--d-branch-1"><span>#</span></a></h3><p>强制删除本地分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-D</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>明确不再需要该分支</li>
<li>分支中存在未合并提交，但仍需要强制删除</li>
</ul><div><div><div></div><div>Warning</div></div><div><p><code>git branch -D</code> 会忽略“分支尚未合并”的保护检查。执行前应确认该分支中的提交不再需要。</p></div></div><hr /><hr /></section></section></section>
<section><h1>4. 分支整合与历史调整<a href="#4-分支整合与历史调整"><span>#</span></a></h1><p>本节用于合并分支、重新应用提交，以及调整当前分支的提交位置。</p><hr /><p><a></a></p><section><h2>合并分支<a href="#合并分支"><span>#</span></a></h2><section><h3><code>git merge &lt;branch&gt;</code><a href="#git-merge-branch"><span>#</span></a></h3><p>将指定分支合并到当前分支。</p><p>例如：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>D---E</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>master -&gt; C</span></div></div><div><div><div>6</div></div><div><span>dev    -&gt; E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>普通合并后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C-------M</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>\         /</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>D-------E</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>master -&gt; M</span></div></div><div><div><div>6</div></div><div><span>dev    -&gt; E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中 <code>M</code> 为 Merge Commit。</p><p>适用场景：</p><ul>
<li>将功能分支合并到主分支</li>
<li>将上游分支更新合并到当前分支</li>
<li>保留分叉和合并历史</li>
</ul><hr /></section><section><h3>Fast-forward Merge<a href="#fast-forward-merge"><span>#</span></a></h3><p>如果当前分支之后没有新的独立提交，Git 可能直接移动分支指针。</p><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑       ↑</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>master    dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E</span></div></div><div><div><div>2</div></div><div><span><span>                </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>           </span></span><span>master, dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>Fast-forward 合并不会生成 Merge Commit，只会移动当前分支指针。</p></div></div><hr /></section><section><h3><code>git merge --no-ff &lt;branch&gt;</code><a href="#git-merge---no-ff-branch"><span>#</span></a></h3><p>即使可以 Fast-forward，也强制创建 Merge Commit。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑       ↑</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>master    dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C-------M</span></div></div><div><div><div>2</div></div><div><span><span>         </span></span><span>\     /</span></div></div><div><div><div>3</div></div><div><span><span>          </span></span><span>D---E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>需要明确保留一次分支合并记录</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>Rebase<a href="#rebase"><span>#</span></a></h2><section><h3><code>git rebase &lt;branch&gt;</code><a href="#git-rebase-branch"><span>#</span></a></h3><p>将当前分支上的提交重新应用到指定分支之后。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>dev</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>D---E</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>master -&gt; C</span></div></div><div><div><div>6</div></div><div><span>dev    -&gt; E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D'---E'</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑         ↑</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>master      dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Important</div></div><div><p>Rebase 会重新创建被移动的提交，因此这些提交的 Commit ID 会发生变化。</p></div></div><p>适用场景：</p><ul>
<li>将开发分支更新到较新的基础分支之上</li>
<li>保持提交历史为线性结构</li>
</ul><div><div><div></div><div>Warning</div></div><div><p>Rebase 会改写提交历史。对已经推送并被多人使用的公共分支执行 Rebase 时应谨慎。</p></div></div><hr /><hr /><p><a></a></p></section></section><section><h2>Cherry-pick<a href="#cherry-pick"><span>#</span></a></h2><section><h3><code>git cherry-pick &lt;commit&gt;</code><a href="#git-cherry-pick-commit"><span>#</span></a></h3><p>将指定提交的修改应用到当前分支，并创建一个新的提交。</p><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>D---E---F</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>master -&gt; C</span></div></div><div><div><div>6</div></div><div><span>dev    -&gt; F</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>cherry-pick</span><span> </span><span>&lt;E的CommitID&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---E'</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>D---E---F</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>只需要另一个分支中的一个或几个提交</li>
<li>将某个 Bug 修复单独应用到其他分支</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>Reset<a href="#reset"><span>#</span></a></h2><section><h3><code>git reset --soft &lt;commit&gt;</code><a href="#git-reset---soft-commit"><span>#</span></a></h3><p>移动当前分支到指定提交，并保留之后的修改在暂存区。</p><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--soft</span><span> </span><span>HEAD~1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>C</code> 中的修改仍处于暂存状态。</p><p>适用场景：</p><ul>
<li>撤销最近一次提交，但保留已暂存的修改</li>
</ul><hr /></section><section><h3><code>git reset --mixed &lt;commit&gt;</code><a href="#git-reset---mixed-commit"><span>#</span></a></h3><p>移动当前分支到指定提交，并将之后的修改保留在工作区。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--mixed</span><span> </span><span>HEAD~1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以省略 <code>--mixed</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>HEAD~1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后，修改不会处于暂存状态。</p><hr /></section><section><h3><code>git reset --hard &lt;commit&gt;</code><a href="#git-reset---hard-commit"><span>#</span></a></h3><p>移动当前分支，并同步修改暂存区和工作区。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--hard</span><span> </span><span>HEAD~1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E</span></div></div><div><div><div>2</div></div><div><span><span>                </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>             </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--hard</span><span> </span><span>C</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Caution</div></div><div><p><code>git reset --hard</code> 会同步重置暂存区和工作区。未提交的修改可能被直接丢弃。</p></div></div><hr /><hr /></section></section></section>
<section><h1>5. 远程仓库与同步<a href="#5-远程仓库与同步"><span>#</span></a></h1><p>本节用于管理远程仓库、获取和推送更新，以及设置分支跟踪关系。</p><hr /><p><a></a></p><section><h2>管理远程仓库<a href="#管理远程仓库"><span>#</span></a></h2><section><h3><code>git remote add &lt;name&gt; &lt;url&gt;</code><a href="#git-remote-add-name-url"><span>#</span></a></h3><p>添加新的远程仓库。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>add</span><span> </span><span>upstream</span><span> </span><span>https://github.com/official/project.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常见名称：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin    自己克隆或推送使用的远程仓库</span></div></div><div><div><div>2</div></div><div><span>upstream  上游仓库</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git remote -v</code><a href="#git-remote--v"><span>#</span></a></h3><p>查看所有远程仓库地址。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin    git@github.com:user/project.git (fetch)</span></div></div><div><div><div>2</div></div><div><span>origin    git@github.com:user/project.git (push)</span></div></div><div><div><div>3</div></div><div><span>upstream  git@github.com:official/project.git (fetch)</span></div></div><div><div><div>4</div></div><div><span>upstream  git@github.com:official/project.git (push)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>获取远程更新<a href="#获取远程更新"><span>#</span></a></h2><section><h3><code>git fetch &lt;remote&gt;</code><a href="#git-fetch-remote"><span>#</span></a></h3><p>从指定远程仓库获取最新提交和引用，但不自动修改当前本地分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地：</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>A---B---C</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>↑</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>upstream/master</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>远程：</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>A---B---C---D---E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E</span></div></div><div><div><div>2</div></div><div><span><span>                </span></span><span>↑</span></div></div><div><div><div>3</div></div><div><span><span>       </span></span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p><code>git fetch</code> 只更新远程跟踪引用，本地 <code>master</code> 不会自动移动。</p></div></div><p>适用场景：</p><ul>
<li>获取远程最新状态</li>
<li>合并或 Rebase 前先检查远程更新</li>
</ul><hr /><hr /><p><a></a></p></section></section><section><h2>Pull<a href="#pull"><span>#</span></a></h2><section><h3><code>git pull</code><a href="#git-pull"><span>#</span></a></h3><p>获取当前分支上游远程的更新，并合并到当前分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>默认配置下，<code>git pull</code> 通常相当于先执行 <code>git fetch</code>，再执行 <code>git merge</code>。</p></div></div><p>对应命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git pull &lt;remote&gt; &lt;branch&gt;</code><a href="#git-pull-remote-branch"><span>#</span></a></h3><p>从指定远程分支获取并合并。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>origin</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git pull --rebase</code><a href="#git-pull---rebase"><span>#</span></a></h3><p>获取远程更新后，以 Rebase 方式更新当前分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>--rebase</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D</span></div></div><div><div><div>2</div></div><div><span><span>        </span></span><span>\   ↑</span></div></div><div><div><div>3</div></div><div><span><span>         </span></span><span>\ origin/master</span></div></div><div><div><div>4</div></div><div><span><span>          </span></span><span>E---F</span></div></div><div><div><div>5</div></div><div><span><span>              </span></span><span>↑</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A---B---C---D---E'---F'</span></div></div><div><div><div>2</div></div><div><span><span>            </span></span><span>↑         ↑</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>origin/master master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>推送分支<a href="#推送分支"><span>#</span></a></h2><section><h3><code>git push &lt;remote&gt; &lt;branch&gt;</code><a href="#git-push-remote-branch"><span>#</span></a></h3><p>将本地分支推送到远程仓库。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git push -u &lt;remote&gt; &lt;branch&gt;</code><a href="#git-push--u-remote-branch"><span>#</span></a></h3><p>首次推送分支，并建立上游跟踪关系。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>-u</span><span> </span><span>origin</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>建立：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地 dev</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>origin/dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Tip</div></div><div><p>建立上游跟踪关系后，后续通常可以省略远程仓库名和分支名，直接执行：</p></div></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>设置和取消上游跟踪关系<a href="#设置和取消上游跟踪关系"><span>#</span></a></h2><section><h3><code>git branch --set-upstream-to=&lt;remote&gt;/&lt;branch&gt;</code><a href="#git-branch---set-upstream-toremotebranch"><span>#</span></a></h3><p>为当前分支设置上游分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>--set-upstream-to=origin/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>指定本地分支：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>--set-upstream-to=origin/master</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git branch --unset-upstream</code><a href="#git-branch---unset-upstream"><span>#</span></a></h3><p>取消当前分支的上游跟踪关系。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>--unset-upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>删除远程分支<a href="#删除远程分支"><span>#</span></a></h2><section><h3><code>git push &lt;remote&gt; --delete &lt;branch&gt;</code><a href="#git-push-remote---delete-branch"><span>#</span></a></h3><p>删除远程仓库中的分支。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>--delete</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行前：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin/master</span></div></div><div><div><div>2</div></div><div><span>origin/dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /></section></section></section>
<section><h1>6. 过程控制<a href="#6-过程控制"><span>#</span></a></h1><p>本节汇总 Merge、Rebase 和 Cherry-pick 在冲突或中断状态下的继续、跳过和取消命令。</p><hr /><p><a></a></p><section><h2>Merge 的继续与取消<a href="#merge-的继续与取消"><span>#</span></a></h2><section><h3><code>git merge --abort</code><a href="#git-merge---abort"><span>#</span></a></h3><p>取消当前正在进行的合并。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>合并发生冲突</li>
<li>不准备继续本次合并</li>
</ul><hr /></section><section><h3><code>git merge --continue</code><a href="#git-merge---continue"><span>#</span></a></h3><p>冲突解决并暂存后继续合并。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>&lt;file&gt;</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--continue</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>部分情况下也可以直接执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>完成合并。</p><hr /><hr /><p><a></a></p></section></section><section><h2>Rebase 的继续、跳过与取消<a href="#rebase-的继续跳过与取消"><span>#</span></a></h2><section><h3><code>git rebase --continue</code><a href="#git-rebase---continue"><span>#</span></a></h3><p>解决冲突并暂存后继续 Rebase。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>&lt;file&gt;</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>--continue</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git rebase --skip</code><a href="#git-rebase---skip"><span>#</span></a></h3><p>跳过当前无法应用的提交。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>--skip</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p><code>git rebase --skip</code> 会跳过当前提交，该提交中的修改不会被应用到新的历史中。</p></div></div><hr /></section><section><h3><code>git rebase --abort</code><a href="#git-rebase---abort"><span>#</span></a></h3><p>取消整个 Rebase 操作，并恢复到开始前的状态。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /><p><a></a></p></section></section><section><h2>Cherry-pick 的继续与取消<a href="#cherry-pick-的继续与取消"><span>#</span></a></h2><section><h3><code>git cherry-pick --continue</code><a href="#git-cherry-pick---continue"><span>#</span></a></h3><p>解决冲突后继续 Cherry-pick。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>&lt;file&gt;</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>cherry-pick</span><span> </span><span>--continue</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git cherry-pick --abort</code><a href="#git-cherry-pick---abort"><span>#</span></a></h3><p>取消当前 Cherry-pick。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>cherry-pick</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><hr /></section></section></section>
<section><h1>7. 临时保存<a href="#7-临时保存"><span>#</span></a></h1><p>本节用于在切换分支、合并或 Rebase 前临时保存尚未提交的工作区修改。</p><hr /><p><a></a></p><section><h2>Stash<a href="#stash"><span>#</span></a></h2><section><h3><code>git stash</code><a href="#git-stash"><span>#</span></a></h3><p>临时保存当前工作区修改。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>stash</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>适用场景：</p><ul>
<li>当前存在未提交修改，但需要切换分支</li>
<li>合并或 Rebase 前临时清理工作区</li>
</ul><hr /></section><section><h3><code>git stash push -m &lt;message&gt;</code><a href="#git-stash-push--m-message"><span>#</span></a></h3><p>保存修改并添加说明。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>push</span><span> </span><span>-m</span><span> </span><span>"WIP: temporary changes"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git stash list</code><a href="#git-stash-list"><span>#</span></a></h3><p>查看 Stash 列表。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>list</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3><code>git stash pop</code><a href="#git-stash-pop"><span>#</span></a></h3><p>恢复最近一次 Stash，并将其从 Stash 列表移除。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>pop</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260913-git-commit-message-guide/</id>
      <title type="text">Git Commit 常用内容</title>
      <published>2026-09-13T00:00:00.000Z</published>
      <updated>2026-09-13T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260913-git-commit-message-guide/"/>
      <summary type="text">本文记录一些提交commit时会使用到的内容。</summary>
      <content type="html"><![CDATA[<section><h1>1. 快速查找<a href="#1-快速查找"><span>#</span></a></h1><p>如果只是想快速判断某次提交应该使用哪一种 <code>type</code>，可以先查看下面的速查内容；如果需要理解每个字段的含义、适用边界和完整写法，再继续阅读后面的详细说明。</p><div><div><div></div><div>Tip</div></div><div><p>对日常提交来说，先判断“这次修改主要做了什么”通常就能确定 <code>type</code>。<code>scope</code> 和 <code>description</code> 则是在此基础上进一步说明“改了哪里”和“具体改了什么”。</p></div></div><section><h2>常用类型速查表<a href="#常用类型速查表"><span>#</span></a></h2>

<table><thead><tr><th>类型</th><th>用途</th><th>常见场景</th></tr></thead><tbody><tr><td><code>feat</code></td><td>新增功能</td><td>新页面、新 API、新功能、新配置</td></tr><tr><td><code>fix</code></td><td>修复问题</td><td>Bug、异常、错误行为、兼容性问题</td></tr><tr><td><code>docs</code></td><td>文档修改</td><td>README、使用说明、API 文档</td></tr><tr><td><code>style</code></td><td>代码格式</td><td>缩进、空格、换行、格式化</td></tr><tr><td><code>refactor</code></td><td>代码重构</td><td>重组结构、提取方法、清理重复代码</td></tr><tr><td><code>perf</code></td><td>性能优化</td><td>降低耗时、减少请求、优化内存</td></tr><tr><td><code>test</code></td><td>测试相关</td><td>单元测试、集成测试、测试数据</td></tr><tr><td><code>build</code></td><td>构建相关</td><td>依赖、Gradle、Maven、Webpack、Vite</td></tr><tr><td><code>ci</code></td><td>CI/CD</td><td>GitHub Actions、自动测试、自动发布</td></tr><tr><td><code>chore</code></td><td>日常维护</td><td>清理文件、配置整理、维护任务</td></tr><tr><td><code>revert</code></td><td>撤销修改</td><td>撤销之前的 Commit</td></tr></tbody></table></section><section><h2>快速判断<a href="#快速判断"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>新增功能              -&gt; feat</span></div></div><div><div><div>2</div></div><div><span>修复错误              -&gt; fix</span></div></div><div><div><div>3</div></div><div><span>只改文档              -&gt; docs</span></div></div><div><div><div>4</div></div><div><span>只改代码格式          -&gt; style</span></div></div><div><div><div>5</div></div><div><span>重构但行为基本不变    -&gt; refactor</span></div></div><div><div><div>6</div></div><div><span>提高性能              -&gt; perf</span></div></div><div><div><div>7</div></div><div><span>修改测试              -&gt; test</span></div></div><div><div><div>8</div></div><div><span>修改依赖或构建        -&gt; build</span></div></div><div><div><div>9</div></div><div><span>修改 CI/CD            -&gt; ci</span></div></div><div><div><div>10</div></div><div><span>其他维护工作          -&gt; chore</span></div></div><div><div><div>11</div></div><div><span>撤销已有提交          -&gt; revert</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>速查表适合快速选择提交类型，但同一种修改在不同项目规范中可能存在细微差异。确定 <code>type</code> 后，可以继续参考后文对应章节了解具体适用场景。</p></div></div><hr /></section></section>
<section><h1>2. Commit Message 的基本结构<a href="#2-commit-message-的基本结构"><span>#</span></a></h1><p>在快速确定提交类型之后，可以进一步了解一条 Commit Message 是如何组成的。推荐格式仍然是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;type&gt;(&lt;scope&gt;): &lt;description&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中 <code>type</code> 决定提交类别，<code>scope</code> 说明修改范围，<code>description</code> 则概括具体变更。后续章节会依次展开这三个部分，并补充 Body、Footer 和 Breaking Change。</p><p>其中：</p><ul>
<li><code>type</code>：本次提交的类型。</li>
<li><code>scope</code>：可选，表示本次修改影响的模块、功能或范围。</li>
<li><code>description</code>：简短描述本次提交做了什么。</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(auth): add password reset flow</span></div></div><div><div><div>2</div></div><div><span>fix(ui): prevent modal from overflowing viewport</span></div></div><div><div><div>3</div></div><div><span>docs(readme): update installation instructions</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果修改范围比较明确，可以写 <code>scope</code>；如果没有必要，也可以省略：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat: add user search</span></div></div><div><div><div>2</div></div><div><span>fix: handle empty response</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>3. <code>type</code>：提交类型<a href="#3-type提交类型"><span>#</span></a></h1><p><code>type</code> 用于概括这次提交属于哪一类修改，是 Commit Message 中最主要的分类信息。</p><section><h2><code>feat</code>：新增功能<a href="#feat新增功能"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景"><span>#</span></a></h3><p>用于加入新的用户可见功能、能力或行为。</p><p>例如：</p><ul>
<li>新增一个页面。</li>
<li>新增一个按钮或操作入口。</li>
<li>新增一个 API。</li>
<li>新增一种配置选项。</li>
<li>新增搜索、筛选、导出等功能。</li>
<li>新增程序原本不具备的能力。</li>
</ul></section><section><h3>示例<a href="#示例"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat: add user search</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(profile): add avatar upload</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(api): support pagination</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>如果只是调整已有功能的内部实现，而没有新增功能，通常更适合使用 <code>refactor</code>。</p></div></div><hr /></section></section><section><h2><code>fix</code>：修复问题<a href="#fix修复问题"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-1"><span>#</span></a></h3><p>用于修复 Bug、异常行为或不符合预期的问题。</p><p>例如：</p><ul>
<li>修复程序崩溃。</li>
<li>修复数据显示错误。</li>
<li>修复按钮无法点击。</li>
<li>修复布局异常。</li>
<li>修复边界条件处理错误。</li>
<li>修复兼容性问题。</li>
<li>修复空值、越界等异常。</li>
</ul></section><section><h3>示例<a href="#示例-1"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix: handle empty response</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(ui): prevent modal from overflowing viewport</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(parser): handle malformed input</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2><code>docs</code>：文档修改<a href="#docs文档修改"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-2"><span>#</span></a></h3><p>仅修改文档，不涉及程序功能。</p><p>例如：</p><ul>
<li>修改 <code>README.md</code>。</li>
<li>补充安装说明。</li>
<li>更新使用方法。</li>
<li>修改 API 文档。</li>
<li>修正注释中的说明性错误。</li>
<li>增加开发文档或贡献指南。</li>
</ul></section><section><h3>示例<a href="#示例-2"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs: update installation guide</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs(readme): add configuration examples</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs(api): document pagination parameters</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>注意<a href="#注意"><span>#</span></a></h3><div><div><div></div><div>Important</div></div><div><p>如果修改的是代码注释，同时还修改了实际代码逻辑，应根据主要修改内容选择其他类型。</p></div></div><hr /></section></section><section><h2><code>style</code>：代码格式调整<a href="#style代码格式调整"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-3"><span>#</span></a></h3><p>用于不影响程序逻辑的代码风格修改。</p><p>例如：</p><ul>
<li>调整缩进。</li>
<li>修改空格。</li>
<li>调整换行。</li>
<li>修改引号风格。</li>
<li>统一分号。</li>
<li>调整代码格式。</li>
<li>使用格式化工具重新格式化代码。</li>
</ul></section><section><h3>示例<a href="#示例-3"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>style: format source files</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>style(ui): normalize indentation</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>注意<a href="#注意-1"><span>#</span></a></h3><div><div><div></div><div>Warning</div></div><div><p>这里的 <code>style</code> 一般指<strong>代码格式</strong>，不是 UI 视觉样式。</p><p>如果修改了页面颜色、布局、动画等实际 UI 效果，通常更适合使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(ui): add dark mode</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(ui): correct button alignment</span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div><hr /></section></section><section><h2><code>refactor</code>：代码重构<a href="#refactor代码重构"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-4"><span>#</span></a></h3><p>修改代码结构或实现方式，但原则上不增加新功能，也不修复特定 Bug。</p><p>例如：</p><ul>
<li>拆分大型函数。</li>
<li>提取公共方法。</li>
<li>重组类结构。</li>
<li>重命名内部变量或方法。</li>
<li>调整模块结构。</li>
<li>替换等价实现。</li>
<li>清理重复代码。</li>
</ul></section><section><h3>示例<a href="#示例-4"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>refactor: simplify configuration loading</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>refactor(auth): extract token validation logic</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>refactor(api): reorganize request handlers</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>判断方法<a href="#判断方法"><span>#</span></a></h3><div><div><div></div><div>Tip</div></div><div><p>如果修改前后用户看到的功能基本不变，而主要变化发生在内部代码结构，通常可以使用 <code>refactor</code>。</p></div></div><hr /></section></section><section><h2><code>perf</code>：性能优化<a href="#perf性能优化"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-5"><span>#</span></a></h3><p>用于提升运行速度、降低资源消耗或提高效率。</p><p>例如：</p><ul>
<li>减少重复计算。</li>
<li>优化数据库查询。</li>
<li>减少网络请求。</li>
<li>减少内存占用。</li>
<li>优化缓存。</li>
<li>加快页面渲染。</li>
<li>优化大型数据处理性能。</li>
</ul></section><section><h3>示例<a href="#示例-5"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>perf: reduce unnecessary database queries</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>perf(cache): avoid repeated file reads</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>perf(render): reduce redundant component updates</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2><code>test</code>：测试相关修改<a href="#test测试相关修改"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-6"><span>#</span></a></h3><p>用于添加、修改或整理测试代码。</p><p>例如：</p><ul>
<li>新增单元测试。</li>
<li>新增集成测试。</li>
<li>补充边界条件测试。</li>
<li>修复测试代码。</li>
<li>更新测试数据。</li>
<li>调整测试配置。</li>
</ul></section><section><h3>示例<a href="#示例-6"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>test: add validation tests</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>test(auth): cover expired token cases</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>test(api): update response fixtures</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2><code>build</code>：构建系统或依赖修改<a href="#build构建系统或依赖修改"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-7"><span>#</span></a></h3><p>用于影响项目构建过程、依赖管理或打包方式的修改。</p><p>例如：</p><ul>
<li>修改 <code>package.json</code>。</li>
<li>修改 Maven 或 Gradle 配置。</li>
<li>添加、升级或删除依赖。</li>
<li>修改构建脚本。</li>
<li>修改打包配置。</li>
<li>修改编译参数。</li>
<li>调整 Vite、Webpack 等构建工具配置。</li>
</ul></section><section><h3>示例<a href="#示例-7"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>build: update project dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>build(gradle): update JavaFX dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>build(vite): adjust production bundle settings</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>与 <code>chore</code> 的区别<a href="#与-chore-的区别"><span>#</span></a></h3><div><div><div></div><div>Tip</div></div><div><p>如果修改直接影响编译、打包、依赖或构建流程，优先使用 <code>build</code>。</p></div></div><hr /></section></section><section><h2><code>ci</code>：持续集成配置修改<a href="#ci持续集成配置修改"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-8"><span>#</span></a></h3><p>用于 CI/CD 自动化流程。</p><p>例如：</p><ul>
<li>修改 GitHub Actions。</li>
<li>修改 GitLab CI。</li>
<li>调整自动测试流程。</li>
<li>修改自动构建流程。</li>
<li>修改自动发布流程。</li>
<li>修改流水线环境变量。</li>
</ul></section><section><h3>示例<a href="#示例-8"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ci: add automated test workflow</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ci(github): update release workflow</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ci: run tests on multiple Java versions</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2><code>chore</code>：日常维护<a href="#chore日常维护"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-9"><span>#</span></a></h3><p>用于不属于功能、修复、文档、测试、构建等主要分类的维护性修改。</p><p>例如：</p><ul>
<li>清理无用文件。</li>
<li>更新 <code>.gitignore</code>。</li>
<li>修改开发辅助脚本。</li>
<li>整理目录。</li>
<li>更新非核心配置。</li>
<li>清理临时内容。</li>
<li>修改项目维护相关设置。</li>
</ul></section><section><h3>示例<a href="#示例-9"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore: remove unused files</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore: update gitignore</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore(config): clean up development settings</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>注意<a href="#注意-2"><span>#</span></a></h3><div><div><div></div><div>Warning</div></div><div><p>不要把所有无法判断的提交都写成 <code>chore</code>。如果有更具体的类型，应优先使用更具体的类型。</p></div></div><hr /></section></section><section><h2><code>revert</code>：撤销提交<a href="#revert撤销提交"><span>#</span></a></h2><section><h3>适用场景<a href="#适用场景-10"><span>#</span></a></h3><p>用于撤销之前的一次或多次提交。</p><p>通常由：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>revert</span><span> </span><span>&lt;commit&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>产生。</p></section><section><h3>示例<a href="#示例-10"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>revert: revert "feat: add user search"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>revert: restore previous authentication behavior</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section><section><h2>其他常见类型<a href="#其他常见类型"><span>#</span></a></h2><p>不同项目可能会根据需要扩展 Commit 类型。</p><section><h3><code>security</code><a href="#security"><span>#</span></a></h3><p>用于安全相关修改。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>security: sanitize user input</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>security(auth): tighten token validation</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不过 Conventional Commits 并没有强制定义 <code>security</code>，因此是否使用取决于项目规范。</p><hr /></section><section><h3><code>deps</code><a href="#deps"><span>#</span></a></h3><p>有些项目会单独使用 <code>deps</code> 表示依赖修改。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>deps: update runtime dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但更多项目会统一使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>build: update dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore(deps): update dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>建议在同一个项目中保持一致。</p><hr /></section><section><h3><code>release</code><a href="#release"><span>#</span></a></h3><p>有些项目使用 <code>release</code> 表示版本发布。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>release: v2.1.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也有项目使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore(release): v2.1.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section></section>
<section><h1>4. <code>scope</code>：修改范围<a href="#4-scope修改范围"><span>#</span></a></h1><p><code>scope</code> 用于表示本次修改影响的范围。</p><p>格式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type(scope): description</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(auth): add password reset</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(ui): correct sidebar spacing</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs(api): update request examples</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常见 Scope 可以是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>auth</span></div></div><div><div><div>2</div></div><div><span>api</span></div></div><div><div><div>3</div></div><div><span>ui</span></div></div><div><div><div>4</div></div><div><span>config</span></div></div><div><div><div>5</div></div><div><span>database</span></div></div><div><div><div>6</div></div><div><span>router</span></div></div><div><div><div>7</div></div><div><span>editor</span></div></div><div><div><div>8</div></div><div><span>logger</span></div></div><div><div><div>9</div></div><div><span>cache</span></div></div><div><div><div>10</div></div><div><span>installer</span></div></div><div><div><div>11</div></div><div><span>updater</span></div></div><div><div><div>12</div></div><div><span>docs</span></div></div><div><div><div>13</div></div><div><span>tests</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>Scope 不需要过度细化。如果修改范围比较广，可以直接省略。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>refactor: reorganize project structure</span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div><hr /></section>
<section><h1>5. <code>description</code>：简短描述<a href="#5-description简短描述"><span>#</span></a></h1><p>Commit 的第一行应该尽量简短地说明：</p><blockquote><p>这次提交做了什么？</p></blockquote><p>推荐：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix: handle missing configuration</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不推荐：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix: bug</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不推荐：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>update code</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不推荐：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>change some files</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Tip</div></div><div><p>好的 Description 通常应该简洁、具体，能够说明修改目的，并且不依赖打开代码也能理解大致内容。</p></div></div><hr /></section>
<section><h1>6. Commit 的扩展信息<a href="#6-commit-的扩展信息"><span>#</span></a></h1><p>当第一行不足以完整说明一次提交时，可以继续使用 Body、Footer 或 Breaking Change。</p><section><h2>Commit Body<a href="#commit-body"><span>#</span></a></h2><p>如果第一行无法说明清楚，可以添加正文。</p><p>格式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type(scope): short description</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Detailed explanation of the change.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(cache): avoid loading stale configuration</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Always reload the configuration after the source file changes.</span></div></div><div><div><div>4</div></div><div><span>This prevents outdated values from being reused.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Tip</div></div><div><p>Body 适合说明为什么要修改、原来的问题是什么、采用了什么处理方式，以及是否存在需要注意的行为变化。</p></div></div></section><section><h2>Footer<a href="#footer"><span>#</span></a></h2><p>Footer 通常用于记录：</p><ul>
<li>Issue。</li>
<li>Pull Request。</li>
<li>Breaking Change。</li>
<li>其他关联信息。</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(api): handle invalid query parameters</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Closes #128</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Refs #128</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常见写法包括：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Closes #123</span></div></div><div><div><div>2</div></div><div><span>Fixes #123</span></div></div><div><div><div>3</div></div><div><span>Refs #123</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>具体支持情况取决于 Git 托管平台。</p></section><section><h2>Breaking Change<a href="#breaking-change"><span>#</span></a></h2><p>如果提交包含不兼容修改，应明确标识。</p><section><h3>写法一：使用 <code>!</code><a href="#写法一使用-"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(api)!: change response format</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>写法二：Footer<a href="#写法二footer"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(api): change response format</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>BREAKING CHANGE: the `data` field is now an object instead of an array.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以两种方式同时使用。</p><p>常见 Breaking Change：</p><ul>
<li>删除原有 API。</li>
<li>修改公开方法参数。</li>
<li>修改配置文件格式。</li>
<li>修改数据库结构且无法向后兼容。</li>
<li>修改 CLI 参数。</li>
<li>修改外部依赖的调用方式。</li>
</ul><hr /></section></section></section>
<section><h1>7. 如何组织一次 Commit<a href="#7-如何组织一次-commit"><span>#</span></a></h1><div><div><div></div><div>Important</div></div><div><p>最好让一次 Commit 只完成一个相对独立的目的。</p></div></div><p>例如，不建议：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat: add search and fix login and update docs</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更推荐拆分成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(search): add keyword filtering</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(auth): handle invalid login state</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs: update usage guide</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样可以让提交历史更加清晰，也方便：</p><ul>
<li>Code Review。</li>
<li><code>git bisect</code>。</li>
<li><code>git revert</code>。</li>
<li>Cherry-pick。</li>
<li>查看历史。</li>
</ul><hr /></section>
<section><h1>8. 常见场景与完整示例<a href="#8-常见场景与完整示例"><span>#</span></a></h1><section><h2>常见场景<a href="#常见场景"><span>#</span></a></h2><section><h3>新增登录页面<a href="#新增登录页面"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(auth): add login page</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>修复登录失败后没有错误提示<a href="#修复登录失败后没有错误提示"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(auth): show error message after failed login</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>调整按钮间距<a href="#调整按钮间距"><span>#</span></a></h3><p>如果这是实际 UI 行为或视觉修正：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fix(ui): correct button spacing</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果只是代码格式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>style(ui): format button styles</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>重构登录逻辑<a href="#重构登录逻辑"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>refactor(auth): extract login validation</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>优化图片加载速度<a href="#优化图片加载速度"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>perf(image): reduce unnecessary image requests</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>更新 README<a href="#更新-readme"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>docs(readme): update setup instructions</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>升级依赖<a href="#升级依赖"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>build: update dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者项目统一采用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore(deps): update dependencies</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>添加测试<a href="#添加测试"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>test(auth): add login validation tests</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>修改 GitHub Actions<a href="#修改-github-actions"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ci(github): update build workflow</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>删除无用文件<a href="#删除无用文件"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>chore: remove unused assets</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>一个比较完整的 Commit<a href="#一个比较完整的-commit"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(search): add advanced filter options</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Add filters for category, date and status.</span></div></div><div><div><div>4</div></div><div><span>Preserve selected filters when navigating back to the result page.</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>Closes #245</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>Breaking Change 示例<a href="#breaking-change-示例"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>feat(config)!: replace legacy configuration format</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Replace the old flat configuration structure with grouped sections.</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>BREAKING CHANGE: existing configuration files must be migrated to the new format.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>9. 基本原则<a href="#9-基本原则"><span>#</span></a></h1><p>前面的速查表解决的是“应该选哪个类型”，而这里更关注如何让提交历史长期保持清晰、可读和可维护。</p><p>Commit Message 最重要的不是追求复杂，而是：</p><div><div><div></div><div>Important</div></div><div><p><strong>让别人仅通过提交历史，就能快速理解每次修改做了什么。</strong></p></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/study/260912-git-upstream-merge-practice/</id>
      <title type="text">一次 Git 上游分支合并记录</title>
      <published>2026-09-12T00:00:00.000Z</published>
      <updated>2026-09-12T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/study/260912-git-upstream-merge-practice/"/>
      <summary type="text">记录一次将 upstream/master 合并到个人 master 的过程。</summary>
      <content type="html"><![CDATA[<p>今天我比较完整地走了一遍 Git 在真实项目里的使用流程。</p>
<p>我本身并不是第一次写代码，也不是第一次见到 <code>git add</code>、<code>git commit</code>、<code>git push</code> 这些命令。以前开发时也会把代码提交到 GitHub，但那种使用方式更多只是把 Git 当成一个“保存版本、上传代码”的工具。</p>
<p>这一次不同。</p>
<p>我维护的是一个基于上游开源项目修改出来的个人版本。官方仓库还在不断更新，而我的版本里又已经加入了自己的页面、配置等定制内容。于是问题从“怎么把代码上传到 GitHub”变成了：</p>
<blockquote><p><strong>怎样长期维护一个会持续跟随官方更新、同时又保留自己修改历史的分支？</strong></p></blockquote>
<p>这篇文章不是一份操作日志，而是一次真实项目中的版本管理复盘。相比“我先输入了什么命令”，我更想记录的是：<strong>为什么这样做、冲突为什么会出现，以及我最后是依据什么做出取舍。</strong></p>
<p>如果你只是想看相关用法的总结，可以直接跳转到 <a href="#git-knowledge-summary">第十二节</a>。</p>
<hr />
<section><h1>先明确目标：我要保留的是“分叉过、又重新合并”的真实历史<a href="#先明确目标我要保留的是分叉过又重新合并的真实历史"><span>#</span></a></h1><p>我的项目基于 Firefly 继续修改。</p><p>现在我同时维护两个远程。<code>origin</code> 指向我自己的 GitHub 仓库；<code>upstream</code> 指向官方 Firefly 仓库。对我而言，<code>origin/master</code> 代表我的远程版本，而 <code>upstream/master</code> 代表官方的最新版本。</p><p>可以用下面的命令查看当前远程仓库配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里有一个以前没有认真区分的点：</p><blockquote><p><code>origin</code> 和 <code>upstream</code> 并不是 Git 的特殊关键字，它们本质上只是远程仓库的名字。</p></blockquote><p><code>origin</code> 只是 <code>git clone</code> 时 Git 通常默认给源仓库取的名字；而 <code>upstream</code> 是我为了区分“官方上游”而人为添加的名称。</p><p>真正让我做选择的是分支已经发生了分叉：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>                </span></span><span>官方继续开发</span></div></div><div><div><div>2</div></div><div><span><span>               </span></span><span>D──E──F</span></div></div><div><div><div>3</div></div><div><span><span>              </span></span><span>/</span></div></div><div><div><div>4</div></div><div><span>A──B──C──────</span></div></div><div><div><div>5</div></div><div><span><span>       </span></span><span>\</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>X──Y</span></div></div><div><div><div>7</div></div><div><span><span>           </span></span><span>↑</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>我的修改</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我的 <code>master</code> 有自己的提交，官方 <code>upstream/master</code> 也在继续向前。此时我面对的是 <code>merge</code> 和 <code>rebase</code> 两种不同思路。</p><p>如果使用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我的提交会被重新放到官方最新提交之后，历史会变得非常线性：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C──D──E──F──X'──Y'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种方式很整洁，但它会重写我原来的提交。</p><p>而我这次真正想保留的是另一件事：</p><blockquote><p>官方代码和我的个人修改曾经沿着两条路线发展，随后我在某个时间点把它们重新合并到一起。</p></blockquote><p>所以我最终选择：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我特意保留 <code>--no-ff</code>，因为我希望历史中明确留下一个 Merge Commit，让以后回看 Git Graph 时能直接看到：</p><ul>
<li>官方继续开发的轨迹；</li>
<li>我的个性化修改轨迹；</li>
<li>两条历史重新汇合的位置。</li>
</ul><p>这对我来说，比一条“看起来一直都很整齐”的线性历史更符合实际。</p><hr /></section>
<section><h1>真正值得记录的不是命令，而是三类不同的冲突<a href="#真正值得记录的不是命令而是三类不同的冲突"><span>#</span></a></h1><p>这次合并过程中，Git 自动处理了不少文件，但以下几类冲突需要我自己判断：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>src/components/layout/Footer.astro</span></div></div><div><div><div>2</div></div><div><span>src/config/FooterConfig.html</span></div></div><div><div><div>3</div></div><div><span>src/config/footerConfig.ts</span></div></div><div><div><div>4</div></div><div><span>src/config/navBarConfig.ts</span></div></div><div><div><div>5</div></div><div><span>src/config/profileConfig.ts</span></div></div><div><div><div>6</div></div><div><span>src/config/siteConfig.ts</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中大部分是 <code>CONFLICT (content)</code>，而 <code>footerConfig.ts</code> 是 <code>CONFLICT (modify/delete)</code>。</p><p>刚看到 <code>Automatic merge failed</code> 时，很容易下意识把它理解成“合并失败了”。但实际更准确的理解是：</p><blockquote><p>Git 已经完成了能自动完成的部分；剩下这些地方存在多个合理结果，所以必须由开发者自己决定。</p></blockquote><p>因此冲突并不是 Git 出错，而是 Git 在把决策权交回来。</p><p>我主要通过 VS Code Merge Editor 来处理这些问题。这里最重要的三个概念是：</p><ul>
<li><code>Current</code>：我执行 merge 时所在的 <code>master</code></li>
<li><code>Incoming</code>：正在被合入的 <code>upstream/master</code></li>
<li><code>Result</code>：最终真正会进入 Merge Commit 的结果</li>
</ul><p>这次最重要的认识之一，就是 <strong>Result 并不一定等于 Current，也不一定等于 Incoming</strong>。</p><p>很多真实冲突的正确答案其实是：</p><blockquote><p><strong>保留一部分 Current，吸收一部分 Incoming，再做必要的人工调整。</strong></p></blockquote><hr /></section>
<section><h1>第一类冲突：个人配置不应该被“官方默认值”覆盖<a href="#第一类冲突个人配置不应该被官方默认值覆盖"><span>#</span></a></h1><p><code>siteConfig.ts</code> 和 <code>profileConfig.ts</code> 都属于非常典型的个人配置冲突。</p><p>例如 <code>siteConfig.ts</code> 中，官方新版的标题是 <code>title: "Firefly"</code>，而我的版本是 <code>title: "Nightbug Inn"</code>。导航栏标题也类似，官方是 <code>title: "Firefly Blog"</code>，而我的是 <code>title: "Nightbug Inn"</code>。</p><p>除此之外，还有：</p><ul>
<li>site URL</li>
<li>subtitle</li>
<li>description</li>
<li>keywords</li>
<li>themeColor</li>
<li>页面开关</li>
<li>文章列表布局</li>
<li>Bilibili UID</li>
<li>站点开始时间</li>
</ul><p>这些内容的本质并不是“官方代码更新了，而我的代码旧了”，而是：</p><blockquote><p><strong>官方默认配置和我的实际站点配置不同。</strong></p></blockquote><p>同理，<code>profileConfig.ts</code> 中官方保留的是官方作者的头像、名称、签名和链接，而我的版本则是自己的资料。</p><p>因此这两类文件最后都保留了我的旧配置：</p><ul>
<li><code>siteConfig_new.ts == siteConfig_old.ts</code></li>
<li><code>profileConfig_new.ts == profileConfig_old.ts</code></li>
</ul><p>这里真正重要的判断原则是：</p><blockquote><p><strong>先看这个文件在项目里的职责，再决定哪一侧才是正确来源。</strong></p></blockquote><p>如果这是“个人配置”，那官方版本即使更新得更晚，也并不意味着它更适合我的项目。</p><hr /></section>
<section><h1>第二类冲突：文本能合上，不代表逻辑一定正确<a href="#第二类冲突文本能合上不代表逻辑一定正确"><span>#</span></a></h1><p><code>navBarConfig.ts</code> 让我更直观地体会到了这一点。</p><p>我的版本里新增了自己的导航结构，例如：</p><ul>
<li>足迹</li>
<li>娱乐</li>
<li>关于本站</li>
</ul><p>GitHub 链接也改成了自己的仓库，还增加了 Bilibili 等个人链接。</p><p>官方新版则保留了自己的 GitHub / Gitee 配置。</p><p>这类对象数组在 Merge Editor 里很容易出现一种危险情况：<strong>文本块看似合并成功，但对象内部的字段被错误拼接</strong>。</p><p>当时就出现过类似这样的组合：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>name</span><span>: </span><span>"Bilibili"</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>icon</span><span>: </span><span>"fa7-brands:gitee"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>从语法上看，它完全可能是合法的。</p><p>从 Git 的角度看，它也可能已经“没有冲突”。</p><p>但从逻辑上看，它显然是错误的。</p><p>所以这次我真正理解到：</p><blockquote><p><strong>Merge Editor 解决的是文本冲突，不是业务逻辑。</strong></p></blockquote><p>最终 <code>navBarConfig_new.ts == navBarConfig_old.ts</code>，因为我希望继续保留自己的导航结构。但更重要的是，我开始意识到：<strong>任何人工合并后的对象、数组、配置文件，都必须再做一次逻辑检查。</strong></p><hr /></section>
<section><h1>最关键的案例：Footer 不是“选哪一边”，而是一次真正的架构迁移<a href="#最关键的案例footer-不是选哪一边而是一次真正的架构迁移"><span>#</span></a></h1><p>这次最值得记录的其实不是前面的配置冲突，而是 Footer。</p><p>它同时涉及：</p><ul>
<li><code>FooterConfig.html</code></li>
<li><code>Footer.astro</code></li>
<li><code>footerConfig.ts</code></li>
</ul><p>这三者一起看，才能理解官方这次到底改了什么。</p><section><h2><code>FooterConfig.html</code>：官方只提供入口，而我已经真正使用了它<a href="#footerconfightml官方只提供入口而我已经真正使用了它"><span>#</span></a></h2><p>官方最新版的 <code>FooterConfig.html</code> 基本只是一个说明：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!--</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>你可以在这个文件中使用HTML内容自定义网站底部的footer</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>You can use html to customize the footer at the bottom of the website in this file.</span></div></div><div><div><div>4</div></div><div><span>--&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而我的版本已经真正使用了这个入口，里面有完整的：</p><ul>
<li>Footer 样式</li>
<li>徽章</li>
<li>Astro / Tailwind / Twikoo / Algolia 等信息</li>
<li>备案区域</li>
<li>站点运行时间</li>
<li>主题版本号</li>
<li><code>__THEME_VERSION__</code> 占位符</li>
<li>JavaScript 运行逻辑</li>
</ul><p>所以这部分最终自然保留我的版本：</p><p><code>FooterConfig_new.html == FooterConfig_old.html</code></p><p>但真正复杂的问题并不在 HTML 本身，而在它和 <code>Footer.astro</code> 的配合方式。</p></section><section><h2>官方已经移除了旧的 <code>footerConfig</code> 机制<a href="#官方已经移除了旧的-footerconfig-机制"><span>#</span></a></h2><p>我的旧 <code>Footer.astro</code> 中有：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> { </span><span>footerConfig</span><span>, </span><span>profileConfig</span><span> } </span><span>from</span><span> </span><span>"@/config"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>并且读取 Footer HTML 时还有：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> (</span><span>footerConfig</span><span>.</span></span><span>enable</span><span>) {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说，旧结构可以理解成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>footerConfig.ts</span></div></div><div><div><div>2</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>enable</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>Footer.astro</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>FooterConfig.html</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但官方新版已经移除了 <code>footerConfig</code>，变成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> { </span><span>profileConfig</span><span> } </span><span>from</span><span> </span><span>"@/config"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>并且 <code>Footer.astro</code> 直接读取 <code>FooterConfig.html</code>。</p><p>也就是说，官方已经把结构简化成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Footer.astro</span></div></div><div><div><div>2</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>直接读取</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>FooterConfig.html</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这就解释了为什么 <code>footerConfig.ts</code> 会出现 <code>modify/delete</code> 冲突：</p><ul>
<li>我的分支还在修改和使用它；</li>
<li>官方分支已经直接把它删除了。</li>
</ul><p>如果只看单个文件，很容易误以为这是一个“官方删了、我没删”的普通冲突。</p><p>但把三份 Footer 相关文件放在一起看，就能确认：</p><blockquote><p><strong>这不是随手删除，而是一次明确的架构重构。</strong></p></blockquote></section><section><h2>我又不能简单接受官方 <code>Footer.astro</code><a href="#我又不能简单接受官方-footerastro"><span>#</span></a></h2><p>问题在于，我自己的旧版 Footer 还有一个官方没有的功能。</p><p>我会读取 <code>package.json</code> 中的版本号，然后把 <code>FooterConfig.html</code> 里的 <code>__THEME_VERSION__</code> 替换成真实主题版本：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>customFooterHtml</span><span> </span></span><span>=</span><span><span> </span><span>customFooterHtml</span><span>.</span></span><span>replace</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>/__THEME_VERSION__/</span><span>g</span><span>,</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>themeVersion</span><span>,</span></div></div><div><div><div>4</div></div><div><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果我完整接受官方 <code>Footer.astro</code>，这个功能会丢失。</p><p>但如果我完整保留自己的旧版，又会继续依赖已经被官方淘汰的 <code>footerConfig.ts</code>。</p><p>所以这里真正合理的结果不是“接受 Current”或“接受 Incoming”，而是：</p><blockquote><p><strong>采用官方的新架构，同时把我真正需要的功能迁移过去。</strong></p></blockquote></section><section><h2>最终 Footer 是怎么合出来的<a href="#最终-footer-是怎么合出来的"><span>#</span></a></h2><p>最终版做了几件事。</p><p><strong>接受官方的变化：</strong></p><ul>
<li>删除 <code>footerConfig</code> import</li>
<li>不再使用 <code>footerConfig.enable</code></li>
<li>直接读取 <code>FooterConfig.html</code></li>
<li>接受新版 Footer DOM 结构</li>
<li>采用更稳妥的异常处理</li>
</ul><p><strong>保留我的功能：</strong></p><ul>
<li>从 <code>package.json</code> 读取主题版本号</li>
<li>替换 <code>__THEME_VERSION__</code></li>
</ul><p>因此最终：</p><ul>
<li><code>Footer.astro</code> 不等于 old</li>
<li><code>Footer.astro</code> 也不等于 upstream</li>
<li>它是真正由两边变化融合得到的第三个版本</li>
</ul><p>这是这次最典型的三方合并案例。</p><hr /></section></section>
<section><h1><code>modify/delete</code> 冲突真正考验的是“是否理解重构意图”<a href="#modifydelete-冲突真正考验的是是否理解重构意图"><span>#</span></a></h1><p><code>footerConfig.ts</code> 的冲突类型是 <code>CONFLICT (modify/delete)</code>。</p><p>我的旧版还在修改它，官方新版则已经把整个文件删除。</p><p>Git 无法自动判断：</p><blockquote><p>到底应该保留我的修改，还是接受官方删除？</p></blockquote><p>一开始如果只从“我改过，所以不能丢”的角度出发，很容易选择保留。</p><p>但结合前面对 <code>Footer.astro</code> 的分析，我已经确认：</p><ul>
<li>官方确实重构了 Footer；</li>
<li><code>footerConfig.enable</code> 已经没有存在必要；</li>
<li>我的真实功能已经迁移到新架构；</li>
<li><code>FooterConfig.html</code> 仍然继续使用；</li>
<li>版本号注入逻辑也已经保留下来。</li>
</ul><p>这时删除 <code>footerConfig.ts</code> 才是合理的。</p><p>我执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>src/config/footerConfig.ts</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里的 <code>git rm</code> 不只是“删除文件”，在当前 merge 状态里，它同时意味着：</p><blockquote><p><strong>我选择“删除”作为这个 modify/delete 冲突的最终结果。</strong></p></blockquote><p>这让我第一次真正理解：冲突处理不是“保护自己改过的东西”，而是判断 <strong>哪些设计还应该继续存在</strong>。</p><hr /></section>
<section><h1>我最后形成了一套自己的冲突判断方法<a href="#我最后形成了一套自己的冲突判断方法"><span>#</span></a></h1><p>这次合并之后，我觉得真正有价值的不是记住“哪个按钮怎么点”，而是形成了一套更稳定的判断方式。</p><section><h3>1. 这是个人配置，还是公共逻辑？<a href="#1-这是个人配置还是公共逻辑"><span>#</span></a></h3><p>如果是个人配置，例如站点标题、头像、链接，那通常应该优先保留自己的内容。</p></section><section><h3>2. 官方是在改默认值，还是在重构架构？<a href="#2-官方是在改默认值还是在重构架构"><span>#</span></a></h3><p>如果只是默认值变化，不一定要跟。</p><p>如果是架构变化，就应该先理解重构目的。</p></section><section><h3>3. 我的旧代码里，真正需要保留的是“实现”，还是“功能”？<a href="#3-我的旧代码里真正需要保留的是实现还是功能"><span>#</span></a></h3><p>Footer 就是典型例子。</p><p>我真正需要保留的是：</p><blockquote><p>版本号自动注入功能</p></blockquote><p>而不是：</p><blockquote><p>旧的 <code>footerConfig.ts</code> 架构</p></blockquote></section><section><h3>4. Git 不报冲突，逻辑也可能错<a href="#4-git-不报冲突逻辑也可能错"><span>#</span></a></h3><p>对象、数组、配置文件尤其需要人工检查。</p></section><section><h3>5. Result 才是最终答案<a href="#5-result-才是最终答案"><span>#</span></a></h3><p><code>Current</code> 和 <code>Incoming</code> 都只是输入。</p><p>真正进入历史的是最后的 <code>Result</code>。</p><hr /></section></section>
<section><h1>这次使用到的工作流，其实可以压缩成很少几步<a href="#这次使用到的工作流其实可以压缩成很少几步"><span>#</span></a></h1><p>虽然这篇文章讨论了很多细节，但真正的上游同步流程其实很短。</p><p>先确认自己在正确分支：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>获取官方最新历史：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>开始合并：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>发生冲突后，用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认当前状态，逐个处理文件。</p><p>普通冲突解决后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>&lt;file&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果最终决定删除：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>&lt;file&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果方向完全错了，可以在提交 Merge Commit 之前：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>全部解决后，提交并推送：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>commit</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这部分命令本身其实不难。</p><p>真正难的是中间那一步：</p><blockquote><p><strong>理解冲突背后的代码意图。</strong></p></blockquote><hr /></section>
<section><h1>合并结束后，我才真正体会到 Git 历史的价值<a href="#合并结束后我才真正体会到-git-历史的价值"><span>#</span></a></h1><p>这次 Merge Commit 是 <code>dcaed5bc</code>。</p><p>它有两个 parent：</p><ul>
<li><code>dcaed5bc^1</code>：我 merge 前的 <code>master</code></li>
<li><code>dcaed5bc^2</code>：这次被合入的 <code>upstream/master</code></li>
</ul><p>也就是说，对于这次合并，我可以随时回到三个明确状态：</p><ul>
<li>old：我的旧版</li>
<li>upstream：官方新版</li>
<li>new：最终合并版</li>
</ul><p>这也是为什么后来即使我已经把工作区更新成最终版，仍然能把旧文件重新找出来。</p><p>例如查看我的旧版 Footer：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>dcaed5bc^1:src/components/layout/Footer.astro</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看官方当时的 Footer：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>dcaed5bc^2:src/components/layout/Footer.astro</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看最终版：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>dcaed5bc:src/components/layout/Footer.astro</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这件事让我真正意识到：</p><blockquote><p>Git 保存的不是“当前文件”，而是整个项目在不同提交节点上的历史快照。</p></blockquote><hr /></section>
<section><h1>worktree 让我把这段历史真正“摊开”来看<a href="#worktree-让我把这段历史真正摊开来看"><span>#</span></a></h1><p>为了更方便地做 old / upstream / new 三方对比，我没有把主工作区来回切换到旧提交，而是用了 <code>git worktree</code>。</p><p>例如创建 merge 前的个人版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>add</span><span> </span><span>../Firefly-before-merge</span><span> </span><span>dcaed5bc^1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>创建官方当时的版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>add</span><span> </span><span>../Firefly-upstream</span><span> </span><span>dcaed5bc^2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样我同时拥有：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Desktop</span></div></div><div><div><div>2</div></div><div><span>│</span></div></div><div><div><div>3</div></div><div><span>├── Firefly</span></div></div><div><div><div>4</div></div><div><span>│   └── 最终 merge 后版本</span></div></div><div><div><div>5</div></div><div><span>│</span></div></div><div><div><div>6</div></div><div><span>├── Firefly-before-merge</span></div></div><div><div><div>7</div></div><div><span>│   └── 我的 merge 前版本</span></div></div><div><div><div>8</div></div><div><span>│</span></div></div><div><div><div>9</div></div><div><span>└── Firefly-upstream</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>└── 官方 merge 前版本</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这三个目录非常适合复盘：</p><ul>
<li><code>Firefly-before-merge</code>：Current</li>
<li><code>Firefly-upstream</code>：Incoming</li>
<li><code>Firefly</code>：Result</li>
</ul><p>而且主工作区完全不需要切换。</p><p>这两个历史 worktree 通常处于 detached HEAD，因为它们直接检出的是具体 commit，而不是分支。</p><p>对我这次“只看历史、不继续开发”的用途来说，这正合适。</p><p>使用完后可以先检查：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>list</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>再确认两个历史 worktree 是否干净：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>-C</span><span> </span><span>../Firefly-before-merge</span><span> </span><span>status</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>-C</span><span> </span><span>../Firefly-upstream</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认没有需要保留的改动后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>remove</span><span> </span><span>../Firefly-before-merge</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>remove</span><span> </span><span>../Firefly-upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果曾经直接手动删掉目录，还可以：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>prune</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>清理残留登记。</p><p>值得注意的是，<strong>删除 worktree 并不会删除那些历史 commit</strong>。</p><p>worktree 只是“为某个历史节点临时打开一个额外工作目录”。</p><hr /></section>
<section><h1>这次合并之后，我对 Git 的理解发生了什么变化<a href="#这次合并之后我对-git-的理解发生了什么变化"><span>#</span></a></h1><p>以前我对 Git 的理解更接近：</p><p><code>修改文件 → git add → git commit → git push</code></p><p>现在我开始把它理解成：</p><blockquote><p><strong>一套用于描述代码历史关系的数据结构和工具。</strong></p></blockquote><p>这次我第一次把很多以前零散知道的概念真正串起来：</p><ul>
<li>分支为什么只是指向 commit 的引用；</li>
<li><code>fetch</code> 为什么不会修改工作区；</li>
<li><code>merge</code> 为什么能保留真实分叉历史；</li>
<li><code>rebase</code> 为什么会改变 commit hash；</li>
<li>Merge Commit 为什么有多个 parent；</li>
<li><code>^1</code>、<code>^2</code> 为什么能回到不同父提交；</li>
<li>为什么被删掉的旧文件还能重新取回；</li>
<li>为什么 worktree 能同时看到多个历史版本；</li>
<li>为什么 Git 说“冲突解决了”，也不代表程序逻辑一定正确。</li>
</ul><p>这些东西一旦连起来以后，Git 就不再是一堆要背的命令。</p><p>它开始变成一套可以推理的系统。</p><hr /><p><a></a></p></section>
<section><h1>这次实践涉及的 Git 知识完整总结<a href="#这次实践涉及的-git-知识完整总结"><span>#</span></a></h1><p>下面把这次实际使用到的知识集中整理一次。</p><p>这部分也是我以后再次同步 upstream 时，最希望自己能回来查阅的内容。</p><hr /><section><h2>1. Git 的核心不是“文件备份”，而是提交图<a href="#1-git-的核心不是文件备份而是提交图"><span>#</span></a></h2><p>Git 管理的是一张有向无环图（DAG）。</p><p>每个 commit：</p><ul>
<li>有自己的 commit hash；</li>
<li>指向一个或多个 parent；</li>
<li>对应项目在那个时间点的快照；</li>
<li>包含作者、时间、提交信息等元数据。</li>
</ul><p>普通 commit 通常有一个 parent。</p><p>Merge Commit 通常有两个或更多 parent。</p><p>因此，<code>分支</code> 本质上只是一个指向 commit 的可移动引用。</p><hr /></section><section><h2>2. <code>HEAD</code> 表示我现在在哪里<a href="#2-head-表示我现在在哪里"><span>#</span></a></h2><p>通常，<code>HEAD -&gt; master</code> 说明：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HEAD</span></div></div><div><div><div>2</div></div><div><span>↓</span></div></div><div><div><div>3</div></div><div><span>master</span></div></div><div><div><div>4</div></div><div><span>↓</span></div></div><div><div><div>5</div></div><div><span>某个 commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我当前检出的是 <code>master</code>。</p><p>如果直接检出某个 commit，而不是分支，可能进入 <code>detached HEAD</code>。此时 HEAD 直接指向 commit，而不是某个可移动分支。</p><p>这也是我创建历史 worktree 时看到的状态。</p><hr /></section><section><h2>3. <code>origin</code> / <code>upstream</code> 是远程名称<a href="#3-origin--upstream-是远程名称"><span>#</span></a></h2><p>它们不是固定角色。</p><p>在我的项目中约定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>origin</span></div></div><div><div><div>2</div></div><div><span>= 我的 GitHub 仓库</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>upstream</span></div></div><div><div><div>5</div></div><div><span>= 官方 GitHub 仓库</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>远程信息可以查看：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>4. <code>master</code> 与 <code>origin/master</code> 不是一回事<a href="#4-master-与-originmaster-不是一回事"><span>#</span></a></h2><p><code>master</code> 是本地分支。<code>origin/master</code> 是一个 remote-tracking branch，即：</p><blockquote><p>我本地记录的“上一次获取远程信息时，origin 的 master 在哪里”。</p></blockquote><p>同样，<code>upstream/master</code> 代表我本地记录的 upstream master 状态。</p><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会更新 <code>upstream/master</code>，但不会直接修改我的 <code>master</code>。</p><hr /></section><section><h2>5. <code>fetch</code> 和 <code>pull</code> 不一样<a href="#5-fetch-和-pull-不一样"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>主要负责：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>下载远程对象</span></div></div><div><div><div>2</div></div><div><span>+</span></div></div><div><div><div>3</div></div><div><span>更新远程跟踪引用</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它不会直接修改当前分支。</p><p>而：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通常可以理解成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fetch</span></div></div><div><div><div>2</div></div><div><span>+</span></div></div><div><div><div>3</div></div><div><span>merge/rebase</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>具体行为取决于配置。</p><p>这次我更喜欢把步骤拆开：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>因为每一步发生了什么更加清楚。</p><hr /></section><section><h2>6. 什么叫分叉<a href="#6-什么叫分叉"><span>#</span></a></h2><p>假如共同历史是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>官方继续：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C──D──E</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而我自己：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C──X──Y</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那么就形成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>        </span></span><span>D──E</span></div></div><div><div><div>2</div></div><div><span><span>       </span></span><span>/</span></div></div><div><div><div>3</div></div><div><span>A──B──C</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>\</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>X──Y</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这就是 diverged history。</p><p>两边都拥有对方没有的新提交。</p><hr /></section><section><h2>7. merge 的本质<a href="#7-merge-的本质"><span>#</span></a></h2><p>merge 会寻找两条历史的共同祖先，也就是 <code>merge base</code>，然后综合：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>共同祖先</span></div></div><div><div><div>2</div></div><div><span>Current</span></div></div><div><div><div>3</div></div><div><span>Incoming</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>计算最终变化。</p><p>如果能自动判断，就自动合并；</p><p>如果无法唯一判断，就产生 conflict。</p><p>所以 Git merge 本身其实就是典型的“三方合并”。</p><hr /></section><section><h2>8. rebase 的本质<a href="#8-rebase-的本质"><span>#</span></a></h2><p>rebase 并不是“把分支接过去”这么简单。</p><p>它会把当前分支独有的提交重新应用到新的 base 上。</p><p>因此，旧提交 <code>X</code> 通常会变成新的 <code>X'</code>，commit hash 也会随之改变。</p><p>所以，<code>rebase</code> 适合整理线性历史；<code>merge</code> 适合保留真实分叉历史。</p><p>这次我的目标明确是后者。</p><hr /></section><section><h2>9. fast-forward 与 <code>--no-ff</code><a href="#9-fast-forward-与---no-ff"><span>#</span></a></h2><p>如果当前分支没有独立提交，目标分支只是单纯领先：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C   master</span></div></div><div><div><div>2</div></div><div><span><span>       </span></span><span>\</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>D──E   upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>某些情况下 merge 可以直接让 <code>master</code> 指向 <code>E</code>，而不创建额外 Merge Commit。</p><p>这就是 <code>fast-forward</code>。而：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要求 Git 明确创建 Merge Commit。</p><p>我的目的就是保留“这里发生过一次上游同步”的历史节点。</p><hr /></section><section><h2>10. Current / Incoming 是相对概念<a href="#10-current--incoming-是相对概念"><span>#</span></a></h2><p>在我这次：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>的场景中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Current = master</span></div></div><div><div><div>2</div></div><div><span>Incoming = upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果我站在另一个分支上反方向 merge，这两个角色也会反过来。</p><p>所以不能死记：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Current = 我的</span></div></div><div><div><div>2</div></div><div><span>Incoming = 官方</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>真正应该记：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Current</span></div></div><div><div><div>2</div></div><div><span>= 当前 checkout 的分支</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>Incoming</span></div></div><div><div><div>5</div></div><div><span>= 被 merge 进来的那一侧</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>11. Result 才是最终真相<a href="#11-result-才是最终真相"><span>#</span></a></h2><p>Merge Editor 中最重要的不是左边还是右边，而是 <code>Result</code>，因为最终 commit 保存的是 Result。</p><p>它可以：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>完全等于 Current</span></div></div><div><div><div>2</div></div><div><span>完全等于 Incoming</span></div></div><div><div><div>3</div></div><div><span>或者完全不同于两者</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>Footer.astro</code> 就属于第三种。</p><hr /></section><section><h2>12. content conflict<a href="#12-content-conflict"><span>#</span></a></h2><p>当双方修改同一个文件的重叠区域，Git 无法自动确定结果时，就可能出现 <code>CONFLICT (content)</code>。传统文本形式会出现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</span></div></div><div><div><div>2</div></div><div><span>Current</span></div></div><div><div><div>3</div></div><div><span>=======</span></div></div><div><div><div>4</div></div><div><span>Incoming</span></div></div><div><div><div>5</div></div><div><span>&gt;&gt;&gt;&gt;&gt;&gt;&gt; upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>VS Code Merge Editor 只是把这些信息做成了更直观的 UI。</p><hr /></section><section><h2>13. modify/delete conflict<a href="#13-modifydelete-conflict"><span>#</span></a></h2><p>如果一侧修改一个文件，而另一侧删除它：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A：修改</span></div></div><div><div><div>2</div></div><div><span>B：删除</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Git 无法自动决定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>保留修改后的文件</span></div></div><div><div><div>2</div></div><div><span>还是</span></div></div><div><div><div>3</div></div><div><span>接受删除</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以会产生 <code>CONFLICT (modify/delete)</code>。<code>footerConfig.ts</code> 就是实际案例。</p><hr /></section><section><h2>14. <code>git add</code> 在冲突解决时还有“确认结果”的含义<a href="#14-git-add-在冲突解决时还有确认结果的含义"><span>#</span></a></h2><p>平时：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>file</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>经常被理解成：</p><blockquote><p>把文件加入暂存区。</p></blockquote><p>但在 merge conflict 中，它还有一个重要语义：</p><blockquote><p><strong>我已经解决这个文件的冲突，请把现在的版本视为最终结果。</strong></p></blockquote><p>所以修改完成后必须 <code>git add</code>，Git 才知道这个冲突已经处理结束。</p><hr /></section><section><h2>15. <code>git rm</code> 同样可以解决 conflict<a href="#15-git-rm-同样可以解决-conflict"><span>#</span></a></h2><p>对 modify/delete conflict：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>file</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>意味着：</p><blockquote><p>最终结果就是删除这个文件。</p></blockquote><p>这次 <code>footerConfig.ts</code> 就是这样完成解决的。</p><hr /></section><section><h2>16. <code>git status</code> 是 merge 过程中最重要的导航工具<a href="#16-git-status-是-merge-过程中最重要的导航工具"><span>#</span></a></h2><p>任何不确定的时候：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>都非常有用。</p><p>它可以告诉我：</p><ul>
<li>当前分支；</li>
<li>是否正在 merge；</li>
<li>未解决冲突；</li>
<li>已解决但尚未提交的文件；</li>
<li>staged / unstaged 状态。</li>
</ul><p>以后处理冲突时，比凭记忆猜当前状态可靠得多。</p><hr /></section><section><h2>17. <code>git merge --abort</code><a href="#17-git-merge---abort"><span>#</span></a></h2><p>如果 merge 进行到一半发现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>方向错了</span></div></div><div><div><div>2</div></div><div><span>冲突太复杂</span></div></div><div><div><div>3</div></div><div><span>想重新开始</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在尚未完成 Merge Commit 时通常可以：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>让仓库尽量恢复到 merge 开始之前。</p><p>这比手工乱删冲突文件安全得多。</p><hr /></section><section><h2>18. Merge Commit 有多个 parent<a href="#18-merge-commit-有多个-parent"><span>#</span></a></h2><p>普通提交里，<code>A ← B</code> 表示 B 有一个 parent A。</p><p>Merge：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>A──B──C</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>\   \</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>X───M</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>M 同时拥有：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>C</span></div></div><div><div><div>2</div></div><div><span>X</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两个 parent。</p><p>因此可以使用 <code>M^1</code> 和 <code>M^2</code> 访问不同的父提交。</p><p>访问不同父提交。</p><hr /></section><section><h2>19. <code>^1</code> 和 <code>^2</code> 的意义取决于 merge 方向<a href="#19-1-和-2-的意义取决于-merge-方向"><span>#</span></a></h2><p>在：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>M^1</span></div></div><div><div><div>2</div></div><div><span>= merge 前的 master</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>M^2</span></div></div><div><div><div>5</div></div><div><span>= upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是因为第一个 parent 是当前分支原来的 tip，第二个 parent 是被合入的 tip。</p><hr /></section><section><h2>20. <code>git log --graph --oneline --decorate</code><a href="#20-git-log---graph---oneline---decorate"><span>#</span></a></h2><p>这次我第一次觉得这条命令特别有价值：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--graph</span><span> </span><span>--oneline</span><span> </span><span>--decorate</span><span> </span><span>-10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中，<code>--graph</code> 用来画提交拓扑；<code>--oneline</code> 把每个提交压缩成一行；<code>--decorate</code> 用来显示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HEAD</span></div></div><div><div><div>2</div></div><div><span>master</span></div></div><div><div><div>3</div></div><div><span>origin/master</span></div></div><div><div><div>4</div></div><div><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等引用位置。</p><p>它比单纯看提交列表更容易理解 Git 的结构。</p><hr /></section><section><h2>21. commit hash 是 Git 历史的精确坐标<a href="#21-commit-hash-是-git-历史的精确坐标"><span>#</span></a></h2><p>例如，<code>dcaed5bc</code> 不是一个“版本号标签”，而是 commit ID 的缩写。</p><p>它可以直接用于：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>dcaed5bc</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>dcaed5bc^1</span><span> </span><span>dcaed5bc</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>add</span><span> </span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等操作。</p><p>只要没有发生历史重写，对特定 commit 的引用就很稳定。</p><hr /></section><section><h2>22. Git 保存已 commit 的旧版本<a href="#22-git-保存已-commit-的旧版本"><span>#</span></a></h2><p>当前文件被修改，不代表历史文件被覆盖。</p><p>只要曾经 commit，<code>旧内容</code> 仍然存在于对应 commit 的 tree 中。</p><p>因此可以：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>&lt;commit&gt;:&lt;path&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>直接读取历史文件。</p><p>这也是为什么我后来还能完整找回合并前的旧版。</p><hr /></section><section><h2>23. 未 commit 的内容则另当别论<a href="#23-未-commit-的内容则另当别论"><span>#</span></a></h2><p>Git 能恢复历史版本的前提是：</p><blockquote><p>那些内容已经进入过 Git 历史。</p></blockquote><p>如果一段代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>只存在于工作区</span></div></div><div><div><div>2</div></div><div><span>从未 commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>随后被覆盖或删除，那么 Git 本身不一定有它的对象。</p><p>这时可能需要：</p><ul>
<li>VS Code Local History</li>
<li>IDE 本地历史</li>
<li>文件系统恢复</li>
<li>其他备份</li>
</ul><p>所以：</p><blockquote><p>有意义的阶段性工作应该及时 commit。</p></blockquote><p>commit 不一定意味着“功能已经完美”，它首先是一个可恢复的历史节点。</p><hr /></section><section><h2>24. <code>git worktree</code> 允许同一仓库同时检出多个版本<a href="#24-git-worktree-允许同一仓库同时检出多个版本"><span>#</span></a></h2><p>普通 checkout 会改变当前工作目录。</p><p>worktree 则允许：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>主目录检出 master</span></div></div><div><div><div>2</div></div><div><span>另一个目录检出旧 commit</span></div></div><div><div><div>3</div></div><div><span>再一个目录检出官方 commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而且共享 Git 对象数据库。</p><p>非常适合：</p><ul>
<li>多版本对比</li>
<li>修复历史版本</li>
<li>同时开发多个分支</li>
<li>复现旧 bug</li>
<li>写迁移文档</li>
</ul><hr /></section><section><h2>25. detached HEAD 并不是错误<a href="#25-detached-head-并不是错误"><span>#</span></a></h2><p>当 worktree 直接检出“某个 commit”而不是“某个 branch”时，会进入 detached HEAD。</p><p>它非常适合只读历史快照。</p><p>真正需要注意的是：</p><blockquote><p>如果在 detached HEAD 上创建了新 commit，又没有创建分支保存引用，那么以后不容易继续找到这些提交。</p></blockquote><p>所以查看旧代码没问题；</p><p>如果准备开发，则最好：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>-c</span><span> </span><span>new-branch</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>创建分支。</p><hr /></section><section><h2>26. worktree 应该用 Git 自己管理<a href="#26-worktree-应该用-git-自己管理"><span>#</span></a></h2><p>查看：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>list</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>删除：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>remove</span><span> </span><span>&lt;path&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果目录已经被手动删掉：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>prune</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用于清理无效记录。</p><hr /></section><section><h2>27. merge 通常不需要 force push<a href="#27-merge-通常不需要-force-push"><span>#</span></a></h2><p>这次 merge 没有重写已有历史，只是在顶部增加了新的 Merge Commit。</p><p>因此：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即可。</p><p>而 rebase 已经推送过的提交后，才可能因为历史变更需要：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>--force-with-lease</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通常应该优先使用 <code>--force-with-lease</code>，而不是裸 <code>--force</code>，因为前者会额外检查远程状态，降低覆盖别人新提交的风险。</p><hr /></section><section><h2>28. Git 解决的是历史，编译器解决的是代码正确性<a href="#28-git-解决的是历史编译器解决的是代码正确性"><span>#</span></a></h2><p>一次 merge 能成功 commit，只能说明“Git 层面没有未解决冲突”，并不代表“程序一定正确”。例如前面的 <code>Bilibili + Gitee icon</code> 这种组合完全可能：</p><ul>
<li>没有 Git conflict；</li>
<li>TypeScript 语法也正确；</li>
<li>但业务逻辑错误。</li>
</ul><p>所以 merge 后还应该进行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>构建</span></div></div><div><div><div>2</div></div><div><span>测试</span></div></div><div><div><div>3</div></div><div><span>预览</span></div></div><div><div><div>4</div></div><div><span>功能检查</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Git 不能替代这些步骤。</p><hr /></section></section>
<section><h1>以后同步 upstream，我准备使用的标准流程<a href="#以后同步-upstream我准备使用的标准流程"><span>#</span></a></h1><p>以后官方继续更新，我会先：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>master</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认：</p><ul>
<li>在正确分支；</li>
<li>工作区没有不希望混入 merge 的未提交内容。</li>
</ul><p>然后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看历史：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--graph</span><span> </span><span>--oneline</span><span> </span><span>--decorate</span><span> </span><span>--all</span><span> </span><span>-20</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认确实需要合并后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>upstream/master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果发生冲突：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>逐个处理。</p><p>普通文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>&lt;file&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>接受删除：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>&lt;file&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果想放弃这次 merge：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>全部完成后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认没有 <code>unmerged paths</code>，然后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>commit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最后：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>master</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果需要检查最终历史：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--graph</span><span> </span><span>--oneline</span><span> </span><span>--decorate</span><span> </span><span>-20</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>这次实践让我对 Git 的理解发生了什么变化<a href="#这次实践让我对-git-的理解发生了什么变化"><span>#</span></a></h1><p>以前我对 Git 的理解更接近 <code>修改文件 → git add → git commit → git push</code>。现在，我开始把 Git 理解成：</p><p><strong>一套用于描述代码历史关系的数据结构和工具</strong></p><p>分支不是“复制出来的文件夹”；</p><p>远程不是“云端文件夹”；</p><p>merge 也不是“把两个文件夹覆盖到一起”。</p><p>它们实际在操作的是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>commit</span></div></div><div><div><div>2</div></div><div><span>parent</span></div></div><div><div><div>3</div></div><div><span>reference</span></div></div><div><div><div>4</div></div><div><span>history graph</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>真正理解这些以后，很多 Git 命令突然就不再需要死记。</p><p>例如：</p><p>为什么 <code>fetch</code> 不改工作区？</p><p>因为它主要更新远程对象和 remote-tracking refs。</p><p>为什么 rebase 会变 commit hash？</p><p>因为它创建了拥有不同 parent 的新 commit。</p><p>为什么 merge commit 有 <code>^1</code> 和 <code>^2</code>？</p><p>因为它真的有两个 parent。</p><p>为什么已经被删掉的文件还能找回来？</p><p>因为旧 commit 的 tree 仍然记录着那个文件。</p><p>为什么 worktree 可以同时看到三个版本？</p><p>因为一个 Git repository 可以拥有多个工作目录，每个工作目录检出不同引用。</p><p>这些东西第一次真正连接在了一起。</p><hr /></section>
<section><h1>结语<a href="#结语"><span>#</span></a></h1><p>这次我原本只是想做一件很简单的事：</p><blockquote><p>把 Firefly 官方的新更新同步到自己的版本里。</p></blockquote><p>最后却变成了一次非常完整的 Git 实战。</p><p>我经历了：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>确认 origin / upstream</span></div></div><div><div><div>2</div></div><div><span>↓</span></div></div><div><div><div>3</div></div><div><span>fetch 官方历史</span></div></div><div><div><div>4</div></div><div><span>↓</span></div></div><div><div><div>5</div></div><div><span>观察分支分叉</span></div></div><div><div><div>6</div></div><div><span>↓</span></div></div><div><div><div>7</div></div><div><span>选择 merge 而非 rebase</span></div></div><div><div><div>8</div></div><div><span>↓</span></div></div><div><div><div>9</div></div><div><span>使用 --no-ff 保留 Merge Commit</span></div></div><div><div><div>10</div></div><div><span>↓</span></div></div><div><div><div>11</div></div><div><span>处理 content conflict</span></div></div><div><div><div>12</div></div><div><span>↓</span></div></div><div><div><div>13</div></div><div><span>处理 modify/delete conflict</span></div></div><div><div><div>14</div></div><div><span>↓</span></div></div><div><div><div>15</div></div><div><span>理解 Current / Incoming / Result</span></div></div><div><div><div>16</div></div><div><span>↓</span></div></div><div><div><div>17</div></div><div><span>判断配置冲突与架构冲突</span></div></div><div><div><div>18</div></div><div><span>↓</span></div></div><div><div><div>19</div></div><div><span>手工完成真正的三方合并</span></div></div><div><div><div>20</div></div><div><span>↓</span></div></div><div><div><div>21</div></div><div><span>commit</span></div></div><div><div><div>22</div></div><div><span>↓</span></div></div><div><div><div>23</div></div><div><span>push</span></div></div><div><div><div>24</div></div><div><span>↓</span></div></div><div><div><div>25</div></div><div><span>用 Git Graph 验证历史</span></div></div><div><div><div>26</div></div><div><span>↓</span></div></div><div><div><div>27</div></div><div><span>理解 Merge Commit parent</span></div></div><div><div><div>28</div></div><div><span>↓</span></div></div><div><div><div>29</div></div><div><span>用 worktree 恢复三个历史版本</span></div></div><div><div><div>30</div></div><div><span>↓</span></div></div><div><div><div>31</div></div><div><span>重新复盘 old / upstream / new</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>最重要的收获不是我记住了多少条命令。</p><p>而是我开始理解：</p><blockquote><p><strong>Git 的真正价值不是让我“能把代码上传到 GitHub”，而是让我可以明确地回答：代码是怎样一步一步变成今天这个样子的。</strong></p></blockquote><p>这次我特意没有把官方线和个人线通过 rebase 压成一条“看起来一直都很整齐”的历史。</p><p>我希望真实保留：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>这里，我从官方版本分叉；</span></div></div><div><div><div>2</div></div><div><span>这里，我加入了自己的修改；</span></div></div><div><div><div>3</div></div><div><span>这里，官方继续向前更新；</span></div></div><div><div><div>4</div></div><div><span>这里，两套修改发生冲突；</span></div></div><div><div><div>5</div></div><div><span>这里，我理解两边代码后做出了自己的选择；</span></div></div><div><div><div>6</div></div><div><span>这里，它们重新合并。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对一个正在开始系统使用 Git 管理真实项目的人来说，我觉得这段历史本身就值得留下。</p><p>而下一次再看到 <code>CONFLICT</code> 时，我大概不会再把它理解成“Git 出问题了”。</p><p>它更像是在问我：</p><blockquote><p><strong>两个方向都发生了变化，你希望项目接下来长成什么样？</strong></p></blockquote><p>这可能才是版本管理真正开始变得有意思的地方。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/tech/260911-firefly-bug-fix/</id>
      <title type="text">Firefly bug修复：由 localStorage 引发的配置失效bug</title>
      <published>2026-09-11T00:00:00.000Z</published>
      <updated>2026-09-11T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/tech/260911-firefly-bug-fix/"/>
      <summary type="text">记录 Firefly 全屏壁纸 Hero 布局被旧 localStorage 值覆盖的问题，以及从配置读取链路入手定位并修复问题的过程。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>Note</div></div><div><p>本文记录的修复已通过 <a href="https://github.com/CuteLeaf/Firefly/pull/632" target="_blank">PR #632</a> 提交至 Firefly 项目，并已由原作者合并。</p></div></div>
<p>今天在调整 Firefly 博客的全屏壁纸时，遇到了一个有些奇怪的问题。</p>
<p>我在 <code>backgroundWallpaper.ts</code> 中将壁纸模式设置为 <code>fullscreen</code>，同时把全屏布局设置为 <code>hero</code>：</p>
<div><figure><figcaption><span>src/config/backgroundWallpaper.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>const</span><span> </span><span>backgroundWallpaper</span><span>:</span><span> </span><span>BackgroundWallpaperConfig</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>mode</span><span>:</span><span> </span><span>"fullscreen"</span><span>,</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>  </span><span>fullscreen</span><span>:</span><span> {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>layout</span><span>:</span><span> </span><span>"hero"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>    </span><span>position</span><span>:</span><span> </span><span>"center"</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>8</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>按照配置说明，<code>hero</code> 应该在主页呈现全屏壁纸；向下滚动屏幕，下方内容出现的同时壁纸变成模糊背景。</p>
<p>然而刷新页面后，实际效果仍然像 <code>classic</code> 布局。配置本身没有类型错误，项目也能正常运行，但页面就是没有按照预期切换到 Hero 模式。</p>
<p>这让我开始怀疑：问题可能不在配置文件，而在配置被读取之后的某个环节。</p>
<section><h1>从配置读取链路开始排查<a href="#从配置读取链路开始排查"><span>#</span></a></h1><p>一开始，我没有逐一翻阅项目中的所有文件，而是只搜索了几个与问题直接相关的关键词：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>backgroundWallpaper</span></div></div><div><div><div>2</div></div><div><span>fullscreenLayout</span></div></div><div><div><div>3</div></div><div><span>data-fullscreen-layout</span></div></div><div><div><div>4</div></div><div><span>hero</span></div></div><div><div><div>5</div></div><div><span>localStorage</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样很快将范围缩小到了几个文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>src/config/backgroundWallpaper.ts</span></div></div><div><div><div>2</div></div><div><span>src/layouts/Layout.astro</span></div></div><div><div><div>3</div></div><div><span>src/utils/setting-utils.ts</span></div></div><div><div><div>4</div></div><div><span>src/styles/layout-base.css</span></div></div><div><div><div>5</div></div><div><span>src/styles/layout-styles.css</span></div></div><div><div><div>6</div></div><div><span>src/components/controls/DisplaySettingsIntegrated.svelte</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我首先确认了配置类型。<code>FullscreenWallpaperLayout</code> 的定义确实包含 <code>hero</code>：</p><div><figure><figcaption><span>src/types/backgroundWallpaper.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>type</span><span> </span><span>FullscreenWallpaperLayout</span><span> </span><span>=</span><span> </span><span>"classic"</span><span> </span><span>|</span><span> </span><span>"hero"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这说明 <code>hero</code> 是项目正式支持的布局，并不是配置名称写错了。</p><p>接着，我检查了负责 Hero 布局的样式。项目会根据 HTML 元素上的两个属性决定最终布局：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>html</span></div></div><div><div><div>2</div></div><div><span>  </span><span>data-wallpaper-mode</span><span>=</span><span>"fullscreen"</span></div></div><div><div><div>3</div></div><div><span>  </span><span>data-fullscreen-layout</span><span>=</span><span>"hero"</span></div></div><div><div><div>4</div></div><div><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当这两个属性同时满足时，壁纸会固定覆盖整个首屏，首页内容则被移动到首屏下方。</p><p>也就是说，只要页面最终得到的是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>data-wallpaper-mode="fullscreen"</span></div></div><div><div><div>2</div></div><div><span>data-fullscreen-layout="hero"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Hero 模式就应该正常工作。</p><p>至此，问题开始变得清晰：或许不是 Hero 样式没有生效，而是 <code>data-fullscreen-layout</code> 最终根本没有被设置成 <code>hero</code>。</p></section>
<section><h1>找到真正覆盖配置的地方<a href="#找到真正覆盖配置的地方"><span>#</span></a></h1><p>继续检查 <code>Layout.astro</code> 的页面初始化逻辑后，我发现程序会先读取浏览器中的 <code>localStorage</code>：</p><div><figure><figcaption><span>src/layouts/Layout.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>storedFullscreenLayout</span><span> </span><span>=</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>localStorage</span><span>.</span><span>getItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>resolvedFullscreenLayout</span><span> </span><span>=</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>storedFullscreenLayout</span><span> </span><span>===</span><span> </span><span>"hero"</span><span> </span><span>||</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>storedFullscreenLayout</span><span> </span><span>===</span><span> </span><span>"classic"</span></div></div><div><div><div>7</div></div><div><span>    </span><span>?</span><span><span> </span><span>storedFullscreenLayout</span></span></div></div><div><div><div>8</div></div><div><span>    </span><span>:</span><span><span> </span><span>fullscreenLayout</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这段代码的含义是：</p><ol>
<li>先检查浏览器是否保存过全屏布局；</li>
<li>如果保存值是 <code>hero</code> 或 <code>classic</code>，就优先使用保存值；</li>
<li>只有没有有效保存值时，才使用配置文件中的布局。</li>
</ol><p>我之前曾经使用过 <code>classic</code> 布局，因此浏览器中很可能还保存着：</p><div><figure><figcaption><span>浏览器控制台</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>localStorage</span><span>.</span></span><span>getItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>// "classic"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>于是，即使后来把配置文件改成：</p><div><figure><figcaption><span>src/config/backgroundWallpaper.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>layout</span><span>: </span></span><span>"hero"</span><span>,</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>初始化脚本仍然会选择浏览器中遗留的 <code>classic</code>。</p><p>最终页面得到的其实是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>html</span><span> </span><span>data-fullscreen-layout</span><span>=</span><span>"classic"</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>配置没有失效，Hero 样式也没有失效。真正的问题是：配置值在页面初始化时被旧的本地记录覆盖了。</p></section>
<section><h1>为什么这是一个程序问题<a href="#为什么这是一个程序问题"><span>#</span></a></h1><p>如果项目允许用户在显示设置中切换全屏布局，那么优先使用用户保存的选择是合理的。</p><p>但这个项目还有一个显示设置总开关。当全屏布局切换功能关闭时，用户已经无法通过界面切换或重置布局，此时页面却仍然读取以前保存的值。</p><p>这会导致一种不太合理的状态：</p><ul>
<li>项目维护者在配置文件中设置了 <code>hero</code>；</li>
<li>全屏布局切换功能已经关闭；</li>
<li>浏览器仍然使用过去保存的 <code>classic</code>；</li>
<li>用户无法从界面中恢复配置默认值。</li>
</ul><p>对比壁纸模式的处理逻辑后，我还发现，项目已经对 <code>wallpaperMode</code> 考虑了类似情况：</p><div><figure><figcaption><span>src/utils/setting-utils.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> (</span><span>!</span><span><span>isSwitchable</span><span>) {</span></span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>localStorage</span><span>.</span><span>removeItem</span><span>(</span><span>"wallpaperMode"</span><span>);</span></div></div><div><div><div>3</div></div><div><span>  </span><span>return</span><span><span> </span><span>backgroundWallpaper</span><span>.</span></span><span>mode</span><span>;</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当壁纸模式不允许切换时，旧记录会被清除，并直接采用项目配置。</p><p>而 <code>fullscreenLayout</code> 缺少了同样的判断。这正是问题的根源。</p></section>
<section><h1>修复思路<a href="#修复思路"><span>#</span></a></h1><p>修复目标很明确：</p><ul>
<li>如果全屏布局允许用户切换，就继续读取并保留用户保存的选择；</li>
<li>如果全屏布局不允许切换，就忽略并清除旧记录，始终采用配置文件中的布局。</li>
</ul><p>首先，我修改了 <code>getStoredFullscreenLayout()</code>。当布局切换功能关闭时，程序会清除旧记录并直接返回配置中的默认布局：</p><div><div><div><figure><figcaption><span>src/utils/setting-utils.ts</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>function</span><span> </span><span>getStoredFullscreenLayout</span><span>()</span><span>:</span><span> </span><span>FullscreenWallpaperLayout</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>const</span><span> </span><span>defaultLayout</span><span> </span><span>=</span><span> </span><span>getDefaultFullscreenLayout</span><span>();</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>  </span><span>if</span><span> (</span></div></div><div><div><div>5</div></div><div><span>    </span><span>typeof</span><span><span> </span><span>localStorage</span><span> </span></span><span>===</span><span> </span><span>"undefined"</span><span> </span><span>||</span></div></div><div><div><div>6</div></div><div><span>    </span><span>typeof</span><span><span> </span><span>localStorage</span><span>.</span></span><span>getItem</span><span> </span><span>!==</span><span> </span><span>"function"</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>) {</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span><span> </span><span>defaultLayout</span><span>;</span></span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>  </span><span>const</span><span> </span><span>isSwitchable</span><span> </span><span>=</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>displaySettingsConfig</span><span>.</span><span>fullscreenLayoutSwitchable</span><span>;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span>isSwitchable</span><span>) {</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>localStorage</span><span>.</span><span>removeItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>16</div></div><div><span>    </span><span>return</span><span> </span><span>defaultLayout</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span><span>  </span></span><span>}</span></span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>  </span><span>const</span><span> </span><span>stored</span><span> </span><span>=</span><span><span> </span><span>localStorage</span><span>.</span></span><span>getItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>  </span><span>return</span><span><span> </span><span>stored</span><span> </span></span><span>===</span><span> </span><span>"hero"</span><span> </span><span>||</span><span><span> </span><span>stored</span><span> </span></span><span>===</span><span> </span><span>"classic"</span></div></div><div><div><div>22</div></div><div><span>    </span><span>?</span><span><span> </span><span>stored</span></span></div></div><div><div><div>23</div></div><div><span>    </span><span>:</span><span><span> </span><span>defaultLayout</span><span>;</span></span></div></div><div><div><div>24</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>页面首屏初始化发生在客户端组件挂载之前，因此还需要同步修改 <code>Layout.astro</code>。这里不再无条件读取本地记录，而是先判断全屏布局是否允许切换：</p><div><div><div><figure><figcaption><span>src/layouts/Layout.astro</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>// 初始化全屏布局：优先使用设置面板保存的运行时选择</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>storedFullscreenLayout</span><span> </span><span>=</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>localStorage</span><span>.</span><span>getItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>4</div></div><div><span>// 仅在允许切换时使用设置面板保存的运行时选择；否则配置值始终生效</span></div></div><div><div><div>5</div></div><div><span>const</span><span> </span><span>storedFullscreenLayout</span><span> </span><span>=</span><span> </span><span>isFullscreenLayoutSwitchable</span></div></div><div><div><div>6</div></div><div><span>  </span><span>?</span><span> </span><span>localStorage</span><span>.</span><span>getItem</span><span>(</span><span>"fullscreenLayout"</span><span>)</span></div></div><div><div><div>7</div></div><div><span>  </span><span>:</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>if</span><span> (</span><span>!</span><span>isFullscreenLayoutSwitchable</span><span>) {</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>localStorage</span><span>.</span><span>removeItem</span><span>(</span><span>"fullscreenLayout"</span><span>);</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>const</span><span> </span><span>resolvedFullscreenLayout</span><span> </span><span>=</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>storedFullscreenLayout</span><span> </span><span>===</span><span> </span><span>"hero"</span><span> </span><span>||</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>storedFullscreenLayout</span><span> </span><span>===</span><span> </span><span>"classic"</span></div></div><div><div><div>16</div></div><div><span>    </span><span>?</span><span><span> </span><span>storedFullscreenLayout</span></span></div></div><div><div><div>17</div></div><div><span>    </span><span>:</span><span><span> </span><span>fullscreenLayout</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>为了让内联初始化脚本能够获得这个开关，还需要将解析后的配置值传入脚本：</p><div><figure><figcaption><span>src/layouts/Layout.astro</span></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>script</span></div></div><div><div><div>2</div></div><div><span>  </span><span>is:inline</span></div></div><div><div><div>3</div></div><div><span>  </span><span>define:vars</span><span>=</span><span>{</span><span>{</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>defaultWallpaperMode</span><span>:</span><span> </span><span>backgroundWallpaper</span><span>.</span><span>mode</span><span>,</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>isWallpaperSwitchable</span><span>:</span></div></div><div><div><div>6</div></div><div><span>      </span><span>displaySettingsConfig</span><span>.</span><span>wallpaperModeSwitchable</span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>isFullscreenLayoutSwitchable</span><span>:</span></div></div><div><div><div>8</div></div><div><span>      </span><span>displaySettingsConfig</span><span>.</span><span>fullscreenLayoutSwitchable</span><span>,</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>fullscreenLayout</span><span>,</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>}</span><span>}</span></div></div><div><div><div>11</div></div><div><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样，首屏渲染和后续客户端初始化使用了完全一致的规则。</p></section>
<section><h1>验证修复<a href="#验证修复"><span>#</span></a></h1><p>为了稳定复现原来的问题，我先在浏览器中写入一个旧值：</p><div><figure><figcaption><span>浏览器控制台</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>localStorage</span><span>.</span></span><span>setItem</span><span>(</span><span>"fullscreenLayout"</span><span>, </span><span>"classic"</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后将项目配置设为：</p><div><figure><figcaption><span>src/config/backgroundWallpaper.ts</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>mode</span><span>: </span></span><span>"fullscreen"</span><span>,</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>fullscreen</span><span>: {</span></span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>layout</span><span>: </span><span>"hero"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>},</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在关闭全屏布局切换功能的情况下刷新页面。</p><p>修复前，页面仍然采用 <code>classic</code>。</p><p>修复后，我确认了以下结果：</p><ul>
<li>页面立即使用了配置中的 Hero 布局；</li>
<li>HTML 上的 <code>data-fullscreen-layout</code> 为 <code>hero</code>；</li>
<li><code>localStorage</code> 中遗留的 <code>fullscreenLayout</code> 已被删除；</li>
<li>再次刷新页面后，Hero 布局仍然正常；</li>
<li>重新启用布局切换功能后，用户选择的布局依然可以正常保存。</li>
</ul><p>这说明修复没有取消原有的用户偏好功能，只是让本地存储的优先级与功能开关保持一致。</p></section>
<section><h1>写在最后<a href="#写在最后"><span>#</span></a></h1><p>这次问题表面上看起来像是 CSS 或 Hero 布局实现错误，但真正的原因却是浏览器里一条不起眼的本地记录。</p><p>整个排查过程也再次提醒我：当一个配置看起来“没有生效”时，不能只盯着配置定义和样式本身，还应该检查它的完整读取链路：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>配置默认值</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>运行时状态</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>浏览器持久化数据</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>DOM 属性</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>最终样式</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>尤其是同时支持“项目默认配置”和“用户运行时设置”的功能，必须明确两者的优先级：</p><ul>
<li>功能允许用户调整时，用户选择优先；</li>
<li>功能不允许用户调整时，项目配置优先；</li>
<li>已经失去作用的历史记录，应当及时清理。</li>
</ul><p>这次修改本身并不复杂，真正花时间的是找出究竟是哪一层覆盖了配置。</p><p>好在最后并不需要重写 Hero 布局，也不需要大范围调整样式。只要让 <code>fullscreenLayout</code> 与已有的 <code>wallpaperMode</code> 遵循相同的状态管理规则，问题就自然解决了。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/essay/260907-summerdays/</id>
      <title type="text">夏夜</title>
      <published>2026-09-07T00:00:00.000Z</published>
      <updated>2026-09-07T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/essay/260907-summerdays/"/>
      <summary type="text">回忆中没有内容，只有晚风、虫鸣，和那个曾经拥有完整夏天的自己。</summary>
      <content type="html"><![CDATA[<p>我很喜欢夏夜。</p>
<p>其实也很难说清，这种偏爱究竟是从什么时候开始的。</p>
<p>可能只是后来回头看的时候，才慢慢发现，好像很多美好的事情，都发生在这样的夜晚。</p>
<p>于是时间久了，“夏夜”对我来说，也就不再只是一个季节里的某段时间。</p>
<hr />
<p>傍晚时分。</p>
<p>白日积攒下来的暑气尚未完全散去，空气里还留着一点余温。太阳落下以后，风渐渐变得舒服起来。</p>
<p>路灯亮了起来，树木与房屋的轮廓慢慢沉进夜色，草丛里开始传来虫鸣。</p>
<p>有时候，我会站在小路边，任凭晚风拂面，眺望远方，或者只是单纯地发呆。</p>
<p>天色再暗一些，便能看见天空中的星星。</p>
<p>有时候，田野里也会亮起一些“星星”。</p>
<p>萤火虫。</p>
<p>我喜欢夏夜，也顺便喜欢这夏夜里的生灵。</p>
<p>它们并不一定有多么特别，只是每当看到那一点微弱的光，我总会很自然地想到夏天。</p>
<p>想到晚风，想到虫鸣，想到一些已经过去很久的夜晚。</p>
<hr />
<p>真正留在记忆里的，往往并不是什么了不起的大事。</p>
<p>可能是某次很晚才回到家。</p>
<p>可能是放暑假以后，明知道第二天不用早起，于是迟迟不愿睡觉。</p>
<p>也可能什么都没有发生，只是在阳台坐着，吹风，听着远处的声音，看着路上稀疏的车流。</p>
<p>很多细节如今已经记不清了。</p>
<p>只有一些很零碎的“感觉”留了下来：</p>
<p>那天晚上很热。</p>
<p>路灯是昏黄的。</p>
<p>回家的时候已经很晚。</p>
<p>路边还有店铺亮着灯。</p>
<p>风吹过来很舒服。</p>
<p>远处似乎有人说话。</p>
<p>有时候也不知道为什么，这些看似无关紧要的东西，反而比真正发生过什么记得更久。</p>
<p>也许人最后怀念的，本来就不一定是某一件具体的事情。</p>
<p>有时候，我们怀念的是当时的天气，当时的声音，当时身边的人，以及那个时候的自己。</p>
<p>于是，在某个相似的夜晚，那些东西又会突然从记忆深处浮现出来。</p>
<p>不是一个完整的故事。</p>
<p>甚至算不上清晰的画面。</p>
<p>只是某个瞬间，会突然觉得：</p>
<p>好像自己又回到了很多年前。</p>
<hr />
<p>所以我时常会想：</p>
<p><strong>那些年的夏夜，到底发生了什么呢？</strong></p>
<p>初中的某个晚上，也许刚刚放学，一个人坐车回家。</p>
<p>高中的某个暑假，也许和朋友在外面吃完晚饭，玩到很晚才回去。</p>
<p>大学里的某个夜晚，也许考试终于结束，大家终于可以松一口气。</p>
<p>那时候好像总有很多时间。</p>
<p>白天很长，夜晚也很长。</p>
<p>一个暑假像是一整段独立于其他日子的时间，就连“明天”似乎都离现在十分遥远。</p>
<p>再后来毕业，夏夜再也不天然意味着一整段无所顾忌、完全属于自己的时光。</p>
<p>夏天依然会如期而至。</p>
<p>只是一些曾经习以为常的东西，其实早就已经成为了遥远的曾经。</p>
<p>有时候我会想，自己怀念的或许不只是某一个夏天。</p>
<p>而是曾经拥有过完整夏天的自己。</p>
<hr />
<p>有些东西似乎一直没有怎么改变。</p>
<p>路灯亮起以后，街道依旧会显得安静。</p>
<p>路边的店铺依旧亮着灯，偶尔有人骑车经过，远处也依旧会传来模糊的人声。</p>
<p>于是某些很久以前的片段，也总会在这样的时刻重新浮现。</p>
<p>现在想起来，反而是那些“什么都没有发生”的夜晚，最容易让人怀念。</p>
<p>也许正因为当时什么都没有发生，我们才没有意识到，那些平凡的日子有一天也会变成回忆。</p>
<hr />
<p>所以后来我渐渐觉得，自己喜欢的也许并不只是“夏夜”本身。</p>
<p>更准确地说，是那些与夏夜联系在一起的人、地方、声音和回忆。</p>
<p>我们不可能回到“曾经”。</p>
<p>很多事情也终究会随着时间慢慢变得模糊。</p>
<p>可“曾经”似乎又没有真正消失。</p>
<p>它只是被藏在了时间深处。</p>
<p>等到某个相似的夜晚，再因为一阵风、一声虫鸣、一盏昏黄的路灯，被重新想起。</p>
<p>或许这也是我为什么总想留下些什么。</p>
<p>一张照片，一次旅行，一段文字，或者某件现在看来微不足道的小事。</p>
<p>并不是因为它们一定有多么重要。</p>
<p>只是我知道，那些现在觉得理所当然的东西，有一天也会成为过去。</p>
<p>如果能够在它们彻底模糊以前，稍微留下一点痕迹，也已经足够。</p>
<p>也许很多年以后，我还会在某个夏夜重新看到这些记录。</p>
<p>然后想起：</p>
<p>原来那个时候的我，是这样生活着的。</p>]]></content>
    </entry>
    <entry>
      <id>https://inn.nightbugclub.cn/posts/essay/260906-first-blog/</id>
      <title type="text">写在开头</title>
      <published>2026-09-06T00:00:00.000Z</published>
      <updated>2026-09-06T00:00:00.000Z</updated>
      <author><name>Eternity_Riguru</name></author>
      <link rel="alternate" href="https://inn.nightbugclub.cn/posts/essay/260906-first-blog/"/>
      <summary type="text">第一篇文章，想写什么写什么好了。</summary>
      <content type="html"><![CDATA[<p>这是这里的第一篇文章。</p>
<p>真正决定要敲下这些文字的时候，我反而不知道该从哪里说起。</p>
<p>关于“写日记”，或者说，“写博客”这件事，我其实想过很多次。偶尔遇到值得记录的事情，会想着“以后一定要把它写下来”；折腾完某个项目，也会觉得其中踩过的坑、做过的尝试，如果只是随着时间慢慢忘掉，好像有些可惜。还有一些更零碎的东西——某个突然冒出来的想法，一段很喜欢的文字，一张照片，一次旅行，甚至只是某一天晚上莫名其妙产生的情绪——它们似乎都值得拥有一个地方，被好好保存下来。</p>
<p>但“以后再写”这句话，说着说着，就过去了很久，直到这件事本身也被忘却。</p>
<p>直到最近，我偶然间找到了 Firefly 的博客模板，十分喜爱。于是我认为，也许真的可以开始了。</p>
<section><h1>我最初认识的“网站”<a href="#我最初认识的网站"><span>#</span></a></h1><p>如果认真回想，我对“网站”这件事最早的印象，其实可以追溯到小学的信息课。</p><p>那时候的课本里教过我们怎样搭建一个 FTP 服务器。</p><p>现在回头看，那其实离今天意义上的“做一个网站”还有很远的距离。没有精心设计的页面，也谈不上什么内容系统，只是按照课本上的步骤一点一点配置，然后尝试让另一台电脑访问自己共享出来的文件。</p><p>可对当时的我来说，这件事却很神奇。</p><p>原来电脑里的东西并不一定只能待在自己的硬盘里。</p><p>只要经过一些设置，它们就可以通过网络，被另一台电脑找到、打开和下载。</p><p>那时的我或许还说不清什么是服务器，也不了解域名、前端、后端，更不会去想一个网站究竟是怎样运行起来的。但我第一次很直观地感受到：</p><p><strong>原来我也可以在网络上拥有一个属于自己的地方。</strong></p></section>
<section><h1>网上冲浪<a href="#网上冲浪"><span>#</span></a></h1><p>后来真正接触到互联网上各种各样的网站，这种印象也慢慢发生了变化。</p><p>我开始意识到，“网站”并不只是一个用来放文件的地址。</p><p>它可以是一家公司面向所有人的门面，可以是一套复杂的服务，也可以只是某个人留在互联网角落里的一间小屋。</p><p>有人在这里写技术文章，有人记录旅行，有人整理作品，也有人只是偶尔写下几句近况。</p><p>相比那些庞大而复杂的平台，我反而越来越喜欢后者。</p><p>一个人按照自己的喜好搭起来的网站，不一定精致，也不一定有很多访问者，却能从字体、颜色、图片，甚至一句不起眼的文字里，看出属于这个人的独特之处。</p><p>也许正是那时候开始，我就对这种“在网络上拥有一个自己的地方”的感觉产生了兴趣。</p><p>只是彼时的我还不知道，很多年之后，我真的会坐在电脑前，一点一点把这样一个地方搭起来。</p></section>
<section><h1>最近的一点突发奇想<a href="#最近的一点突发奇想"><span>#</span></a></h1><p>真正让我最近重新冒出“要不要自己做一个网站”这个念头的，其实是一件挺偶然的事。</p><p>前段时间，我参与了一个小工具的前端重构工作。</p><p>一开始，我只是把它当成一次普通的 UI 重构。原本的关注点更多还是放在“这个按钮应该放在哪里”“这一块界面应该怎么整理”“怎样让用户看起来更赏心悦目”这些很具体的问题上。</p><p>但做着做着，我开始频繁接触 CSS 和 JavaScript。</p><p>也就是在这个过程中，我突然发现，这两样原本在我印象里多少有些陌生的东西，其实比我想象中有趣得多。</p><p>CSS 并不只是给页面“加个颜色”或者“改改字体”。</p><p>一个元素放在哪里，间距留多少，页面该怎样随着屏幕大小变化，甚至一段文字在什么位置出现得最舒服，这些看起来很细小的事情，最后都会影响一个界面给人的感觉。</p><p>而 JavaScript 又像是给这些静态的界面加上了某种“反应”。</p><p>按钮可以回应点击，内容可以随着操作变化，页面上的东西不再只是安静地摆在那里，而是开始和使用它的人产生互动。</p><p>这种感觉对我来说有点新鲜。</p><p>也是从那个时候开始，我第一次不只是把网页当成一个“打开之后用完就关掉”的东西，而是开始留意它到底是怎么被做出来的。</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><p>于是，曾经停留在小学信息课本里的那个模糊印象，和最近重新燃起的一点兴趣，意外地在这里接上了。</p></section>
<section><h1>为什么想写一个博客<a href="#为什么想写一个博客"><span>#</span></a></h1><p>最开始，我并没有给这个博客赋予什么宏大的意义。</p><p>它不一定要有多少人阅读，也不一定非要持续输出多么专业、深刻的内容。相比于这些，我更希望这里首先是属于我自己的一个小角落。</p><p>互联网的节奏总是很快。</p><p>信息不断出现，又不断被新的信息覆盖。短暂的想法被塞进聊天记录，照片沉进越来越长的相册，曾经花费许多时间解决的问题，在几个月后重新遇见时，甚至连自己都记不清当初是怎么处理的。</p><p>而博客似乎慢一些。</p><p>我可以花时间整理一件事情的前因后果，也可以只是安静地写下一段没有明确目的的文字。它不需要立刻得到回应，也不用被压缩成几句话。</p><p>我很喜欢这种感觉。</p><p>所以，我想把一些东西留下来。</p></section>
<section><h1>我想在这里写些什么<a href="#我想在这里写些什么"><span>#</span></a></h1><p>现在其实还没有一个特别明确的答案。</p><p>大概会有一些关于技术的内容。</p><p>比如我正在折腾的网站，写代码时遇到的问题，Git 的使用记录，一些突然想到的小功能，以及那些从“应该很简单吧”开始，最后折腾了大半天才终于解决的问题。</p><p>也会有一些与技术无关的东西。</p><p>旅行时看到的风景，拍下来的照片，喜欢的作品，偶然产生的想法，还有一些关于生活本身的记录。</p><p>或许以后回过头来看，会发现这些文章并没有什么统一的主题。</p><p>但我觉得这也没什么不好。</p><p>因为生活本身好像也从来没有什么“统一的主题”。</p><p>今天可能在研究一段代码，明天可能突然想写一场雨，过几天又开始整理一次旅行。博客只是把这些原本散落在不同地方的碎片，慢慢收集到一起。</p><p>如果时间足够久，也许它最终会变成某种属于我的时间线。</p></section>
<section><h1>“第一篇”为什么这么写？<a href="#第一篇为什么这么写"><span>#</span></a></h1><p>在创建博客的时候，我其实也想过：</p><p>第一篇文章是不是应该特别一点？</p><p>是不是应该认真介绍一下自己，讲讲网站为什么叫这个名字，再郑重其事地写一段类似“从今天开始，我要坚持写博客”的宣言？</p><p>后来想想，好像没有这个必要。</p><p>第一篇文章最重要的事情，或许仅仅只是——</p><p><strong>它真的被写出来了。</strong></p><p>至于这个博客以后会变成什么样，现在的我其实也不知道。</p><p>也许文章会越来越多，也许会不断修改网站的样式，也许某一天回头看现在的设计，会觉得处处都是问题。</p><p>甚至连这篇文章本身，几年之后再读，大概也会觉得有些青涩。</p><p>不过这恰恰也是记录的意义。</p><p>如果所有东西都等到“足够成熟”之后才开始，那么很多属于当下的东西，反而永远不会留下来。</p><p>所以，就从这里开始吧。</p></section>
<section><h1>写给以后<a href="#写给以后"><span>#</span></a></h1><p>我很好奇几年后的自己重新看到这里时，会是什么感觉。</p><p>那时候这个网站还在吗？</p><p>页面会变成什么样？</p><p>现在正在做的事情，有多少坚持了下来？</p><p>曾经觉得很重要的问题，后来有没有得到答案？</p><p>也许未来的我已经忘记了写下这篇文章时的心情。</p><p>那也没关系。</p><p>至少此刻的我知道，在某个普通的夜晚，我终于给自己一直想做的事情开了一个头。</p><p>没有特别隆重的仪式，也没有什么值得纪念的大事件。</p><p>只是打开编辑器，写下了一些文字。</p><p>于是，这个原本空荡荡的博客里，有了第一篇文章。</p><p>而故事，大概也就从这里开始了。</p><div><div><div></div><div>Tip</div></div><div><p>如果读到这里，你还想再多了解一点我，或者这个网站本身，也欢迎去看看 <strong><a href="/about/">关于我</a></strong> 和 <strong><a href="/about-website/">关于本站</a></strong>。</p></div></div><hr /><p><em>说起来，我决定把今天（2026年9月6日）作为网站的“生日”哦！</em></p></section>]]></content>
    </entry>
</feed>
