<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>陈鑫宇</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://4choor3.github.io/img/avatar.jpg</icon>
  <id>https://4choor3.github.io/</id>
  <link href="https://4choor3.github.io/" rel="alternate"/>
  <link href="https://4choor3.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 陈鑫宇</rights>
  <subtitle>A 股量化 / Python / 工具实践记录</subtitle>
  <title>陈鑫宇</title>
  <updated>2026-10-06T14:40:00.000Z</updated>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="blog" scheme="https://4choor3.github.io/tags/blog/"/>
    <category term="随笔" scheme="https://4choor3.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p>在脑子不清醒的时候，处于一个曲水流觞，醉酒当歌，浩浩乎如冯虚御风的状态。<br>写下一些无厘头的、神奇的语言。<br>是成不了兰亭集序那样的精彩诗篇的。<br>总归是写下的内容，总归是有价值的，总归是有用的。</p><blockquote><p>“自由”一词，在政治学领域已经泛滥了，所以先澄清一下：<br>本文所说的“自由”，主要偏重于和“人权”及“民权”相关的自由——比如：思想的自由、言论的自由、支配私有财产的自由、使用网络的自由……<br>——<a href="https://program-think.blogspot.com/2015/08/Technology-and-Freedom.html">编程幻想 《“对抗专制、捍卫自由”的N种技术力量》</a></p></blockquote><p>“翻墙是违法行为。”<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>于是，一堵墙便有了它的正当性。</p><p>至于墙的另一边有什么，人民应该知道什么，不应该知道什么，似乎不再需要由人民自己决定。</p><p>我们被告知，墙外的信息并不可靠，境外势力居心叵测，互联网需要管理，社会需要稳定。</p><p>这些理由听起来似乎无懈可击。</p><p>一个人有可能被谎言欺骗，也有能力辨别真假,<br>一个人可能受到错误观点的影响，也可以通过阅读与思考修正自己的判断。</p><p>为什么不让人们自己判断？<br>为什么要在接触信息之前，就预先划定一道边界？</p><p>如果人民有能力独立思考，那么就应当允许接触信息、比较观点、作出判断；如果一种观点只能依靠隔绝其他信息来维持，那么这种做法究竟是在保护，还是在限制？</p><p>互联网本应连接世界。人们可以借此了解不同的文化，阅读不同国家的新闻，接触不同的思想，讨论彼此的生活。</p><p>但这堵墙让这些事情不再理所当然。</p><p>它限制的不仅是网站的访问，更是信息流动的范围。当某些新闻、观点与讨论难以进入人们日常接触的信息环境时，人们能够了解到什么，便在一定程度上受到这种环境的支配。</p><p>而一个人无法了解的事情，往往也很难成为他思考与讨论的对象。</p><br><p>《中华人民共和国宪法》规定：“中华人民共和国公民有言论、出版、集会、结社、游行、示威的自由。”<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p><p>或许吧。</p><p>在国内的社交平台上，一条评论在发布后悄无声息地消失；一篇帖子突然被删除；一个账号突然被封禁。</p><p>你留下了一句实话，刷新页面后，它便不复存在，仿佛从未被说出口。</p><p>官方媒体在发布新闻时，直接关闭评论区。<br>新闻照常发布，报道照常播出，官方的声音可以迅速传遍网络，而公众却连留下意见的机会都没有。</p><p>官方发布什么、如何发布、发布多少，普通人却未必有权回应。<br>官方媒体把自己的声音传遍网络，公众却可能连一句反驳、质疑或不满都无法留下。</p><p>评论区和弹幕里刷屏的不见活人的👍👍👍、🌹🌹🌹、🫰🫰🫰。<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p><p>有些词不能出现，有些人物不能直接提及。</p><p>维尼熊是敏感词，包子也是。<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup></p><p>于是，人们开始学会绕开语言本身。</p><p>不能说名字，就换一个称呼；不能直接表达，就使用谐音、代称和隐喻。不能公开讨论，就把真正想说的话藏进看似无关的句子里。</p><p>有些词，只有了解背景的人才能看懂。<br>荒诞的句子，实际上是在讨论一个无法被直接讨论的话题。</p><p>语言并没有消失，只是被逼得越来越隐晦。</p><p>不能直呼其名，就用代称；不能直接批评，就用谐音；不能公开讨论，就用隐喻、反讽和看似无关的词语，把真正想说的话藏进去。</p><p>于是，维尼熊、包子等，便不再只是普通的词语。它们成了暗号，成了被删掉的名字重新返回公共空间的方式，也成了人们在高压审查之下彼此辨认、彼此回应的语言。</p><p>人们只能故意说得不那么直接，故意留下一点模糊，让机器无法准确识别，让审核者难以轻易下手，也让真正想表达的人能够看懂其中的意思。</p><p>人们当然知道哪些词可能触发审查，哪些句子可能导致删帖，哪些话题可能让账号被禁言。<br>但他们并没有因此放弃表达，而是在不断试探边界：换一个字，改一个音，拆开一个词，借用一个表情，写一句表面无关、实际上人人都明白的话。</p><p>审查越严密，暗语就越复杂；禁忌越多，隐喻就越密集。</p><p>荒诞、含糊甚至莫名其妙的表达，往往并不是没有意义，而是因为真正的意义不能被直接写出来。人们用擦边的词汇绕开封锁，用隐晦的方式传递愤怒，用玩笑包裹批评，用表面的荒诞保存真实的记忆。</p><p>不是没人敢说，而是不能用原本的方式说。</p><p>把反抗藏进谐音，把讽刺藏进玩笑，把质疑藏进一句看似无害的话里。只要还有人能够看懂，只要这些话还能在审查的缝隙中流动，官方试图制造的沉默就没有真正完成。</p><p>被删掉的是字面，无法被彻底删除的，是人们想要表达的意志。</p><p><strong>人民过得快乐吗？</strong><br>官方说，人民是快乐的。<br>那么，我就快乐吧。</p><p>毕竟，新闻里的经济持续增长，城市里高楼林立，宣传片中的人民安居乐业，生活似乎一天比一天富足。至于现实中的人究竟如何感受，生活里又有多少疲惫、困惑与不如意，似乎并没有那么重要。</p><p>只要宏大的叙事足够完整，个体的感受便可以被放到一边；只要宣传中的生活足够明亮，现实中的阴影就仿佛不值一提。</p><p>如果有人提出质疑。<br>那么，问题定不在现实本身，而在于这个人“不够积极”“不懂感恩”或者“被错误思想影响”。</p><p>官方说人民幸福，所以人民就必须幸福；<br>官方说社会稳定，所有不满都必须被解释为少数人的问题；<br>官方说一切都在变好，那么那些感到失望、疲惫和无力的人，就只能怀疑自己。</p><p>所谓的言论自由，不只是法律条文中一句看上去庄严的承诺，也不只是允许你在没有触碰权力和禁区的情况下说几句无关痛痒的话。</p><p>你可以赞美，但不能质疑；可以歌颂，但不能追问；可以转发官方结论，却不能提出不同答案。</p><br><blockquote><p>每一個人，既是時代的英雄，亦是時代的一粒沙。那些青春的熱血和勇氣，不應該成為沒有人念的悼詞。那些追殺學生的軍人，也不會因為他們在執行任務而不是劊子手。<br>八九六四，不僅鑲嵌在中國與世界的歷史裡，這一場席捲了千萬人的運動，他們就是我們，就是我們身邊的每一個人。<br>——<a href="https://8964museum.com/">六四記憶‧人權博物館</a></p></blockquote><p>历史不该被遗忘。<br>至少，不应该只记住那些被允许记住的历史。</p><br><p>中国的政治教育，相较于资本主义国家，有着更强的存在感。<br>尤其是爱国主义教育。</p><p>中国人有爱国的义务。<sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup>。</p><p>爱国不是一种情感，而是一种政治立场。</p><br><p>“当然，行是行的，这固然很好，可是千万别闹出什么乱子。”<sup class="footnote-ref"><a href="#fn6" id="fnref6">[6]</a></sup><br>别里科夫不过是一个希腊文教师。他能把自己的恐惧变成周围人的恐惧，却终究没有足够的权力，让整个小镇都按照他的意愿生活。<br>如果赋予他权利，整个小镇得回归封建社会。</p><p>当代中国的许多管理体系中，都能看到某种相似的逻辑：能不动就不动，能不改就不改。<br>即使眼前显然存在更好的解决方案，也未必有人愿意迈出一步。<br>改革意味着风险，意味着责任，意味着原有秩序可能受到冲击，维持现状，意味着安全，意味着稳定。<br>科技进步与我何干？<br>我只是个拿工资办事的。</p><p>于是，问题不再是谁能把事情做得更好，而是谁能保证不出乱子。</p><p>越来越多的别里科夫坐上了更高的位置。他们不再只是把自己装进套子里，而是试图把整个社会也装进去。他们守着陈旧的规则，维护着僵化的秩序，把不出错视为最大的成绩，把不改变视作最稳妥的选择。</p><p>至于那些更好的方案，那些不同的声音，那些可能打破现状的尝试——</p><p>一棒子打死。</p><p>解决提出问题的人可比解决问题简单多了。</p><br><p>官僚体系的保守，让一个社会难以向前迈进。<br>对思想与信息的控制，则能让人们逐渐失去判断它究竟有没有向前迈进的能力。</p><p>文字狱实在是太聪明了。<br>知识分子不敢讲，普通人接触不到，讨论者担心后果，记录者逐渐沉默。<br>人们不知道自己不知道什么，更不知道那些从未被允许讨论的事情究竟意味着什么。没有比较，就很难辨别优劣；没有完整的信息，就很难形成独立的判断。当某段历史从课本、媒体与公共讨论中消失，它甚至不必被所有人否认，只需要不再被人提起，就足以逐渐淡出一代人的记忆。</p><p>经济发展可能因此错失机会，科技进步可能因此受到阻碍，社会也可能因为无法及时纠正错误而付出更大的成本。<br>但这又有什么关系呢？</p><p>1949年建立的“新中国”，名义上是“人民共和国”，实质上是“党天下”。<sup class="footnote-ref"><a href="#fn7" id="fnref7">[7]</a></sup></p><p>如果统治的目标不是让社会变得更好，而是让现有秩序尽可能长久地维持下去，那么发展迟缓或许也算不上最重要的问题。<br>毕竟，一个人只有见过更好的生活，才会意识到自己正在忍受怎样的生活；一个人只有了解不同的历史，才可能追问为什么自己的历史只能有一种讲法。<br>封闭信息，至少可以让这些问题更晚出现。<br>有些历史必须被遗忘，有些作品必须被封堵，有些声音必须被消除。</p><p>至于<a href="https://zh.wikipedia.org/wiki/%E5%85%AD%E5%9B%9B%E4%BA%8B%E4%BB%B6">六四</a>、<a href="https://zh.wikipedia.org/wiki/%E7%99%BD%E7%B4%99%E9%81%8B%E5%8B%95">白纸</a>这样的事件，更是需要被尽可能地隔离于公共记忆之外。</p><p>倘若它们只是毫无意义的闹剧，只是经不起推敲的谣言，又何必如此忌惮人们去了解它们？</p><p>可有些事情偏偏不能说，有些问题偏偏不能问，有些记忆偏偏不能留下。</p><p>于是，我们被要求热爱国家，却未必被允许完整地认识它，<br>被要求铭记历史，却未必能够自由地讨论历史，<br>被要求相信现有秩序是最好的，却又被隔绝在那些可能证明它并非最好的信息之外。</p><br><p>历史不该被遗忘。</p><p>那些曾经站出来的人，不应该只存在于某些人的私人记忆里；那些曾经发生过的事，也不应该因为不再被提起，就仿佛从未发生。</p><p>而遗忘最可怕的地方，或许就在于：当一代人终于忘记了那些曾经发生过的事情，他们甚至可能不再明白，自己究竟失去了什么。</p><br><blockquote><p>我们的存在，就是对恶意最大的反抗。<br>——<a href="https://mtf.wiki">MtF.wiki</a></p></blockquote><p>2026 年，我开始频繁地刷X。</p><p>这是一个消磨时间的地方。</p><p>我可以浏览各种信息，看新闻，看讨论，看一些与自己的生活并无太大关联的事情。国内那些令人不安的近况，内网难以看到的新闻，以及来自不同角落的声音，都在这里交织在一起。</p><p>大多数内容看过便忘了，偶尔遇到感兴趣的，也不过是停留片刻。</p><p>后来，我开始频繁地刷到彩虹旗<sup class="footnote-ref"><a href="#fn8" id="fnref8">[8]</a></sup>。</p><p>起初只是偶然。一个帖子，一张图片，几句陌生的讨论。我不太了解其中的含义，却总会下意识地点进去看看。</p><p>看得越多，算法便推送得越多。就这样，一个过去对我而言相当陌生的群体，逐渐进入了我的视野。</p><p>我开始了解LGBT群体，接触跨性别者的经历，阅读那些关于性别认同、自我接纳与社会偏见的讨论。我看见了许多与自己原先想象不同的生活，也发现，自己过去所不知道的事情，远比想象中多得多。</p><p>开始真正了解起她们，事情便不再只是好奇那么简单。</p><p>我开始翻看她们过去的帖子，点进某个头像，却发现这个账号已经许久没有更新。而最上面那条帖子的评论里，却满是“晚安”。</p><p>有时，还能看到讣告。</p><p>有人留下了告别的文字，有人甚至没能留下什么。一个曾经活跃的人，就这样成为了他人记忆中的一部分。</p><p>我逐渐意识到，在这些看似平常的网络交流背后，存在着一些远比帖子本身沉重的现实。</p><p>对于一些MtF而言，生活并不是简单地做自己，然后等待他人理解。</p><p>她们可能要面对家人的不理解、周围人的歧视，以及求学、就业、医疗与社会关系中的种种阻碍。她们需要花费巨大的精力，才能争取到一些原本不该如此艰难的东西：被认真对待，按照自己的意愿生活，拥有一个可以期待的未来。</p><p>而在缺少可靠支持的环境中，连寻找信息也可能成为一道难关。许多经验散落在个人帖子里，许多问题只能自己摸索，走过的弯路也未必有人告诉后来者。</p><p>但我也因此看到了另一面。</p><p><a href="https://mtf.wiki">MtF.wiki</a></p><p><a href="https://one-among.us/">One Among Us</a></p><p><a href="https://rle.wiki/">RLE.wiki</a></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><strong>每一个名字，都曾经对应着一个具体的人。</strong></p><p>她也曾期待过一段关系，认真地学习某种技能，兴致勃勃地分享一件小事。她也可能只是希望明天比今天好一点。</p><p>这些愿望并不宏大。</p><p>普通到不应该成为奢望。</p><p>我原本只是想了解一个群体，后来却开始在意那些人的命运。</p><p>我希望她们能够得到更多支持与理解，能够学习、工作、爱人，也能够被爱。<br>我希望那些仍在寻找答案的人，能够找到可靠的资料，那些感到孤独的人，能够遇见愿意倾听自己的人。</p><p>我也希望，那些正在努力生活的人，不必总是在解释自己、保护自己与寻找退路之间耗尽力气。</p><p>我不知道这样的日子究竟何时才能到来。</p><p>但我希望它能够到来。</p><p>愿离去的人被记得，愿仍在路上的人能够平安地走下去。</p><p>愿她们终有一天，不必再仅仅为了存在本身而反抗。</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>《中华人民共和国计算机信息网络国际联网管理暂行规定》 <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>《中华人民共和国宪法》 <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p>见抖音、bilibil等平台的人名日报评论区 <a href="#fnref3" class="footnote-backref">↩︎</a></p></li><li id="fn4" class="footnote-item"><p>见维基百科中“對習近平的負面稱呼”栏目 <a href="#fnref4" class="footnote-backref">↩︎</a></p></li><li id="fn5" class="footnote-item"><p>《中华人民共和国宪法》第五十四条 <a href="#fnref5" class="footnote-backref">↩︎</a></p></li><li id="fn6" class="footnote-item"><p>契诃夫 《装在套子里的人》 <a href="#fnref6" class="footnote-backref">↩︎</a></p></li><li id="fn7" class="footnote-item"><p>刘晓波《零八宪章》 <a href="#fnref7" class="footnote-backref">↩︎</a></p></li><li id="fn8" class="footnote-item"><p>这里指的是以跨性别旗为背景的账号头像🏳️‍⚧️ <a href="#fnref8" class="footnote-backref">↩︎</a></p></li></ol></section>]]>
    </content>
    <id>https://4choor3.github.io/2026/10/06/xia-ji-ba-xie/</id>
    <link href="https://4choor3.github.io/2026/10/06/xia-ji-ba-xie/"/>
    <published>2026-10-06T10:00:00.000Z</published>
    <summary>在脑子不清醒的时候写一些不是写的东西。ps：好废话的表达（貌似看不到`description`呢）</summary>
    <title>浑浑噩噩的写一些乱七八糟的东西i</title>
    <updated>2026-10-06T14:40:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <category term="备份" scheme="https://4choor3.github.io/tags/%E5%A4%87%E4%BB%BD/"/>
    <content>
      <![CDATA[<blockquote><p><a href="https://zh.wikipedia.org/wiki/%E9%98%AE%E6%99%93%E5%AF%B0">阮晓寰</a>（1977年6月10日—），中华人民共和国IT行业从业者、博客作家、异见人士。其从2009年开始在Blogger上以“编程随想”为名撰写文章，内容包括各种网络安全知识、翻墙方法、匿名教学以及政治评论。2021年5月10日，阮晓寰被上海警方以涉嫌煽动颠覆国家政权罪逮捕。2023年2月10日，上海市第二中级人民法院以煽动颠覆国家政权罪判处其有期徒刑7年、剥夺政治权利2年，并处罚金2万元；2024年12月13日，上海市高级人民法院驳回上诉并维持原判。</p><p>——维基百科</p></blockquote><br><p>编程随想<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>的<a href="https://program-think.blogspot.com/">博客</a>和<a href="https://program-think.blogspot.com/2021/05/share-books.html">电子书库</a>在国内长期被GFW封禁。</p><p>原站在墙内打不开，收录方式一直是靠托管平台的备份加<code>BT Sync</code>同步。</p><p>自2021被捕后，博客永远的就停在了那一刻。</p><p>26年的今天，我还能打开<a href="https://program-think.blogspot.com/">博客</a>，访问他的<a href="https://github.com/programthink">GitHub</a>，访问他的<a href="https://x.com/programthink">X</a>，在我看来，这如同一个奇迹一般。因为在网上见识过太多太多的被ban掉的内容，我不得不考虑做一份备份记录一下。</p><p>备份主要内容：分享的书籍和博客。</p><h2 id="统计"><a class="headerlink" href="#统计"></a>统计</h2><p>博客正文710篇，2009到2021年4月。</p><p>标签里政治279篇、时事评论175篇、信息安全100篇、历史84篇、翻墙50篇。</p><p>电子书2146个文件。</p><p>按一级目录分11类：IT、军事、历史、哲学、心理学、政治、文艺、社会学、科普、管理、经济。</p><table><thead><tr><th style="text-align:center">PDF</th><th style="text-align:center">EPUB</th><th style="text-align:center">MOBI</th><th style="text-align:center">TXT</th><th style="text-align:center">CHM</th><th style="text-align:center">ZIP</th><th style="text-align:center">AZW3</th><th style="text-align:center">DJVU</th></tr></thead><tbody><tr><td style="text-align:center">1316</td><td style="text-align:center">379</td><td style="text-align:center">204</td><td style="text-align:center">99</td><td style="text-align:center">76</td><td style="text-align:center">40</td><td style="text-align:center">15</td><td style="text-align:center">9</td></tr></tbody></table><p>扫描件占绝大多数，同一本书常存着两三种格式，所以文件数比书目数多得多。</p><p>总文件超过16 GB,一共需要分17次推完。</p><p>10.04凌晨02:25开始，04:28推完，17次提交。</p><p>需要按体积切批，GitHub每次提交总量不能大于2 GB，必须切分上传。</p><p>第一批把blog那6162个小文件凑够1.44 GB。后面13批全是books，一百多到三百多个不等，每批是1.4 GB上下。间隔五到七分钟。</p><p>不过好在没有超过100 MB的文件，否则就不好上传了</p><p>HEAD上所有文件的字节加起来<code>16.19 GB</code>。<code>.git</code>目录达到15 GiB：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">count: 2489      size: 13.98 GiB</span><br><span class="line">packs: 1         size-pack: 1.22 GiB</span><br></pre></td></tr></table></figure><h2 id="使用"><a class="headerlink" href="#使用"></a>使用</h2><p>访问作者bolg或通过直接打开<code>blog/html/index.html</code>使用。</p><p>CHM已经编译好，依赖<code>hhc.exe</code>和<code>hha.dll</code>，<code>file</code>判定是1999年的PE32 Intel 80386二进制，只支持Windows。生成<code>.hhp</code>/<code>.hhc</code>配置的脚本不在仓库里，EPUB同理，想重新生成需要自己找工具链。</p><p><code>__所有电子书的清单__.html</code>和<code>所有电子书目录的密钥__.txt</code>都是描述的下载链接，已经分根据密钥通过Resilio Sync下载到了books，下载方法和指导详见作者<a href="https://github.com/programthink/books">GitHub</a>。</p><h2 id="公开"><a class="headerlink" href="#公开"></a>公开</h2><p>仓库：<a href="https://github.com/4choor3/programthink-Backup">programthink-Backup</a></p><p>目前是public，可供自由下载。<s>被ban掉前</s></p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>阮晓寰 <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]>
    </content>
    <id>https://4choor3.github.io/2026/10/04/programthink-backup/</id>
    <link href="https://4choor3.github.io/2026/10/04/programthink-backup/"/>
    <published>2026-10-04T11:20:00.000Z</published>
    <summary>「编程随想」的博客全文和电子书库，搬进自己的仓库做备份</summary>
    <title>编程随想的博客和书籍备份</title>
    <updated>2026-10-04T12:15:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <category term="备份" scheme="https://4choor3.github.io/tags/%E5%A4%87%E4%BB%BD/"/>
    <category term="OneDrive" scheme="https://4choor3.github.io/tags/OneDrive/"/>
    <content>
      <![CDATA[<p>习惯做多份备份。</p><p>上学期间，每学期都会产生很多docx、pptx、pdf等，或许是结课论文，或许是课堂汇报ppt，或许是要交的材料文件，总爱做一份备份。<br>既是记录生活，也是存下过去。</p><p>由于我有两台电脑，而且手机也是也需要查看，所以云端必不可少。<br>往往还需要发给别人看，需要发送链接。</p><p>国内那些云盘有多难用，我就没必要赘述了。</p><p>过去一直用的<code>OneDrive</code>。但只有5GB免费空间，同步又需要走微软服务器，需要常驻后台进程，还会有延迟和强占带宽。</p><p>如果两台电脑同时开着同一个docx更麻烦。同步回来多出一个名字带(2)的副本，哪个是新的得自己判断。</p><p>最好的记录方式当然是<code>git</code><s>实在是小题大做</s>。</p><p>但是既然可取，何不试试？<br>私有仓库，564个文件，711.1MiB，12个提交。</p><h2 id="拦截大文件"><a class="headerlink" href="#拦截大文件"></a>拦截大文件</h2><p>由于GitHub的限制，单文件不可以超过100MB，所以做了一个钩子来避免上传超过100MB的文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/your/path/cloud-storage/.git/hooks/pre-commit</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">MAX_SIZE=$((<span class="number">100</span> * <span class="number">1024</span> * <span class="number">1024</span>))   <span class="comment"># 100MB</span></span><br><span class="line">git diff --cached --name-only --diff-filter=ACM | <span class="keyword">while</span> <span class="built_in">read</span> f; <span class="keyword">do</span></span><br><span class="line">  size=$(<span class="built_in">stat</span> -f%z <span class="string">&quot;<span class="variable">$f</span>&quot;</span>)</span><br><span class="line">  <span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$size</span>&quot;</span> -gt <span class="string">&quot;<span class="variable">$MAX_SIZE</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    git restore --staged <span class="string">&quot;<span class="variable">$f</span>&quot;</span>     <span class="comment"># 移出暂存区，不阻止提交</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"><span class="built_in">exit</span> 0</span><br></pre></td></tr></table></figure><h2 id="文件组成"><a class="headerlink" href="#文件组成"></a>文件组成</h2><table><thead><tr><th style="text-align:center">类型</th><th style="text-align:center">个数</th><th style="text-align:center">体积</th></tr></thead><tbody><tr><td style="text-align:center">mp4</td><td style="text-align:center">3</td><td style="text-align:center">193.8 MiB</td></tr><tr><td style="text-align:center">pdf</td><td style="text-align:center">74</td><td style="text-align:center">184.0 MiB</td></tr><tr><td style="text-align:center">ppt / pptx</td><td style="text-align:center">34</td><td style="text-align:center">107.7 MiB</td></tr><tr><td style="text-align:center">docx</td><td style="text-align:center">63</td><td style="text-align:center">98.8 MiB</td></tr><tr><td style="text-align:center">doc</td><td style="text-align:center">30</td><td style="text-align:center">52.0 MiB</td></tr><tr><td style="text-align:center">jpg</td><td style="text-align:center">317</td><td style="text-align:center">66.2 MiB</td></tr><tr><td style="text-align:center">md</td><td style="text-align:center">14</td><td style="text-align:center">0.2 MiB</td></tr></tbody></table>]]>
    </content>
    <id>https://4choor3.github.io/2026/10/04/cloud-storage/</id>
    <link href="https://4choor3.github.io/2026/10/04/cloud-storage/"/>
    <published>2026-10-03T19:00:00.000Z</published>
    <summary>把OneDrive里的课程资料整个交给Git之后，`.git`长到了670MB。拆一遍对象库：那次562个文件重命名一个字节都没多写，真正占地方的是一次被reset掉的测试提交留下的110MB。</summary>
    <title>学期资料备份GitHub</title>
    <updated>2026-10-03T19:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <category term="macOS" scheme="https://4choor3.github.io/tags/macOS/"/>
    <category term="开源" scheme="https://4choor3.github.io/tags/%E5%BC%80%E6%BA%90/"/>
    <content>
      <![CDATA[<p>macOS系统的桌面壁纸是一些240帧的视频，但只能在屏幕保护界面播放。</p><p>那么高清好看的壁纸居然不可以作为动态壁纸播放！太糟糕了！</p><p>市面上有很多动态壁纸的软件，但是他们大多软件功能复杂，操作繁琐，价格高昂，语言不便，并且自带下载视频，而不是播放系统壁纸视频。</p><p>项目已在GitHub开源，地址：<a href="https://github.com/4choor3/Lumina">Lumina</a></p><p>9个Swift文件，2995行，Xcode Command Line Tools编译，无需引第三方库。</p><p>同步发布软件包，可以直接下载使用。</p><p>第一次运行会自动寻找系统自带的Aerial素材，在<code>~/Library/Application Support/com.apple.wallpaper/aerials/videos/</code>下面，缓存的壁纸文件mov路径，也可以自备视频。</p><h2 id="软件制作困难"><a class="headerlink" href="#软件制作困难"></a>软件制作困难</h2><h3 id="遮挡"><a class="headerlink" href="#遮挡"></a>遮挡</h3><p>视频壁纸并未直接替代原壁纸图片，而是选择新建图层覆盖。</p><p>macOS的桌面有多层窗口。用<code>CGWindowListCopyWindowInfo</code>dump出来如下：</p><table><thead><tr><th style="text-align:center">层级</th><th style="text-align:left">owner</th></tr></thead><tbody><tr><td style="text-align:center">-2147483624</td><td style="text-align:left">程序坞，系统自己的壁纸</td></tr><tr><td style="text-align:center">-2147483603</td><td style="text-align:left">访达，桌面图标，铺满整块屏</td></tr><tr><td style="text-align:center">-2147483601</td><td style="text-align:left">通知中心小组件</td></tr><tr><td style="text-align:center">0</td><td style="text-align:left">普通应用窗口</td></tr></tbody></table><p>动态壁纸层需要位于系统壁纸层与桌面图标层之间。最终取值为<code>-2147483610</code>，比图标层低7层。</p><p>设置中存在名为“壁纸盖住桌面图标”的开关。</p><p>4K视频持续解码，需要在电池供电时暂停。电池状态通过IOKit获取：调用<code>IOPSCopyPowerSourcesInfo</code>获取电源信息列表，读取<code>kIOPSPowerSourceStateKey</code>；若其值等于<code>kIOPSBatteryPowerValue</code>，则判定为电池供电。</p><p>全屏模式检测不使用辅助功能。改为遍历窗口列表中<code>layer == 0</code>的窗口，取叠放顺序最靠前的窗口，计算其与屏幕的交集面积；若覆盖面积超过98.5%，则判定为全屏。</p><p>遮挡检测同样遍历窗口列表，但选项不同。窗口列表按叠放顺序提供，索引越靠前表示层级越高。因此需要查找位于壁纸窗口之前、<code>layer &gt;= 0</code>且覆盖面积超过98%的窗口。此处不能添加<code>.excludeDesktopElements</code>；添加后会将壁纸窗口本身过滤掉，导致遮挡检测始终返回未被遮挡。全屏检测可以添加该选项，因为该场景本应忽略桌面层。</p><p>锁屏和屏幕休眠通过通知处理。在<code>DistributedNotificationCenter</code>上监听<code>com.apple.screenIsLocked</code>和<code>com.apple.screenIsUnlocked</code>，并在<code>NSWorkspace</code>上监听<code>screensDidSleep</code>和<code>screensDidWake</code>；事件到达时立即置位。电池和全屏状态每2秒轮询一次，仅在状态实际翻转时回调。</p><p>满足暂停条件时，日志记录一行“暂停播放（全屏应用 / 电池供电）”，并并列列出原因。</p><p>每块屏幕对应一个独立的<code>NSWindow</code>，通过遍历<code>NSScreen.screens</code>完成创建。插拔显示器或更改分辨率会发送<code>didChangeScreenParametersNotification</code>，且一次操作会连续发送多次；因此增加0.6秒防抖后再重建。</p><p>切换Space或激活其他应用后，偶尔会出现窗口不在当前Space的情况。在<code>didActivateApplicationNotification</code>和<code>activeSpaceDidChangeNotification</code>之后延迟0.35秒再次调用<code>orderFrontRegardless</code>。该数值经实验确定；若延迟更短，系统尚未完成窗口位置调整。</p><h3 id="倍速"><a class="headerlink" href="#倍速"></a>倍速</h3><p>AVPlayer在2×以上存在硬性上限。使用macOS自带Aerial航拍素材测试（3840x2160、HEVC Main 10、239.76 fps），2.0×可稳定达到120帧/秒；调整至2.2×后降至每秒0至1帧，<code>AVPlayerItem.canPlayFastForward</code>也为false。仅相差0.2×即完全停止。</p><p>规避方法是将倍速预置到时间轴。使用<code>AVMutableComposition</code>将整段视频<code>scaleTimeRange</code>压缩至1/倍速 的时长，播放器仅以1×播放。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> compressed <span class="operator">=</span> <span class="type">CMTimeMultiplyByFloat64</span>(asset.duration, multiplier: <span class="number">1.0</span> <span class="operator">/</span> speed)</span><br><span class="line">track.scaleTimeRange(full, toDuration: compressed)</span><br></pre></td></tr></table></figure><p>时间轴压缩后，修改倍速必须重建内容，而重建会导致播放进度归零。修正方法是在重建前记录源视频进度（<code>currentTime × bakedSpeed</code>），重建后seek到<code>position / factor</code>。</p><p>另有一个较不显著的问题：将“是否需要预置倍速”表示为布尔值并纳入配置指纹。3×、4×、5×均对应true，配置签名不变，因此不触发重建，始终按首次的3×播放。改为比较实际倍速值后行为正确。</p><p>重建还会导致短暂黑屏。<code>replaceCurrentItem</code>会清空图层缓冲。先移除旧层再创建新层，期间必然出现黑屏。使用旧层快照进行遮挡也不可行，因为<code>AVPlayerLayer.contents</code>恒为空，播放器不通过该通道输出。</p><p>最终采用双缓冲。保持两个<code>(AVPlayer, AVPlayerLayer)</code>实例，在不可见实例上更换item、执行seek并开始播放，轮询直到<code>isReadyForDisplay</code>为真且时间轴推进（最多等待1秒），然后交换可见性；此时才暂停旧实例并调用<code>replaceCurrentItem(with: nil)</code>释放解码器。轮播切换复用同一路径，手动“下一幅”同样不会出现黑屏。</p><p>另有一处与倍速相关的问题：<code>AVPlayer.play()</code>等价于<code>rate = 1.0</code>，因此先设置<code>rate</code>再调用<code>play()</code>会覆盖速度。表现为初始速度正确，但暂停后恢复时速度变为1×。所有起播路径统一改为<code>playImmediately(atRate:)</code>。</p><p>高倍速下卡顿是另一类问题。4×下出现周期性卡顿。最初认为由解码能力不足导致，但实测时间推进准确，CPU占用仅2.3%。实际原因是帧生成速率与上屏速率不匹配：30 fps素材在4×下需要120帧/秒，而AppKit窗口默认不以ProMotion的120 Hz满速运行，需要显式申请。macOS不提供<code>CALayer.preferredFrameRateRange</code>，该API属于iOS；只能使用<code>NSView.displayLink</code>。声明后可通过回调频率验证，日志可输出<code>display link实测120.5 Hz</code>。</p><p>5×属于物理限制。30 fps素材在5×下需要150帧/秒，120 Hz屏幕最多显示120帧/秒，必然丢帧。设置界面按“屏幕刷新率 ÷ 源帧率”计算该素材的最高无损倍速，超过该值时以橙色标记，无需逐档测试。</p><h3 id="开机自启"><a class="headerlink" href="#开机自启"></a>开机自启</h3><p>开机自启配置写入<code>~/Library/LaunchAgents/com.chenxinyu.lumina.plist</code>。其中<code>ProgramArguments</code>包含<code>--autostart</code>，设置<code>RunAtLoad</code>，并将<code>LimitLoadToSessionType</code>设为<code>Aqua</code>。执行<code>bootstrap</code>前先执行一次<code>bootout</code>；否则重复切换自启开关时会返回<code>service already loaded</code>。</p><p>应用采用ad-hoc签名（codesign --sign -），不包含开发者证书。用户下载并解压后，Gatekeeper会拦截。处理方式有两种：在“系统设置 → 隐私与安全性”中选择“仍要打开”；或执行<code>xattr -dr com.apple.quarantine /Applications/Lumina.app</code>。该操作应在构建脚本中一并执行。</p><p>此外，将<code>.app</code>组装在<code>mktemp</code>创建的临时目录中，项目内仅保留zip文件。macOS 26的App库依据LaunchServices注册表收录bundle；只要项目目录中曾出现<code>.app</code>，就会与<code>/Applications</code>中的副本共同注册，导致显示两个图标。</p>]]>
    </content>
    <id>https://4choor3.github.io/2026/10/04/lumina/</id>
    <link href="https://4choor3.github.io/2026/10/04/lumina/"/>
    <published>2026-10-03T19:00:00.000Z</published>
    <summary>自己写了个把本地视频当macOS桌面壁纸的小工具。记下窗口层级怎么卡在桌面图标下面、暂停判定为什么要分四种写，以及倍速撞上AVPlayer的2×上限之后怎么绕过去。</summary>
    <title>macOS桌面动态壁纸</title>
    <updated>2026-10-03T19:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <category term="开源" scheme="https://4choor3.github.io/tags/%E5%BC%80%E6%BA%90/"/>
    <category term="协作" scheme="https://4choor3.github.io/tags/%E5%8D%8F%E4%BD%9C/"/>
    <content>
      <![CDATA[<p><a href="https://rle.wiki">RLE.wiki</a></p><p>「一份 RLE 指北」</p><p><code>Real Life Experience</code>实际生活体验。</p><blockquote><p>欢迎大家来到 RLE.wiki. 这是一份给跨性别者与性别多元群体的 RLE 指南。更多帮助正在路上，敬请期待……</p></blockquote><p>笔者从X得知的这个项目。在翻它的校园栏目的时候。有着一百七十多个学校，大部分城市都能翻出几条。但是没有我的大学。</p><p>怎么可以没有大农专！我写！</p><br><p>写了<code>docs/campus/NJAU.md</code>，162行。开头一张总览表，规则是情况正面用<code>✓</code>、负面用<code>✗</code>、不确定用<code>?</code>、视情况而定用<code>-</code>。</p><p>PR挂上去没多久，Twinsherry在下面留了句话，说LS-KR之前已经从这里撤过一次南京农业大学的条目，作为数据遗产资料的继承人，她希望把旧版本里LGBT相关的部分补进来。</p><p>我当天问了旧内容能在哪个commit或者归档仓库里查到，她给了个链接，PR 586，标题「doc: rm NJAU」，2025年5月合并的，改一个文件，纯删85行。</p><p><s>我一开始就是知道的，那是学姐。只是她后来想不开了，走之前rm了南农相关的文章。git历史我也是看到了的</s></p><br><p>旧版本能找到的东西就是那段差分里剩下的全部。</p><p>其中的LGBT内容和校内医疗相关的说法挑出来，一部分并进正文，部分核实不了的用<code>::: details 旧版资料</code>折叠起来挂在对应小节下面，标注了是上一版投稿人的描述。</p><blockquote><p>::: details旧版资料：上一版投稿人对宿舍搜查的描述（笔者没遇到过，认为是特殊情况，予以保留）</p><blockquote><p>辅导员或学院或学校会定期秘密地搜查学生的宿舍，频率为一个学期至少两次。同班所有人的物品均有被翻动的痕迹。<br>笔者从医院回到宿舍时发现自己的电脑已经是只剩一次机会否则全盘加密锁定的状态。<br>没有藏好的糖（还需要吃的那些）也被收走。<br>带锁是必须的，不要用密码锁。</p></blockquote><p>:::</p></blockquote><p>我没法验证。校医院能不能测六项、益丰大药房有没有可能出现二十多块一盒的补子。</p><p>第二个提交<code>f33b805</code>改了59行删了24行，把那张总览表重新排版对齐，另外补了几处我当时想起来的细节：军训16天、后半程有晚训、每天三个时段分性别训等等。</p><h2 id="审稿尺度"><a class="headerlink" href="#审稿尺度"></a>审稿尺度</h2><p>投稿指南里定的尺子很具体。</p><blockquote><ol><li>不得修改模板中的二级标题，如果认为不需要填写该项目，请注释掉。markdown的注释语法为<code>&lt;!-- --&gt;</code>，例如<code>&lt;!-- ## 其他信息 --&gt;</code>。</li><li>如有必要，可以自由添加多级标题。</li><li>为了观察方便，英文与汉字之间需要添加空格，例如<code>例子 example 例子</code>。</li><li>专有名词缩写注意大小写，例如<code>MtF</code>，具体可以参考<a href="https://mtf.wiki/zh-cn/docs/useful-info/abbreviation/">https://mtf.wiki/zh-cn/docs/useful-info/abbreviation/</a>。</li><li>主观内容请使用引用格式，例如<code>&gt; 引用内容</code>。</li><li>句末需要加上中文句号<code>。</code>。</li><li>如果有引用到静态文件，例如图片视频等，请在<code>docs/assets</code>目录下上传图片（以文档名开头），并在markdown文件中使用<strong>相对路径</strong>的方式引用。例如我的文档名为<code>example.md</code>，在其中引用一张名为<code>Test.png</code>的图片，那么我需要将<code>Test.png</code>改名为<code>example_Test.png</code>，上传在<code>docs/assets</code>目录下，然后在markdown里面使用<code>![](/assets/example_Test.png)</code>的方式引用。</li><li>对于过时内容的处理：<ul><li>影响力持续至今的事情，尽管其已经结束，但为了因果关系完整，仍应予以保留。（举例：2020年，某校因为COVID-19疫情防控措施而取消了某政策，至今没有恢复，但由于2022年12月的防控措施变化，而在近期存在恢复可能的，在等待两年（至2024年12月）、排除其恢复可能后，将“以前的政策”和“受COVID-19疫情防控措施影响”这两句按下方处理）</li><li>已无影响力，但影响力消失不足两年的，予以折叠、标注过时（使用<code>:::details :::</code>语法）。</li><li>已无影响力，影响力消失满两年的，予以删除。</li></ul></li></ol></blockquote><h2 id="部署"><a class="headerlink" href="#部署"></a>部署</h2><p>PR一开就有bot来贴预览地址，，每个PR独占一个Cloudflare Pages部署，改完刷新就能看到修改完的界面是什么样。</p><p><s>好高级！我都不知道GitHub居然还能这样</s></p>]]>
    </content>
    <id>https://4choor3.github.io/2026/10/04/rle-wiki/</id>
    <link href="https://4choor3.github.io/2026/10/04/rle-wiki/"/>
    <published>2026-10-03T19:00:00.000Z</published>
    <summary>记录一次往RLE.wiki提交校园条目的过程：从fork到PR 778合并，中间把一年多前被删掉的旧版本内容又接了回来。</summary>
    <title>给RLE.wiki添加农专院校</title>
    <updated>2026-10-03T19:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <category term="开源" scheme="https://4choor3.github.io/tags/%E5%BC%80%E6%BA%90/"/>
    <category term="SQL" scheme="https://4choor3.github.io/tags/SQL/"/>
    <content>
      <![CDATA[<p>不知道该学什么SQL，和AI交流下来，我一个非计算机本专业生，学习基础的，简单的SQLite好像就够了。<s>简单在哪里啦！</s></p><p>想练SQL一直卡在环境上。在线基本只有长文教程，本地装个数据库又麻烦，适配性不强。不如自己写一个：纯静态页面，双击HTML就能用，离线使用。</p><p>现在有50关，从<code>SELECT</code>一路排到递归CTE。每题写真正的SQL，由SQLite执行后比对结果集，对了才放行。</p><p>仓库在<a href="https://github.com/4choor3/sql-quest">4choor3/sql-quest</a>，在线版<a href="https://4choor3.github.io/sql-quest/">https://4choor3.github.io/sql-quest/</a>。</p><h2 id="底子"><a class="headerlink" href="#底子"></a>底子</h2><p>sql.js把SQLite编译成了WebAssembly，查询全在浏览器本地跑，数据不出机器。</p><p>麻烦在分发。常规做法是一个index.html加一堆js再加个640KB的<code>.wasm</code>，还得起HTTP服务——<code>file://</code>下<code>fetch</code>拿不到本地文件。我把wasm转成base64塞进js，<code>npm run build</code>出一个1.2MB的<code>sql-quest.html</code>，依赖全在里面。base64会让体积涨三分之一，换来的是不用起服务，值。</p><h2 id="题库"><a class="headerlink" href="#题库"></a>题库</h2><p>基础篇是书店业务：作者、书籍、客户、订单、订单明细五张表，25本书、30笔订单。数据里埋了几个坑。有个客户从没下过单，练<code>LEFT JOIN</code>的时候正好拿来分辨<code>COUNT(o.id)</code>和<code>COUNT(*)</code>——后者会把没订单的那行数成1。有本书的<code>genre</code>是NULL，<code>IS NULL</code>和相关子查询里的<code>IS</code>（不是<code>=</code>）都得用它。订单有已完成、已取消、待付款三种状态，聚合之前不先过滤就算错。</p><p>进阶篇换成校园选课，也是五张表，但<code>courses.prereq_id</code>指向同一张表的<code>id</code>，自连接和递归CTE有真实的层级能展开。另外特意留了5个一节课都没选的学生，不然<code>NOT EXISTS</code>这类题没有正确答案。</p><p>进阶关不难在语法，难在组合。表一多，得自己想清楚从哪张出发、怎么连过去；而且多半要拆两步，先算中间结果再基于它算答案。</p><p><img src="/img/sql-quest/03-advanced.jpg" alt="递归CTE展开课程先修链，右栏自动切到了校园选课库"></p><h2 id="判题"><a class="headerlink" href="#判题"></a>判题</h2><p>答案写法定不下来，没法做文本比对。每关存一份参考SQL，运行时现场算出期望结果，再和你的结果比。四种比法：<code>columns</code>是列名加无序行集合，<code>ordered</code>要求行顺序也对，<code>set</code>只看行集合，<code>scalar</code>比单个数值带浮点容差。</p><p>不通过时给差异清单。测<code>COUNT(*)</code>那个坑的时候，面板直接列出「冯磊0」和「冯磊1」两行，比说一句答案错误有用得多。</p><p><img src="/img/sql-quest/02-diff.jpg" alt="差异面板把写错的那一行直接摆出来"></p><p>另外加了关键字约束。题目要求用<code>LEFT JOIN</code>，你写死<code>WHERE name = '冯磊'</code>凑答案会被拦下。结果对了，但这题就白做了。</p><h2 id="编辑器"><a class="headerlink" href="#编辑器"></a>编辑器</h2><p>一开始就是个textarea。后来想加高亮和补全，发现两个都不好办。</p><p>高亮最后做成了「文字透明的textarea叠在一层<code>&lt;pre&gt;</code>上面」。选区、光标、输入法都还是textarea的原生行为，看到的高亮来自底下那层。两层必须像素对齐，否则文字会重影，所以排版规则只写一处、两层共用，另外textarea用<code>scrollbar-gutter: stable</code>常驻预留滚动条槽位，高亮层补上同样的右内边距，两层的内容盒才等宽。测试里卡的是内容盒宽度、<code>scrollHeight</code>和13项排版属性全等。</p><p>补全要贴着光标弹，得先知道光标在textarea里的像素坐标。用了个镜像div：复刻textarea的计算样式，把光标之前的文本灌进去，再插一个span量它的位置。好处是字号改了自动跟随，不用改代码。</p><p>排序按光标位置分。语句开头给关键字（<code>sel</code> → <code>SELECT</code>），<code>FROM</code>或<code>JOIN</code>后面给表名（<code>bo</code> → <code>books</code>），查询中段给列名（<code>st</code> → <code>stock</code>），打了<code>b.</code>就只给books的列。别名也认：写了<code>COUNT(*) AS book_count</code>，后面打<code>book_co</code>能补出来；<code>WITH city_stat AS (...)</code>之后能补CTE名；派生表后面打<code>b.ti</code>，能从内层SELECT推出<code>title</code>。</p><p><img src="/img/sql-quest/01-autocomplete.jpg" alt="JOIN后面只给表名，列名在那儿是不合法的"></p><p>格式化用sql-formatter。全量包312KB，带20多种方言，这项目只用SQLite，就写了个esbuild插件把方言注册表换成只含sqlite的版本，压到57KB。</p><h2 id="几个坑"><a class="headerlink" href="#几个坑"></a>几个坑</h2><p>WASM实例那块最要命。切关卡要恢复到该关的初始状态，我最初的写法是这样：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> old = <span class="variable language_">this</span>.<span class="property">db</span>;</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">db</span> = <span class="keyword">new</span> <span class="variable language_">this</span>.<span class="property">SQL</span>.<span class="title class_">Database</span>(snapshot);</span><br><span class="line">old.<span class="title function_">close</span>();</span><br></pre></td></tr></table></figure><p><code>this.db</code>平时就是数据集注册表里的常驻实例，把旧的关掉，那套数据就永久废了——之后任何查询都抛<code>ErrnoError: FS error</code>，而且不可恢复。这个bug只在反复跨数据集切换时才触发，只做一套库的话永远碰不到。改成原地替换注册表引用就好了：新实例顶上，注册表指向新的，再关旧的。</p><p>CSS表格一次踩了仨。</p><p><code>max-width</code>对table cell不生效。我给<code>td</code>设了<code>max-width: 320px</code>想截断长文本，实测宽度360px。CSS 2.1里这个属性对表格单元格就是没用，最后把截断压在内层元素上。</p><p>数值列右对齐好看，但表头默认左对齐，两者能差50px以上。得先按列判断是不是数值列（该列所有非空值都是数字），给<code>th</code>和<code>td</code>加同一个class才对齐。这个问题之前一直没被测出来，因为我的几何断言只比了单元格<strong>盒子</strong>的坐标——盒子当然对得齐，但文字能差一大截。后来改用<code>Range</code>量文字的实际位置，按对齐方式比对应边缘。</p><p>还有<code>width: 100%</code>会把多余空间按比例摊给每一列。<code>id</code>列内容只要40px，被撑到90px，看着间距很怪。改成让最后一个文本列吸收剩余宽度——文本列变宽只有好处，数值列变宽只会让数字贴着右边、中间空一大截。</p><p><img src="/img/sql-quest/05-table.jpg" alt="改完之后：文本列左对齐、数值列右对齐，列宽按内容走"></p><p>还有个正则在补全里作祟。在<code>FROM orders o JOIN o</code>后面打下去，候选里给的是<code>order_date</code>（一个列名）、<code>ON</code>、<code>OR</code>、<code>ORDER BY</code>，偏偏没有<code>orders</code>。</p><p>原因是我用一条正则从<strong>第一个</strong><code>FROM</code>开始匹配：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">FROM orders o JOIN o</span><br><span class="line">└──── 整段被吞掉 ────┘</span><br></pre></td></tr></table></figure><p><code>JOIN</code>落在被吞掉的部分里，被当成「这个子句里已经有别的关键字了」，于是判定失败、退化成列名上下文。改成取最后一个引导词再往后看就行。顺带把表名位置的候选也收干净了——那儿本来就只有表名合法，现在<code>JOIN order_d</code>干脆什么都不给，而不是递上一个<code>order_date</code>误导人。</p><h2 id="测试"><a class="headerlink" href="#测试"></a>测试</h2><p>这类工具有个麻烦：错了不一定看得出来。所以写了一套脚本，改完必跑。<code>verify</code>检查50关的参考答案能不能过自己的校验、起始代码会不会被误判成通关；<code>e2e</code>是浏览器端到端的46项；<code>editor</code>管格式化、补全、高亮那56项；<code>fullplay</code>把50关在浏览器里逐个打通；<code>offline</code>验单文件版在<code>file://</code>加断网下能不能跑。</p><p>最后一个是这次新加的：<code>live</code>，部署完实测线上站点能不能真答题。之前只看HTTP 200就以为发布成功了，其实接口通了不代表页面能跑。</p><p><img src="/img/sql-quest/06-allclear.jpg" alt="浏览器里把50关逐个打通之后"></p><h2 id="仓库"><a class="headerlink" href="#仓库"></a>仓库</h2><p>代码、题库、两套示例库都在<a href="https://github.com/4choor3/sql-quest">4choor3/sql-quest</a>。不构建也能用，<code>dist/sql-quest.html</code>下载下来双击就开，放U盘或者发给别人都行。两套库也单独导出了<code>dist/bookstore.sql</code>和<code>dist/campus.sql</code>，可以拖进DBeaver自己练。MIT协议。</p><p>主题做了深浅两套，浅色不是简单反色，强调色压暗到<code>#9a6200</code>，六类语法色在白底上的对比度都在4.8:1以上。</p><p><img src="/img/sql-quest/04-light.jpg" alt="浅色主题"></p>]]>
    </content>
    <id>https://4choor3.github.io/2026/09/27/sql-quest/</id>
    <link href="https://4choor3.github.io/2026/09/27/sql-quest/"/>
    <published>2026-09-26T18:45:00.000Z</published>
    <summary>一个在浏览器里真跑SQL的闯关练习工具。50道关卡、两套示例库，SQLite编译成WebAssembly本地执行。记几个实现细节和踩到的坑：WASM实例的生命周期、表格对齐的三个CSS陷阱、贪婪正则让JOIN后的补全失效。</summary>
    <title>自制SQL学习工具，闯关</title>
    <updated>2026-09-26T19:25:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="硬件" scheme="https://4choor3.github.io/tags/%E7%A1%AC%E4%BB%B6/"/>
    <category term="DIY" scheme="https://4choor3.github.io/tags/DIY/"/>
    <category term="嵌入式" scheme="https://4choor3.github.io/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <content>
      <![CDATA[<p>RP2040开发板，做嵌入式开发</p><p>拿它做点能跑起来的东西，配了一块1.8寸屏：屏幕、电池、主控、充电板。</p><p><img src="/img/rp2040/01-pi-demo-wiring.jpg" alt="桌面原型：YD-RP2040、1.8寸屏、TP4056与500mAh锂电用飞线连成一整套，屏幕正在显示圆周率"></p><p>项目开在<a href="https://github.com/4choor3/rp2040-txt-reader">4choor3/rp2040-txt-reader</a>，接线表在<code>hardware/PINOUT.md</code>，带图的分步制作指南在<code>docs/BUILD_GUIDE.zh-CN.md</code>。</p><h2 id="选型"><a class="headerlink" href="#选型"></a>选型</h2><p>对于板子的选择：</p><table><thead><tr><th>方案</th><th>卡在哪</th></tr></thead><tbody><tr><td>ESP32 + TFT_eSPI</td><td>经典Arduino路线，性能够，但放文件需要重编译烧录，或者开网页传，工具链麻烦</td></tr><tr><td>ESP32-C3 SuperMini</td><td>25元、硬币大小、自带Wi-Fi热点，手机连上用网页传文件——还是甩不开Arduino</td></tr><tr><td>ESP32 + I2C OLED</td><td>1.3寸OLED配U8g2，对比度好，但屏偏小，固件构建也重</td></tr><tr><td><strong>RP2040 + CircuitPython</strong></td><td>不用Wi-Fi：刷上固件后板载16MB闪存直接变成一块U盘，代码、字体、TXT拖进去就能用</td></tr></tbody></table><p>选RP2040最为方便：<strong>插上线就是U盘</strong>，驱动装一次就识别、不用开热点、不用编译器。</p><h2 id="物料清单"><a class="headerlink" href="#物料清单"></a>物料清单</h2><table><thead><tr><th>部件</th><th>型号 / 规格</th><th>尺寸</th><th>成本</th></tr></thead><tbody><tr><td>主控</td><td>YD-RP2040（16MB闪存版）</td><td>53.3 × 22.9 mm</td><td>¥9–11</td></tr><tr><td>屏幕</td><td>1.8寸TFT，ST7735，128×160 SPI</td><td>模块约56 × 34 mm</td><td>¥7–10</td></tr><tr><td>电池</td><td>503035聚合物锂电，3.7V 500mAh</td><td>约35 × 30 × 3.5 mm</td><td>¥5–8</td></tr><tr><td>充电保护</td><td>TP4056 Type-C模块</td><td>—</td><td>¥1–2</td></tr><tr><td>翻页键</td><td>6×6×5轻触微动开关×2</td><td>—</td><td>&lt;¥1</td></tr><tr><td>电源开关</td><td>SS12D00拨动开关（可选）</td><td>—</td><td>&lt;¥1</td></tr></tbody></table><p>自己焊接的话，主控要买<strong>未焊接排针的散件版</strong>，屏幕需要焊接好的，不能是裸板。</p><h2 id="结构"><a class="headerlink" href="#结构"></a>结构</h2><p><strong>屏幕（最底层，屏面朝外）→ 缓冲层 + 电池（中层）→YD-RP2040 + TP4056（顶层）</strong></p><p>主控53.3 × 22.9 mm能放在约56 × 34 mm的屏幕背后，电池夹在中间。</p><h3 id="缓冲层"><a class="headerlink" href="#缓冲层"></a>缓冲层</h3><p>屏幕背面有LDO、贴片阻容，有的批次还带SD卡座，凸起1–2mm。软包锂电池皮很薄，不能直接压上去。需要垫一些软的中层。</p><h2 id="焊接"><a class="headerlink" href="#焊接"></a>焊接</h2><h3 id="先拆掉排针"><a class="headerlink" href="#先拆掉排针"></a>先拆掉排针</h3><p>如果收到板子后者屏幕是已经焊好排针的版本，需要先拆下排针：</p><blockquote><ol><li>用斜口钳或尖嘴钳夹碎排针底部的黑色塑料座，让金属针彼此独立。</li><li>一手镊子夹住一根针，一手用电烙铁加热背面的焊点；锡化啦就轻轻往外抽。</li><li>吸锡器吸净孔内残锡，或者用牙签趁热捅穿——<strong>孔必须通透</strong>，后面要穿线。</li></ol></blockquote><h3 id="屏幕接线"><a class="headerlink" href="#屏幕接线"></a>屏幕接线</h3><p>剪8根约5cm的30AWG硅胶线，分别穿过屏幕底板那8个孔（VCC、GND、SCL、SDA、RES、DC、CS、BLK）焊死。</p><h3 id="电源链路"><a class="headerlink" href="#电源链路"></a>电源链路</h3><p>电池红线焊TP4056的<strong>B+</strong>，黑线焊<strong>B−</strong>。<strong>这两根正负极不能接反或短接</strong>。</p><p>接着OUT+出来先过拨动开关再过主控Vin，OUT−接主控GND。</p><h3 id="飞线对接"><a class="headerlink" href="#飞线对接"></a>飞线对接</h3><p>把屏幕上焊接的8根线从电池侧边翻上来，一根一根焊到主控对应引脚上。<strong>每焊一根都先比划长度</strong>：剪掉多余的、重新剥皮再焊，保证线贴着电池边缘走、不拱起来。</p><p>全部接完就是文章开头那张图的样子。</p><h3 id="按键"><a class="headerlink" href="#按键"></a>按键</h3><p>两个按键各自的一个引脚并联起来接GND，另一个引脚分别接GP16 / GP17。主控内部上拉，按下即拉低触发，不占地方也不用额外元件。</p><h2 id="接线表"><a class="headerlink" href="#接线表"></a>接线表</h2><h3 id="屏幕（1-8寸ST7735，走硬件SPI1）"><a class="headerlink" href="#屏幕（1-8寸ST7735，走硬件SPI1）"></a>屏幕（1.8寸ST7735，走硬件SPI1）</h3><table><thead><tr><th>屏幕引脚</th><th>接到YD-RP2040</th><th>作用</th></tr></thead><tbody><tr><td>VCC</td><td>3V3</td><td>屏幕逻辑供电</td></tr><tr><td>GND</td><td>GND</td><td>信号地</td></tr><tr><td>SCL / SCK</td><td>GP10</td><td>SPI硬件时钟</td></tr><tr><td>SDA / MOSI</td><td>GP11</td><td>SPI数据输出</td></tr><tr><td>RES / RST</td><td>GP12</td><td>屏幕硬件复位</td></tr><tr><td>DC / A0</td><td>GP13</td><td>数据 / 命令切换</td></tr><tr><td>CS / CE</td><td>GP14</td><td>SPI片选</td></tr><tr><td>BLK / LED</td><td>3V3</td><td>背光供电（常亮）</td></tr></tbody></table><p>板上通常只引出一个3V3孔，接两根线不方便。做法是<strong>在屏幕端把VCC和BLK短接</strong>，并联后共用一根飞线接到3V3，最整洁。</p><h3 id="电源链路（电池-→-充电板-→-开关-→-主控）"><a class="headerlink" href="#电源链路（电池-→-充电板-→-开关-→-主控）"></a>电源链路（电池 → 充电板 → 开关 → 主控）</h3><table><thead><tr><th>起点</th><th>终点</th><th>说明</th></tr></thead><tbody><tr><td>电池红线(+)</td><td>TP4056 B+</td><td>充电输入正极</td></tr><tr><td>电池黑线(−)</td><td>TP4056 B−</td><td>充电输入负极</td></tr><tr><td>TP4056 OUT−</td><td>主控GND</td><td>系统共地</td></tr><tr><td>TP4056 OUT+</td><td>拨动开关 引脚1</td><td>正极先过开关</td></tr><tr><td>拨动开关 引脚2</td><td>主控Vin</td><td>主板供电</td></tr><tr><td>TP4056 OUT+</td><td>主控Vin</td><td>不加开关时的直连方案</td></tr></tbody></table><h3 id="翻页按键"><a class="headerlink" href="#翻页按键"></a>翻页按键</h3><table><thead><tr><th>功能</th><th>一端</th><th>另一端</th><th>触发逻辑</th></tr></thead><tbody><tr><td>下一页</td><td>GP16</td><td>GND</td><td>按下拉低</td></tr><tr><td>上一页</td><td>GP17</td><td>GND</td><td>按下拉低</td></tr></tbody></table><img src="/img/rp2040/pico-pinout-dimensions.jpg" alt="Raspberry Pi Pico 引脚图与产品尺寸图" style="width:62%;display:block;margin:0 auto;"><h2 id="通电前检查"><a class="headerlink" href="#通电前检查"></a>通电前检查</h2><p>拨开关之前确认这几处：</p><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 剪断的金属废线头全部清理干净，没有掉进任何板子缝隙里。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 从底板翻上来的导线外皮完好，没有被板边锋利焊盘割破露铜。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 电池与任何金属针脚、焊点之间都有绝缘（双面胶或硬塑料片），没有被刺穿的可能。</li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> 有条件就上万用表蜂鸣档：该通的关键引脚确实通，相邻引脚没有意外短路。</li></ul><img src="/img/rp2040/02-repl-startup.jpg" alt="第一次上电：屏幕亮起并显示 CircuitPython 启动信息与提示文字" style="width:58%;display:block;margin:0 auto;"><h2 id="上电"><a class="headerlink" href="#上电"></a>上电</h2><p>接线正确、固件也烧好之后拨开关，屏幕会亮起来打印启动信息——这台跑的是CircuitPython 10.3.0，画面里的提示文字是固件自带的拼音化中文。</p><p>屏幕上出现字，说明SPI那五根线、复位、片选和供电正确。</p><img src="/img/rp2040/03-image-viewer.jpg" alt="另一个角度：主控电源灯亮起，屏幕显示一张 24 位 BMP 图片" style="width:80%;display:block;margin:0 auto;"><p>屏幕是彩屏，能显示标准24位BMP图片，仓库里<code>examples/image_viewer.py</code>可做测试。</p><h2 id="放文件：插上线就是U盘"><a class="headerlink" href="#放文件：插上线就是U盘"></a>放文件：插上线就是U盘</h2><ol><li>Type-C线插设备连电脑，会弹出一块名叫<code>CIRCUITPY</code>的U盘。</li><li>把UTF-8编码的<code>book.txt</code>拖进盘根目录，拔线。</li><li>拨开关开机，按「下一页」从第一页开始读。</li></ol><p>充电时线要插在<strong>TP4056的Type-C口</strong>，插主控口是直接给主控供电，不走充电板。</p><h2 id="固件"><a class="headerlink" href="#固件"></a>固件</h2><p>板子跑的是CircuitPython（仓库：<a href="https://github.com/adafruit/circuitpython">adafruit/circuitpython</a>）。屏幕驱动、中文字体、翻页逻辑一共216行，另外依赖三个库，都在Adafruit的仓库里：</p><ul><li><a href="https://github.com/adafruit/Adafruit_CircuitPython_ST7735R">Adafruit_CircuitPython_ST7735R</a> — 屏幕底层驱动</li><li><a href="https://github.com/adafruit/Adafruit_CircuitPython_Display_Text">Adafruit_CircuitPython_Display_Text</a> — 文本排版</li><li><a href="https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font">Adafruit_CircuitPython_Bitmap_Font</a> — 加载点阵字体</li><li>想一次拿齐，用官方合集<a href="https://github.com/adafruit/Adafruit_CircuitPython_Bundle">Adafruit_CircuitPython_Bundle</a></li></ul><p>中文用的是BDF点阵字体，<a href="https://github.com/AmusementClub/WenQuanYi-Bitmap-Song-TTF">文泉驿点阵宋体</a>有现成的中文字形可以转。</p><p>注意：</p><ul><li>CircuitPython 9.x起<code>FourWire</code>从<code>displayio</code>拆成了独立的<code>fourwire</code>模块，照8.x教程写会报<code>AttributeError</code>，表现是<strong>背光亮但屏幕全黑</strong>——很容易误判成焊接问题。</li><li>这块屏的色序是BGR，建display时要显式<code>bgr=True</code>，不设颜色会反。</li></ul><h2 id="仓库"><a class="headerlink" href="#仓库"></a>仓库</h2><p>完整制作指南（选型 → 烧录 → 组装 → 排障）在<a href="https://github.com/4choor3/rp2040-txt-reader/blob/main/docs/BUILD_GUIDE.zh-CN.md">docs/BUILD_GUIDE.zh-CN.md</a>，接线表在<a href="https://github.com/4choor3/rp2040-txt-reader/blob/main/hardware/PINOUT.md">hardware/PINOUT.md</a>，代码与示例在仓库根目录。MIT协议，欢迎照抄。</p>]]>
    </content>
    <id>https://4choor3.github.io/2026/09/13/rp2040-txt-reader/</id>
    <link href="https://4choor3.github.io/2026/09/13/rp2040-txt-reader/"/>
    <published>2026-09-13T15:10:00.000Z</published>
    <summary>第一次碰RP2040开发板做的硬件实践：用YD-RP2040配1.8寸ST7735屏，从选型、三明治结构设计到焊接装配、接线与通电检查，材料不到30块。</summary>
    <title>RP2040开发板电路设计</title>
    <updated>2026-10-04T20:50:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="blog" scheme="https://4choor3.github.io/tags/blog/"/>
    <category term="Hexo" scheme="https://4choor3.github.io/tags/Hexo/"/>
    <category term="GitHub" scheme="https://4choor3.github.io/tags/GitHub/"/>
    <content>
      <![CDATA[<p>博客是静态站：Markdown写作，内容推送到仓库，GitHub Pages出站。<br>文章格式一直是Markdown加front-matter，三次迁移换的都是主题。下面是三次用过的引擎、目录结构和配置。</p><table><thead><tr><th></th><th>Hugo</th><th>Astro</th><th>Hexo</th></tr></thead><tbody><tr><td>语言</td><td>Go</td><td>Node</td><td>Node</td></tr><tr><td>安装</td><td>单二进制</td><td>npm</td><td>npm</td></tr><tr><td>站点配置</td><td>config.yml</td><td>astro.config.mjs</td><td>_config.yml</td></tr><tr><td>输出目录</td><td>public</td><td>dist</td><td>public</td></tr><tr><td>主题索引</td><td>themes.gohugo.io</td><td>astro.build/themes</td><td>hexo.io/themes</td></tr></tbody></table><h2 id="第一版：Hugo"><a class="headerlink" href="#第一版：Hugo"></a>第一版：Hugo</h2><p>Hugo用Go编写，安装是一个二进制文件，放进PATH即可使用，不需要Node环境。全站一个配置文件，文章按目录组织，主题放在themes目录（直接放或用git submodule管理）。front-matter支持TOML、YAML、JSON三种格式。当时用的版本是0.165.0。本地预览是<code>hugo server</code>，生成是<code>hugo</code>，输出在public目录。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">content/</span><br><span class="line">  posts/</span><br><span class="line">    2026-09-08-hello-world.md</span><br><span class="line">themes/</span><br><span class="line">  PaperMod/</span><br><span class="line">config.yml</span><br></pre></td></tr></table></figure><p>主题是PaperMod，自带排版、深色模式、目录（TOC）、代码高亮和搜索。星空背景、鼠标点击特效、访问统计不在主题里，需要自己写CSS和JS挂在主题外面。这几个效果要维护的代码量接近主题本身。所以换掉了。</p><h2 id="第二版：Astro"><a class="headerlink" href="#第二版：Astro"></a>第二版：Astro</h2><p>Astro属于Node生态，用npm装依赖，配置文件是astro.config.mjs，构建输出到dist目录。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">src/</span><br><span class="line">  content/</span><br><span class="line">    blog/</span><br><span class="line">      hello.md</span><br><span class="line">  layouts/</span><br><span class="line">    BlogPost.astro</span><br><span class="line">astro.config.mjs</span><br><span class="line">package.json</span><br></pre></td></tr></table></figure><p>内容集合是它的一个机制：文章的front-matter字段在TypeScript文件里定义，不符合schema的字段会在构建时报错。输出默认不含前端框架的JavaScript，需要交互的组件单独声明。本地预览是<code>npm run dev</code>。</p><p>日常使用上，改配置后要重新执行npm install和npm run build，node_modules目录装下来几百兆。所以换到了Hexo。</p><h2 id="第三版：Hexo"><a class="headerlink" href="#第三版：Hexo"></a>第三版：Hexo</h2><p>Hexo属于Node生态，当前版本8.1.2，主题是开源的安知鱼（anzhiyu），版本1.7.0。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">source/</span><br><span class="line">  _posts/</span><br><span class="line">    2026-09-08-hello-world.md</span><br><span class="line">themes/</span><br><span class="line">  anzhiyu/</span><br><span class="line">_config.yml              # 站点配置</span><br><span class="line">_config.anzhiyu.yml      # 主题配置（站点级覆盖）</span><br></pre></td></tr></table></figure><p>配置两层：站点_config.yml管生成与目录参数，_config.anzhiyu.yml管外观和功能开关。新建文章用<code>hexo new</code>，本地预览用<code>hexo server</code>，生成用<code>hexo generate</code>，输出在public目录。主题自带星空背景、鼠标点击特效、首页打字机、本地搜索、字数统计、访客统计、深色模式、繁简转换、版权声明、音乐页、分类标签归档页、图片灯箱、相对时间，都在主题配置里开关，不用自己写代码。其中本地搜索由生成search.xml、前端检索实现，不依赖外部服务。</p><p>默认字体是霞鹜文楷，文章支持封面图、置顶、分类和标签。背景粒子和首页头图自写脚本放置。定制放在_config.anzhiyu.yml里，主题更新不受影响。</p><p>三个引擎的产物都是纯静态文件，HTML加CSS加JS，不需要服务端。</p>]]>
    </content>
    <id>https://4choor3.github.io/2026/09/08/blog-rebuilt-three-times/</id>
    <link href="https://4choor3.github.io/2026/09/08/blog-rebuilt-three-times/"/>
    <published>2026-09-08T18:00:00.000Z</published>
    <summary>Hugo、Astro到Hexo的三次迁移：引擎、目录结构、配置文件与主题功能对照。</summary>
    <title>博客三次重构：Hugo、Astro到Hexo</title>
    <updated>2026-09-15T14:40:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="HPC" scheme="https://4choor3.github.io/tags/HPC/"/>
    <category term="SLURM" scheme="https://4choor3.github.io/tags/SLURM/"/>
    <category term="Linux" scheme="https://4choor3.github.io/tags/Linux/"/>
    <content>
      <![CDATA[<p>学校高性能计算平台共两百余台机器，纳入SLURM调度的171台，合计12352个CPU核，调度器统计内存90余TB，外挂23PB并行文件系统。硬件分批采购，代际混合。</p><p>120台为早期CPU节点，双路32核Xeon 8358P，单机64核 / 500G内存，承载日常主要算力。数台胖节点为四路24核Xeon 8360H，单机96核 / 2T内存，供大内存作业使用。最新批次为GPU节点，单机96核 / 1T内存、8张H20，另有一台配2块4090。另有2台国产K100异构节点，自五月末起持续offline。</p><p>平台另有二十余台GPU机器未纳入SLURM节点列表；登录、管理、监控类机器同样不计入调度器口径，<code>sinfo</code>不显示。</p><p>主要用户方向为生物信息与计算化学，<code>module avail</code>提供87个模块，blast、bwa、vasp、gromacs占多数。</p><p>登录节点仅承担登录与作业提交，计算须下发至计算节点。调度系统版本SLURM 23.11.10，以下数据均在登录节点实测采集。</p><h2 id="节点详情"><a class="headerlink" href="#节点详情"></a>节点详情</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sinfo -s                             <span class="comment"># 分区概览：节点数、状态分布</span></span><br><span class="line">sinfo -N -o <span class="string">&quot;%N %P %c %m %G %t&quot;</span>      <span class="comment"># 逐节点：核数、内存、加速卡、状态</span></span><br><span class="line">squeue -o <span class="string">&quot;%.8i %.8P %.18j %.8t %.10M %R&quot;</span>    <span class="comment"># 自己的作业</span></span><br><span class="line">sacct -u <span class="variable">$USER</span> --starttime=now-2hours        <span class="comment"># 自己的历史作业与资源用量</span></span><br></pre></td></tr></table></figure><p><code>sinfo -s</code>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">PARTITION AVAIL  TIMELIMIT   NODES(A/I/O/T) NODELIST</span><br><span class="line">cpu96        up   infinite          1/4/0/5   fat[01-05]</span><br><span class="line">cpu64*       up   infinite        25/95/0/120 node[01-120]</span><br><span class="line">test96       up   infinite          0/2/0/2   node[121-122]</span><br><span class="line">soy-com      up   infinite          0/22/0/22   sri-com-[01-22]</span><br><span class="line">sri-com      up   infinite      13/1/0/14     sri-com-[23-36]</span><br><span class="line">sri-mem      up   infinite          0/2/0/2   sri-mem-[01-02]</span><br><span class="line">sri-ghz      up   infinite          0/2/0/2   sri-ghz-[01-02]</span><br><span class="line">sri-gpu      up   infinite          0/1/0/1   sri-gpu-01</span><br><span class="line">sri-gnm      up   infinite          0/1/0/1   sri-gnm-01</span><br><span class="line">dcu          up   infinite          0/0/2/2   dk-gpu[01-02]</span><br></pre></td></tr></table></figure><p>带星号的<code>cpu64</code>为默认分区，不指定<code>-p</code>时作业落入此分区。中间四列为allocated / idle / other / total。</p><h2 id="分区"><a class="headerlink" href="#分区"></a>分区</h2><table><thead><tr><th>分区</th><th>节点数</th><th>CPU型号</th><th>单机核数</th><th>单机内存</th><th>加速卡</th></tr></thead><tbody><tr><td><code>cpu64</code>（默认）</td><td>120</td><td>Xeon Platinum 8358P</td><td>64</td><td>500G</td><td>—</td></tr><tr><td><code>cpu96</code></td><td>5</td><td>Xeon Platinum 8360H</td><td>96</td><td>2T</td><td>—</td></tr><tr><td><code>test96</code></td><td>2</td><td>Xeon Platinum 8558P</td><td>96</td><td>250G</td><td>—</td></tr><tr><td><code>soy-com</code></td><td>22</td><td>Xeon Platinum 8558P</td><td>96</td><td>500G</td><td>—</td></tr><tr><td><code>sri-com</code></td><td>14</td><td>—</td><td>96</td><td>500G</td><td>—</td></tr><tr><td><code>sri-mem</code></td><td>2</td><td>—</td><td>96</td><td>2T</td><td>—</td></tr><tr><td><code>sri-ghz</code></td><td>2</td><td>—</td><td>32</td><td>500G</td><td>—</td></tr><tr><td><code>sri-gpu</code></td><td>1</td><td>—</td><td>96</td><td>1T</td><td>H20 × 8</td></tr><tr><td><code>sri-gnm</code></td><td>1</td><td>—</td><td>96</td><td>500G</td><td>4090 × 2</td></tr><tr><td><code>dcu</code></td><td>2</td><td>—</td><td>48</td><td>250G</td><td>国产K100 × 2</td></tr></tbody></table><p>十个分区合计171台、12352核。<code>cpu64</code>中118台为500G内存，其余两台384G。</p><p><code>sri-*</code>一组绑定专属QoS，普通账号提交返回<code>Invalid qos specification</code>，CPU型号无法读取；<code>dcu</code>两台offline，同样无法读取。</p><p><code>cpu64</code>的120台为主力：空闲95台时可直接提交；25台在跑时backfill仍可插入空闲窗口。调度器记录内存500G，节点内<code>free -g</code>实测503G。</p><p>DCU两台装国产K100，<code>sinfo</code>中为<code>down*</code>，<code>scontrol show node</code>给出的原因为<code>Not responding</code>，自五月底持续至今。另两台带加速卡的节点——<code>sri-gpu</code>的H20与<code>sri-gnm</code>的4090——位于上述受限QoS组内。</p><p>系统代际存在差异：早期CPU节点为CentOS 8.2 / 内核4.18，后加的GPU与内存节点为RHEL 9.5 / 内核5.14。<code>/hpc/software</code>下gcc覆盖4.8.5至14.1.0，glibc保留2.29与2.36两套，对应两代系统各留一版。自行编译的二进制跨分区搬运存在兼容风险。</p><h2 id="作业提交"><a class="headerlink" href="#作业提交"></a>作业提交</h2><p><strong>交互式调试</strong>用<code>srun</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">srun -p cpu64 -N1 -n1 --<span class="keyword">time</span>=00:30:00 --pty bash</span><br></pre></td></tr></table></figure><p>实测分配到node08，<code>grep -m1 'model name' /proc/cpuinfo</code>为Xeon Platinum 8358P。进入后<code>nproc</code>返回<strong>1</strong>而非64——srun该步仅分配1个CPU，确认整机规格需读<code>/proc/cpuinfo</code>或使用<code>lscpu</code>。</p><p><strong>正式任务</strong>写脚本交<code>sbatch</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment">#SBATCH -J quant-backtest</span></span><br><span class="line"><span class="comment">#SBATCH -p cpu96</span></span><br><span class="line"><span class="comment">#SBATCH --cpus-per-task=32</span></span><br><span class="line"><span class="comment">#SBATCH --mem=256G</span></span><br><span class="line"><span class="comment">#SBATCH --time=08:00:00</span></span><br><span class="line"><span class="comment">#SBATCH -o /hpc/home/%u/logs/%j.out</span></span><br><span class="line"><span class="comment">#SBATCH -e /hpc/home/%u/logs/%j.err</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> ~/quant</span><br><span class="line">python backtest.py</span><br></pre></td></tr></table></figure><p>申请加速卡加一行<code>#SBATCH --gres=gpu:1</code>，具体型号见分区<code>Gres</code>字段。</p><p>集群<code>SelectTypeParameters</code>为<code>CR_CORE</code>，调度按核数分配，内存不参与资源分配——作业不写<code>--mem</code>亦可运行，节点<code>AllocMem</code>恒为0。以两个1核作业验证，运行期间所在节点<code>AllocMem</code>均为0。同节点多作业之间互不可见实际内存占用，先占满内存的作业会影响其余作业，故提交时须显式指定<code>--mem</code>。<code>sacct</code>中ReqMem显示500000M，为未填该字段时按节点总内存回填的显示值，不代表实际申请整机。</p><p>数组作业上限<code>MaxArraySize</code>为1001，单次最多提交1001个子任务，更大规模需分批提交。</p><h2 id="调度器按什么排"><a class="headerlink" href="#调度器按什么排"></a>调度器按什么排</h2><p><code>PriorityType = priority/multifactor</code>，<code>SchedulerType = sched/backfill</code>。前者按作业年龄、份额、队列权重计算优先级，后者负责插空——作业若能在一个空闲窗口内跑完，即可提前启动。</p><p>该组合利于短作业，反向亦然：为一个5分钟的任务写<code>--time=72:00:00</code>，backfill找不到足够长的窗口，作业只能排队。</p><p><strong>所有分区时间上限均为<code>infinite</code></strong>，MaxTime无系统级限制，时限交由使用者自行声明，长时作业可长期占用节点。</p><p><code>squeue -a</code>在普通账号下不显示其他用户作业：实测<code>squeue -a -h | wc -l</code>返回0，同一时刻<code>sinfo</code>显示25个节点处于alloc状态。判断集群负载只能依据<code>sinfo</code>的节点状态分布。</p><h2 id="存储：23P与5000亿inode"><a class="headerlink" href="#存储：23P与5000亿inode"></a>存储：23P与5000亿inode</h2><p><code>df -hT</code>中容量最大的一行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Filesystem      Type      Size  Used Avail Use% Mounted on</span><br><span class="line">/hpc             dpc        23P  1.1P   22P   5% /hpc</span><br></pre></td></tr></table></figure><p>23PB并行文件系统，已用1.1PB。文件系统类型为<code>dpc</code>，属分布式存储客户端挂载，与本地盘行为不同——小文件随机读性能与顺序读写相差量级，登录节点禁用<code>du</code> / <code>find</code>即源于此。</p><p>inode容量见<code>df -i /hpc</code>：总量5000亿，已用2.33亿。元数据按目录树铺开、查询需走网络，是该量级的成因。</p><p>另两个挂载点：</p><ul><li><code>/usr/local/globle/softs</code>为NFS，191G、59%已用，存放slurm二进制与公共软件，所有节点共享同一份。</li><li><code>/dev/shm</code>为252G内存盘。数据管道中间结果置于此处，读写不落盘；重启即清空。</li></ul><p>家目录位于<code>/hpc/home/&lt;用户名&gt;</code>。以本账号为例，占用接近48TB，主要为从ModelScope拉取的49个模型，其余为量化日线数据。</p><h2 id="环境与启动速度"><a class="headerlink" href="#环境与启动速度"></a>环境与启动速度</h2><p>公共软件通过module加载，<code>module avail</code>挂载87个：生信类（<code>blast</code>、<code>bwa</code>、<code>hmmer</code>、<code>canu</code>、<code>megahit</code>、<code>gtdbtk</code>、<code>metawrap</code>）、计算化学类（<code>vasp</code>、<code>gromacs</code>、<code>amber</code>、<code>autodock</code>）、<code>gcc/4.8.5</code>至<code>gcc/14.1.0</code>、<code>openmpi</code> 3.1至5.0.5、<code>cuda/11.7</code>与<code>12.4</code>、Python 3.7/3.8、R三个版本、<code>singularity</code>两个版本。使用前<code>module load gcc/14.1.0 openmpi/5.0.5</code>即可，优于在home内自行编译。</p><p>Python环境采用miniconda，按用途拆为四个：<code>myenv</code>放通用工具，<code>quant</code>放baostock相关依赖，<code>agent</code>与<code>agent-gpu</code>分开的原因是GPU侧依赖锁定在另一cuda版本。</p><p>zsh启动时间做过专项优化。原始耗时约15秒，成因是oh-my-zsh及插件每次登录重新source，而home挂在分布式存储上，网络文件系统随机读延迟高。处理办法是把omz与历史文件缓存到<code>/dev/shm</code>，并将&quot;重新加载配置&quot;改写为跳过omz重复加载的函数。优化后约0.8秒，多终端窗口并发打开时差异明显。</p><h2 id="长任务用tmux托管"><a class="headerlink" href="#长任务用tmux托管"></a>长任务用tmux托管</h2><p>登录节点直接运行长任务，网络断开即进程终止，改用tmux托管：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tmux new -s pull        <span class="comment"># 建会话</span></span><br><span class="line"><span class="comment"># Ctrl-b d 脱离</span></span><br><span class="line">tmux attach -t pull     <span class="comment"># 重新接上</span></span><br></pre></td></tr></table></figure><p>集群tmux版本为2.7。小时级以上的任务交<code>sbatch</code>；tmux用于托管数据拉取循环与监控进程。</p>]]>
    </content>
    <id>https://4choor3.github.io/2026/09/08/hpc-slurm-notes/</id>
    <link href="https://4choor3.github.io/2026/09/08/hpc-slurm-notes/"/>
    <published>2026-09-08T17:30:00.000Z</published>
    <summary>学校高性能计算平台的SLURM实测：机器构成与代际、分区参数对照（CPU型号/核数/内存/加速卡）、作业提交与内存记账的坑、23P存储的挂载类型与inode规模、环境与启动速度优化。</summary>
    <title>学校HPC集群</title>
    <updated>2026-10-04T21:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="blog" scheme="https://4choor3.github.io/tags/blog/"/>
    <category term="随笔" scheme="https://4choor3.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h1 id="恭喜！"><a class="headerlink" href="#恭喜！"></a>恭喜！</h1><h1 id="2026年9月8日-本博客正式上线。"><a class="headerlink" href="#2026年9月8日-本博客正式上线。"></a>2026年9月8日,本博客正式上线。</h1><p>笔者会在博客记录日常，记录做了什么事情，记录写的代码，会记录做的GitHub仓库。</p><br><p>总之就是什么都会记录喵～</p>]]>
    </content>
    <id>https://4choor3.github.io/2026/09/08/hello-world/</id>
    <link href="https://4choor3.github.io/2026/09/08/hello-world/"/>
    <published>2026-09-08T04:00:00.000Z</published>
    <summary>写A股量化、Python工具链和实践记录。Hexo搭的，GitHub Pages托管。</summary>
    <title>博客上线</title>
    <updated>2026-09-15T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>陈鑫宇</name>
    </author>
    <category term="blog" scheme="https://4choor3.github.io/tags/blog/"/>
    <category term="随笔" scheme="https://4choor3.github.io/tags/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<blockquote><p>“自由”一词，在政治学领域已经泛滥了，所以先澄清一下：<br>本文所说的“自由”，主要偏重于和“人权”及“民权”相关的自由——比如：思想的自由、言论的自由、支配私有财产的自由、使用网络的自由……<br>——[编程幻想 《“对抗专制、捍卫自由”的N种技术力量》][]</p></blockquote><h3 id="所谓的言论自由"><a class="headerlink" href="#所谓的言论自由"></a>所谓的言论自由</h3><p>《中华人民共和国宪法》规定：“中华人民共和国公民有言论、出版、集会、结社、游行、示威的自由。”<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>或许吧。</p><p>在国内的社交平台上，一条评论在发布后悄无声息地消失；一篇帖子突然被删除；一个账号突然被封禁。</p><p>你留下了一句实话，刷新页面后，它便不复存在，仿佛从未被说出口。</p><p>官方媒体在发布新闻时，直接关闭评论区。<br>新闻照常发布，报道照常播出，官方的声音可以迅速传遍网络，而公众却连留下意见的机会都没有。</p><p>官方发布什么、如何发布、发布多少，普通人却未必有权回应。官方媒体把自己的声音传遍网络，公众却可能连一句反驳、质疑或不满都无法留下。</p><p>评论区和弹幕里刷屏的看不见活人的👍👍👍、🌹🌹🌹、🫰🫰🫰。<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p><p>有些词不能出现，有些人物不能直接提及。</p><p>维尼熊是敏感词，包子也是。<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p><p>于是，人们开始学会绕开语言本身。</p><p>不能说名字，就换一个称呼；不能直接表达，就使用谐音、代称和隐喻；不能公开讨论，就把真正想说的话藏进看似无关的句子里。</p><p>有些词，只有了解背景的人才能看懂；有些看似荒诞的句子，实际上是在讨论一个无法被直接讨论的话题。</p><p>语言并没有消失，只是被逼得越来越隐晦。</p><p>人民过得快乐吗？<br>官方说，人民是快乐的。<br>那么，我就快乐吧。</p><p>毕竟，新闻里的经济持续增长，城市里高楼林立，宣传片中的人民安居乐业，生活似乎一天比一天富足。至于现实中的人究竟如何感受，生活里又有多少疲惫、困惑与不如意，似乎并没有那么重要。</p><p>只要宏大的叙事足够完整，个体的感受便可以被放到一边；只要宣传中的生活足够明亮，现实中的阴影就仿佛不值一提。</p><p>如果有人提出质疑。<br>那么，问题定不在现实本身，而在于这个人“不够积极”“不懂感恩”或者“被错误思想影响”。</p><p>官方说人民幸福，所以人民就必须幸福；<br>官方说社会稳定，所有不满都必须被解释为少数人的问题；<br>官方说一切都在变好，那么那些感到失望、疲惫和无力的人，就只能怀疑自己。</p><p>所谓的言论自由，不只是法律条文中一句看上去庄严的承诺，也不只是允许你在没有触碰权力和禁区的情况下说几句无关痛痒的话。</p><p>真正的言论自由，意味着你可以谈论现实，可以质疑政策，可以批评权力，可以讨论具体的人和事，也可以表达不满，而不必担心自己的言论被删除、账号被封禁，甚至因此遭到现实中的报复。</p><p>但在国内，有些事情不能直接说，有些人物不能直接提，有些新闻不能公开讨论，有些历史不能重新审视，有些问题只能被允许以官方规定的方式存在。</p><p>你可以赞美，但不能质疑；可以歌颂，但不能追问；可以转发官方结论，却不能提出不同答案</p><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>《中华人民共和国宪法》第三十五条。 <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>此处为对部分平台评论区现象的概括性描述。 <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p>此处涉及中文互联网语境中与相关政治人物有关的网络审查和规避表达现象。 <a href="#fnref3" class="footnote-backref">↩︎</a></p></li></ol></section>]]>
    </content>
    <id>https://4choor3.github.io/1980/01/01/test/</id>
    <link href="https://4choor3.github.io/1980/01/01/test/"/>
    <published>1980-01-01T10:00:00.000Z</published>
    <summary>在脑子不清醒的时候写一些不是写的东西。ps：好废话的表达（貌似看不到`description`呢）</summary>
    <title>测试文档</title>
    <updated>2026-10-06T14:40:00.000Z</updated>
  </entry>
</feed>
