<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://hadb.me/</id>
    <title>HADB.ME</title>
    <updated>2026-02-28T21:02:45.924Z</updated>
    <generator>HADB.ME</generator>
    <author>
        <name>Bean</name>
    </author>
    <link rel="alternate" href="https://hadb.me/"/>
    <link rel="self" href="https://hadb.me/atom.xml"/>
    <subtitle>feedId:87047734898412544+userId:130241058717434880</subtitle>
    <logo>https://hadb.me/favicon/favicon-1024x1024.png</logo>
    <icon>https://hadb.me/favicon/favicon-32x32.png</icon>
    <rights>Copyright © Bean, CC BY-NC-SA 4.0</rights>
    <entry>
        <title type="html"><![CDATA[2011 春节杂感]]></title>
        <id>/posts/2011/2011-spring-festival</id>
        <link href="https://hadb.me/posts/2011/2011-spring-festival"/>
        <updated>2011-02-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[时间过得真快，昨天把以前的一些老照片扫描进电脑了。真怀念那时候。无忧无虑，单纯快乐。]]></summary>
        <content type="html"><![CDATA[<p>时间过得真快，昨天把以前的一些老照片扫描进电脑了。真怀念那时候。无忧无虑，单纯快乐。</p><p>一转眼哪，小孩子上大学了。此处略去 N 个字。</p><p>如今各种交流方式越来越丰富，反而把原始面对面的交流给淡漠了。我感觉吧，别的再丰富的交流方式都比不上面对面的交流。新浪博客、网易博客、QQ 空间、人人空间、新浪微博、网易微博、腾讯微博……只列举了我自己用的，还有很多很多各种各样的东西。我越来越烦这些了。各类分享，各类转载……太丰富了以至于烦了。小品一年一年地演，春晚一年一年地播，却都一年不如一年。人生也是一年一年地过，生活水平提高的同时，大家却都有同感，过年越来越没意思了。是呀，还是小时候好。那时候不是天天都有好衣服穿，不是天天都有好东西吃，既有好衣服穿又有好东西吃的时候更是少之又少。而过年，正是这两种好事能同时实现的时候。那时候，很早很早就盼着过年，压岁钱什么的小时候也没怎么当真，给我玩玩还是交给家人。不过，我的新衣服他们是穿不下的，好吃的我也吃下肚了，到处玩啊，到谁家玩都有好吃的。人大了，就不怎么爱出去了，也不怎么好出去了，大家串门儿都少了。</p><p>电视里不断放着以前春晚的相声小品，或许 CCTV 知道今年春晚大家没看过瘾。人们总是喜欢批评，说这不好，说那不好。大家的要求越来越高，胃口也越来越大，越来越难被满足。很少有人想过自己有什么变化，却总批评春晚怎么样。不多评论了，此处略去 N 个字。</p><p>要吃饭了，再略去 N 个字吧。以后有感再发。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Hereafter]]></title>
        <id>/posts/2011/hereafter</id>
        <link href="https://hadb.me/posts/2011/hereafter"/>
        <updated>2011-05-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[看了《Hereafter》，假如真的有通灵者，可以跟逝去的人对话，我很想跟我的堂叔说，很抱歉那个时候让你的电脑中了病毒，是我把那张你说不要玩的光盘放到了光驱，想看看是什么游戏，结果就中毒了，当时我看到杀毒软件的警告窗，可是当时我什么都不懂，我就直接关掉了，还很从容地打开了画图软件，发现还可以画图……我还觉得很奇怪，原来电脑中病毒了，还是可以正常地使用啊，看不出来……不过我当时出了一身冷汗，因为当时我感觉电脑中病毒就像人得了癌症一样，电脑会坏掉……我没敢告诉你，而且以后也很少去你家玩电脑了……后来春节你来我家拜年，还告诉我你装了一个新的好玩的游戏，喊我去玩，我说好的，但是后来还是没去。再后来，好像你就得尿毒症了，也再没机会跟你说什么。我想告诉你，我现在在电脑上很有天赋，很感谢你那时候给我的启蒙教育，让我对电脑有了兴趣，我现在的水平绝不比你差……我想，你要是知道了，会为我感到自豪的……就这么多……祝愿你身旁有个女朋友……]]></summary>
        <content type="html"><![CDATA[<p>看了《Hereafter》，假如真的有通灵者，可以跟逝去的人对话，我很想跟我的堂叔说，很抱歉那个时候让你的电脑中了病毒，是我把那张你说不要玩的光盘放到了光驱，想看看是什么游戏，结果就中毒了，当时我看到杀毒软件的警告窗，可是当时我什么都不懂，我就直接关掉了，还很从容地打开了画图软件，发现还可以画图……我还觉得很奇怪，原来电脑中病毒了，还是可以正常地使用啊，看不出来……不过我当时出了一身冷汗，因为当时我感觉电脑中病毒就像人得了癌症一样，电脑会坏掉……我没敢告诉你，而且以后也很少去你家玩电脑了……后来春节你来我家拜年，还告诉我你装了一个新的好玩的游戏，喊我去玩，我说好的，但是后来还是没去。再后来，好像你就得尿毒症了，也再没机会跟你说什么。我想告诉你，我现在在电脑上很有天赋，很感谢你那时候给我的启蒙教育，让我对电脑有了兴趣，我现在的水平绝不比你差……我想，你要是知道了，会为我感到自豪的……就这么多……祝愿你身旁有个女朋友……</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[星际 2 人族枪兵纯爷们一波流战术]]></title>
        <id>/posts/2011/sc2-tactics-marine-rush</id>
        <link href="https://hadb.me/posts/2011/sc2-tactics-marine-rush"/>
        <updated>2011-05-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我是从国服的星际公测期开始玩星际 2 的，时间不久，现在处在青铜第一已经很久了，也经常会分配我跟黄金组的和白银组的人打，也常常能嬴吧，我的战术不多，基本上以一波流为主，如果对方防御不好，很容易被我一波带走。我练一波流也蛮久的了，也有点小经验来告诉大家。]]></summary>
        <content type="html"><![CDATA[<p>我是从国服的星际公测期开始玩星际 2 的，时间不久，现在处在青铜第一已经很久了，也经常会分配我跟黄金组的和白银组的人打，也常常能嬴吧，我的战术不多，基本上以一波流为主，如果对方防御不好，很容易被我一波带走。我练一波流也蛮久的了，也有点小经验来告诉大家。</p><p>我偏向人族，神族和虫族还没怎么打过。不过有一点很重要，你得知道虫族和神族的单位，哪个建筑是做什么的。也要了解他们的一些基本战术，比如虫族的 6D，神族的 4BG 等等，要侦察好，有应对才行。</p><p>打虫族，堵口很重要，防 6D，堵口是必须的。第 10 个农民刚开始造的时候，得有一个农民在造 BS 补给站，堵在口上，然后直接用 Shift 键，让他造完补给站后去侦查对方；家里的农民不断生产，第 12 个农民开始造的时候，也差不多是那个造补给站的农民刚造完的时候，再派个农民去造 BB 兵营，挨着补给站；农民找到敌人位置后，围绕矿区 Shift 一圈，看看虫族有没有很早地放下血池，如果很早，已经造完了，那有可能就是 6D 了，赶紧再拉一个农民去造个 BS，把口堵死。探路的农民可以回来，占领萨尔加瞭望塔。</p><p>下面要做的是就是，补农民，有了 16 个农民的时候升级基地，扔矿骡；有了钱就造 BB，不要采气，纯机枪一波流。注意农民不能断，人口及时补，机枪不停造，钱多了就补 BB，大约 5 分多钟，6 分钟不到的时候，大约有十几个机枪兵，降下 BS，直接 A 向对手家，注意把兵营集结点设到对手家，把兵续好，基本上就能嬴了，如果对手猥琐扎地堡，没扎好的话，赶紧打掉，要是已经扎了好几个，那就退回来，改变战术。可以转女妖吧，把地堡打掉。不过我还没怎么遇到这种情况，基本上新手不会这么做。遇到神族也一样。有一点记住，五六分钟的时候一定要出门，哪怕兵力只有七八个，兵续好就行。晚了就有点麻烦了。这个一波流战术，不适合中后期，到时候转型比较困难。打人族的话，不要堵口，节约时间。还有，要防神族在你家造水晶塔，地堡，看到神族的探路农民，要赶走。把口堵好后就行了，你的战术不要过早让别人知道，有时候必要的时候假装造个气矿，等对方看不到了再取消掉。要是被察觉你的一波战术，然后神族造了好多地堡在家门口，那就打不了了。注意矿骡要及时扔下来。出门时留些魔，快到对方高地时，撒一把，看看神族有没有地堡，没有的话你就笑吧。堵口了的话，直接点叉叉兵，然后把水晶矿打掉，你就等着对方 GG 吧~~</p><p>一定要果断，一定要快！多余的水晶矿不能超过 200，一有钱就要造兵造农民，余钱多了就造兵营。五六分钟一定要出去压一波。果断，不要怕，那个时候对方正在攀科技呢。如果他也是一波的话，是打不过这么多机枪兵的。只要你够快，小狗 rush，4BG 都是浮云。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[星际争霸 2 的成长]]></title>
        <id>/posts/2012/sc2-growth</id>
        <link href="https://hadb.me/posts/2012/sc2-growth"/>
        <updated>2012-02-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[星际 2 公测的时候，我就开始玩了。我是人族。]]></summary>
        <content type="html"><![CDATA[<p>星际 2 公测的时候，我就开始玩了。我是人族。</p><p>一开始，只会一波流，甚至常常纯枪兵一波流，光头哥都不带，最多升级个兴奋剂，也不升攻防。
我在青铜组的时候，这样打还是可以的，常常能够快速取胜。</p><p>如今，我惯常使用的方法是单兵营开二矿，打神族和虫族基本上还没输过，当然，我还是在白银组，不过，相信，马上就能升级了。</p><p>我从一开始最讨厌虫族的毒爆一波，神族的地堡 rush，到现在最喜欢它们，只要防下来，基本上就赢了。</p><p>白银组，在初期的压力比较小，所以单兵营开二矿算比较偷的打法，却比较稳当，因为初期基本上不会受到压制。</p><p>哈哈~ 慢慢来~</p><p>随便闲聊一下……</p><p>每次打完一局都觉得特别冷，都颤抖，心跳也很快，星际 2 的节奏快。哈哈，毕竟也是比赛嘛！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[【SQLServer】“无法对数据库 'XXX' 执行删除，因为它正用于复制” 的解决方法]]></title>
        <id>/posts/2012/sqlserver-cannot-delete</id>
        <link href="https://hadb.me/posts/2012/sqlserver-cannot-delete"/>
        <updated>2012-02-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从今天起，把编程中遇到的所有问题都记录下来，以便今后参考，以及方便网友查阅，希望我的问题可以帮助到很多志同道合的人们，我也是受很多前辈的提点，一步一步走来，希望大家都不要吝啬，将自己遇到的问题记录下来，因为你的一个记录，也许就节省了别人很多的时间！希望有一天，我们能做到世界最好！]]></summary>
        <content type="html"><![CDATA[<p>从今天起，把编程中遇到的所有问题都记录下来，以便今后参考，以及方便网友查阅，希望我的问题可以帮助到很多志同道合的人们，我也是受很多前辈的提点，一步一步走来，希望大家都不要吝啬，将自己遇到的问题记录下来，因为你的一个记录，也许就节省了别人很多的时间！希望有一天，我们能做到世界最好！</p><hr></hr><p>关于这个错误，是因为我今天在服务器上想把数据库复制到本地，使用了 “发布、订阅” 方案，结果还没成功……尴尬……后来，我就直接把服务器上的 mdf 和 ldf 文件都直接拷到本地上了。后来在本地修改表名的时候出现了这个错误，说“无法对数据库'XXX'执行删除，因为它正用于复制”。</p><p>后来我也是查找了网上的一些方法，只需要执行 sp_removedbreplication 'XXX' 就可以了。</p><p>这个语句的解释是：从数据库中删除所有复制对象，但不更新分发服务器上的数据。此存储过程在发布服务器的发布数据库或订阅服务器的订阅数据库上执行。</p><p>具体不是特别理解，不过，至少能解决我的问题了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于 Win8 磁盘使用率经常处于 100% 的状态的原因和解决方法]]></title>
        <id>/posts/2012/win8-disk-full-usage</id>
        <link href="https://hadb.me/posts/2012/win8-disk-full-usage"/>
        <updated>2012-03-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Win8 消费者预览版发布已经有一段时间了，经过我这段时间的使用，发现了一些问题。]]></summary>
        <content type="html"><![CDATA[<p>Win8 消费者预览版发布已经有一段时间了，经过我这段时间的使用，发现了一些问题。</p><p>其中一个很严重的问题是，我的磁盘经常“嗞嗞”的响，这在之前 Win7 的时候，只会在快速拷文件时才会出现。用 Win8 的任务管理器发现，磁盘使用率在 100%，此时电脑会非常卡。猜测估计是同步了微软账号，在同步数据的原因。于是注销到本地帐户。情况或许已经解决……</p><p>后来还下了个 360 安全卫士 Beta For Win8 版的，优化了下，重启了下，什么的，也就再没有出现类似的状况。也不清楚是由于注销了微软帐户还是由于 360 的优化，还是仅仅是因为重启了下就解决了，总之，按照这些步骤，我是解决了这个问题。希望帮到遇到同样问题的小白鼠。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[【已解决】关于文件名的自动变化问题]]></title>
        <id>/posts/2012/filename-changes</id>
        <link href="https://hadb.me/posts/2012/filename-changes"/>
        <updated>2012-03-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这个问题是今天我在给 Foxmail 设置邮件提示音的时候发现的，我想用 Windows 系统的声音文件。]]></summary>
        <content type="html"><![CDATA[<p>这个问题是今天我在给 Foxmail 设置邮件提示音的时候发现的，我想用 Windows 系统的声音文件。</p><p>于是在个性化里的声音里找到了声音文件，它们位于 <code>C:\Windows\Media\</code> 里的不同文件夹里，我用的是 <code>C:\Windows\Media\Savanna\Windows 通知.wav</code>。</p><p>问题出现了，当我把这个文件复制到 Foxmail 文件夹里面的时候，这个文件变成了 <code>Windows Notify.wav</code>”，这我就搞不懂了。</p><p>我知道文件夹的显示名称可以通过 <code>desktop.ini</code> 里面的设置来改变，可是文件的名称总不能用这种方式变化吧？那这个是怎么实现的呢？</p><hr></hr><p>在虞若奇的提醒下，该问题已解决。
打开该文件夹下的 <code>desktop.ini</code> 文件（需要显示系统隐藏文件），如下：</p><pre><code><span class="line" line="1"><span class="stp6e">[LocalizedFileNames]
</span></span><span class="line" line="2"><span class="su5hD">Windows </span><span class="sFlF4">Balloon.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-711
</span></span><span class="line" line="3"><span class="su5hD">Windows Battery </span><span class="sFlF4">Critical.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-712
</span></span><span class="line" line="4"><span class="su5hD">Windows Battery </span><span class="sFlF4">Low.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-713
</span></span><span class="line" line="5"><span class="su5hD">Windows Critical </span><span class="sFlF4">Stop.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-714
</span></span><span class="line" line="6"><span class="su5hD">Windows </span><span class="sFlF4">Default.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-715
</span></span><span class="line" line="7"><span class="su5hD">Windows </span><span class="sFlF4">Ding.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-716
</span></span><span class="line" line="8"><span class="su5hD">Windows </span><span class="sFlF4">Error.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-717
</span></span><span class="line" line="9"><span class="su5hD">Windows </span><span class="sFlF4">Exclamation.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-718
</span></span><span class="line" line="10"><span class="su5hD">Windows Feed </span><span class="sFlF4">Discovered.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-719
</span></span><span class="line" line="11"><span class="su5hD">Windows Hardware </span><span class="sFlF4">Fail.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-720
</span></span><span class="line" line="12"><span class="su5hD">Windows Hardware </span><span class="sFlF4">Insert.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-721
</span></span><span class="line" line="13"><span class="su5hD">Windows Hardware </span><span class="sFlF4">Remove.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-722
</span></span><span class="line" line="14"><span class="su5hD">Windows Logoff </span><span class="sFlF4">Sound.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-723
</span></span><span class="line" line="15"><span class="su5hD">Windows Logon </span><span class="sFlF4">Sound.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-724
</span></span><span class="line" line="16"><span class="su5hD">Windows </span><span class="sFlF4">Notify.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-727
</span></span><span class="line" line="17"><span class="su5hD">Windows Print </span><span class="sFlF4">complete.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-728
</span></span><span class="line" line="18"><span class="su5hD">Windows </span><span class="sFlF4">Shutdown.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-733
</span></span><span class="line" line="19"><span class="su5hD">Windows Navigation </span><span class="sFlF4">Start.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-734
</span></span><span class="line" line="20"><span class="su5hD">Windows Information </span><span class="sFlF4">Bar.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-736
</span></span><span class="line" line="21"><span class="su5hD">Windows Pop-up </span><span class="sFlF4">Blocked.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-737
</span></span><span class="line" line="22"><span class="su5hD">Windows User Account </span><span class="sFlF4">Control.wav</span><span class="sP7_E">=</span><span class="su5hD">@%windir%\system32\mmres.dll,-738
</span></span></code></pre><p>等号前面是文件本身的文件名，而等号后面则是文件的显示名，它是通过系统 system32 下的 mmres.dll 里的资源来控制的。这样有很多好处，其中之一就是，在不同语言的计算机上显示不同的名称，很方便。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[【已解决】Eclipse 代码提示不显示的问题]]></title>
        <id>/posts/2012/eclipse-not-display-code-hint</id>
        <link href="https://hadb.me/posts/2012/eclipse-not-display-code-hint"/>
        <updated>2012-03-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[前些时候重装了系统，在备份 Android SDK 和 Eclipse 的时候出了问题，然后只能重新下载，SDK 下得我都要吐血了，超慢。然后，我发现 Eclipse 有了新版本的，于是就下载了个新版的，结果出了问题了。所有的 Android 代码都没有了代码自动提示了，按 Alt+/，弹出的框里面什么也没有。苦恼了很久，以为是少装了些什么。网上也查了很久，无果。后来在“Windows/Preference/Java/Editor/Content Assist/Advanced”下面发现了些端倪，没有勾选 Java Proposals，我就很奇怪，然后打开室友的 Eclipse，他的版本跟我之前的版本是一样的，我发现，同样的地方，他选择的是 Java Proposals(Task-Focused)，而 Java Proposals 也没有勾选。而我的新版本里面没有 Java Proposals(Task-Focused)。问题找到了，由于我的配置是使用的之前版本的配置，所以就导致了没有选中 Java Proposals，至于那个(Task-Focused)是什么，还没搞懂……不过，至少我的代码提示是回来了，哈哈哈！可以继续编程了！]]></summary>
        <content type="html"><![CDATA[<p>前些时候重装了系统，在备份 Android SDK 和 Eclipse 的时候出了问题，然后只能重新下载，SDK 下得我都要吐血了，超慢。然后，我发现 Eclipse 有了新版本的，于是就下载了个新版的，结果出了问题了。所有的 Android 代码都没有了代码自动提示了，按 Alt+/，弹出的框里面什么也没有。苦恼了很久，以为是少装了些什么。网上也查了很久，无果。后来在“Windows/Preference/Java/Editor/Content Assist/Advanced”下面发现了些端倪，没有勾选 Java Proposals，我就很奇怪，然后打开室友的 Eclipse，他的版本跟我之前的版本是一样的，我发现，同样的地方，他选择的是 Java Proposals(Task-Focused)，而 Java Proposals 也没有勾选。而我的新版本里面没有 Java Proposals(Task-Focused)。问题找到了，由于我的配置是使用的之前版本的配置，所以就导致了没有选中 Java Proposals，至于那个(Task-Focused)是什么，还没搞懂……不过，至少我的代码提示是回来了，哈哈哈！可以继续编程了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Android 中一个 Activity 多个 intent-filter 的调用方法]]></title>
        <id>/posts/2012/several-intent-filters-in-one-activity</id>
        <link href="https://hadb.me/posts/2012/several-intent-filters-in-one-activity"/>
        <updated>2012-06-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在 Android 中，Activity 允许有很多种调用方式，其中一个方法是使用 <intent-filter>。]]></summary>
        <content type="html"><![CDATA[<p>在 Android 中，Activity 允许有很多种调用方式，其中一个方法是使用 <code><intent-filter></code>。</p><p>比如：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">intent-filter</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">action</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.intent.action.VIEW</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="3"><span class="sP7_E">    <</span><span class="sQzsp">category</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.intent.category.DEFAULT</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="4"><span class="sP7_E">    <</span><span class="sQzsp">category</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.intent.category.BROWSABLE</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="5"><span class="sP7_E">    <</span><span class="sQzsp">data</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">host</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">www.google.com</span><span class="sjJ54">"</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">path</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">m/products/scan</span><span class="sjJ54">"</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">scheme</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">http</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="6"><span class="sP7_E"></</span><span class="sQzsp">intent-filter</span><span class="sP7_E">>
</span></span></code></pre><p>一开始我以为，在浏览器中键入 “<a href="http://www.google.com/m/products/scan%E2%80%9D" rel="nofollow">http://www.google.com/m/products/scan”</a> 就可以调用了的，结果发现浏览器只是正常打开它 T.T</p><p>后来发现，它的调用仍然需要使用 intent。</p><pre><code><span class="line" line="1"><span class="s_bVq">Uri</span><span class="su5hD"> uri </span><span class="smGrS">=</span><span class="su5hD"> Uri</span><span class="sP7_E">.</span><span class="sGLFI">parse</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">http://www.google.com/m/products/scan</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span><span class="line" line="2"><span class="s_bVq">Intent</span><span class="su5hD"> it </span><span class="smGrS">=</span><span class="sVHd0"> new</span><span class="sGLFI"> Intent</span><span class="sP7_E">(</span><span class="su5hD">Intent</span><span class="sP7_E">.</span><span class="su5hD">ACTION_VIEW</span><span class="sP7_E">,</span><span class="su5hD"> uri</span><span class="sP7_E">);
</span></span><span class="line" line="3"><span class="sGLFI">startActivity</span><span class="sP7_E">(</span><span class="su5hD">it</span><span class="sP7_E">);
</span></span></code></pre><p>我是在一个 Button 的 <code>onClick()</code> 方法里写的这些代码，这样按这个按钮，就会调用所有符合要求的含有对应 <code><intent-filter></code> 的 Activity，在我的手机里，有 Chrome Beta、浏览器、快拍二维码、条码扫描器，以及我刚刚创建那个应用的 Activity。</p><p>嗯嗯，记录一下，以后会经常把日常遇到的问题记下来，方便遇到同样问题的开发者们一起学习！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[好易思特 HAOest 官方网站今天正式改版上线！]]></title>
        <id>/posts/2012/haoest-new-website</id>
        <link href="https://hadb.me/posts/2012/haoest-new-website"/>
        <updated>2012-06-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[地址：http://www.haoest.com/]]></summary>
        <content type="html"><![CDATA[<p>地址：<a href="http://www.haoest.com/" rel="nofollow">http://www.haoest.com/</a></p><p>欢迎光临！</p><p>截图如下：</p><figure><img src="https://hadb.me/static/posts/2012/20120622.haoest-new-website/01.jpg"></img></figure><p>好易思特 HAOest 官方网站今天正式改版上线！</p><p>另外，官方博客地址：<a href="http://blog.haoest.com/" rel="nofollow">http://blog.haoest.com/</a></p><p>二维码传送门产品主页：<a href="http://portal.haoest.com/" rel="nofollow">http://portal.haoest.com/</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[进一步完善了网站结构并做了一些 SEO 优化]]></title>
        <id>/posts/2012/website-change-and-seo</id>
        <link href="https://hadb.me/posts/2012/website-change-and-seo"/>
        <updated>2012-06-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[早上起来也没闲着，打开百度、谷歌，搜搜“好易思特”，看看收录情况如何，感觉还是不大给力，并没有达到我的要求。仔细反思反思，感觉还是没有做 SEO 优化的问题，之前看到过一些文章，并没有在意。我一直是拿“陌陌”的主页来学习借鉴的，发现，在百度搜索“陌陌”的时候，它的网站链接下面有一段说明文字，是“陌陌（momo）是一款基于地理位置的移动社交工具，你可以通过陌陌……”之类之类的，我打开它的首页，并没有发现这段文字，很纳闷，于是看它网页的源码，发现了这两个标签：]]></summary>
        <content type="html"><![CDATA[<p>早上起来也没闲着，打开百度、谷歌，搜搜“好易思特”，看看收录情况如何，感觉还是不大给力，并没有达到我的要求。仔细反思反思，感觉还是没有做 SEO 优化的问题，之前看到过一些文章，并没有在意。我一直是拿“陌陌”的主页来学习借鉴的，发现，在百度搜索“陌陌”的时候，它的网站链接下面有一段说明文字，是“陌陌（momo）是一款基于地理位置的移动社交工具，你可以通过陌陌……”之类之类的，我打开它的首页，并没有发现这段文字，很纳闷，于是看它网页的源码，发现了这两个标签：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">meta</span><span class="s9AJx"> name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">keywords</span><span class="sjJ54">"</span><span class="s9AJx"> content</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">XXX</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="2"><span class="sP7_E"><</span><span class="sQzsp">meta</span><span class="s9AJx"> name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">description</span><span class="sjJ54">"</span><span class="s9AJx"> content</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">XXX</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span></code></pre><p>于是，一切都懂了！赶紧给自己也加了下，以便以后百度收录的时候可以显示出来。</p><p>另外，还给网站加了下 favicon，之前用插件加过一次，但是发现兼容性不行，在 IE 里是不能显示的，于是直接自己做了两个 ico 文件，放在目录下了。呵呵，感觉还是蛮好看的。</p><p>另外，尝试着添加了个 Google Analytics 功能，待会儿看看效果怎么样。</p><p>对了，所有文章的链接地址也都改变了，改成以文章名为地址，据说这样对搜索引擎更友好。</p><p>OK，去吃午饭！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[android:clearTaskOnLaunch 的用法]]></title>
        <id>/posts/2012/using-of-android-cleartaskonlaunch</id>
        <link href="https://hadb.me/posts/2012/using-of-android-cleartaskonlaunch"/>
        <updated>2012-06-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[关于 android:clearTaskOnLaunch，网上的资料很少，唯一有几个资料，还说得很含糊，看着让人摸不着头脑，今天硬着头皮看了下英文文档，再结合自己的尝试，终于是稍微理解了它的用处。]]></summary>
        <content type="html"><![CDATA[<p>关于 <code>android:clearTaskOnLaunch</code>，网上的资料很少，唯一有几个资料，还说得很含糊，看着让人摸不着头脑，今天硬着头皮看了下英文文档，再结合自己的尝试，终于是稍微理解了它的用处。</p><p>默认情况下，<code>android:clearTaskOnLaunch</code> 的值是 <code>false</code></p><p>此时，比如你的应用里有 N 个 Activity，其中有个是设置页面，你从主页面进入到设置页面设置了一些东西之后，突然，按了下 Home 键，回到了 Android 的 Home，这时候你做了些别的事情，然后你再次点击你的应用程序图标进入你的应用程序的时候，依旧是回到设置页面，继续先前的工作，这也是大多数应用的情况。</p><p>但是，如果你把 <code>android:clearTaskOnLaunch</code> 的值设为 <code>true</code> 呢？顾名思义，它就在启动的时候把 Task 给清空了，就是你再次点击应用程序图标进入你的应用程序的时候是回到应用程序的第一个页面，而不会回到先前的设置页面。也就是说不保存先前的设置状态。</p><p>至于，什么情况下要这么用，我还没想到，像谷歌的 Zxing 项目，也就是 Android 上的“条码扫描器”，它就是这样的，不管你什么时候重新进入该应用，它显示的都是扫描的界面。</p><p>不过呢，我也发现了个意外情况，就是即使你把 <code>android:clearTaskOnLaunch</code> 的值设为了 <code>true</code>，但是在 Home 界面长按 Home 键，可以调出一个你最近进行的任务，从那个里面点击你的应用是可以回到先前保留的状态的，也就是无视 <code>android:clearTaskOnLaunch</code> 了，至于为什么，目前还没搞明白，有兴趣的可以去官方查看一下文档，我没有仔细去看。</p><p>关于 <code>android:clearTaskOnLaunch</code> 的 <a href="http://developer.android.com/intl/zh-CN/guide/topics/manifest/activity-element.html#clear" rel="nofollow">官方文档</a></p><p>另外，今天更新了下 ADT 20，感觉还不错，Windows 下模拟器的程序图标变了，比以前可爱了，呵呵。以前好像是没图标还是怎么地，完全没印象。这个图标倒还是让人印象深刻。</p><p>2012-07-02 补充：</p><p>前几天搞得焦头烂额，我下了一个开源的项目，在里面把所有的 <code>android:clearTaskOnLaunch</code> 全部删除了，可结果还是重新进入的时候从第一个 Activity 开始，我就纳闷了，纠结了，百思不得其解，以为是应用程序更新的时候，有些地方没有完全更新，然后卸载了，重新安装调试，还是这样。没办法，认真检查所有代码，结果发现了这么个东西：<code>intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);</code> 它在启动每个 Activity 的时候加了个 Flag，<code>Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET</code>，也达到了那样的效果，删掉，然后就是一般的效果了，也就是不管什么时候重新进入应用，进入的是先前停留的地方。嗯~搞定！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Eclipse Juno 下搭建 Android 开发环境]]></title>
        <id>/posts/2012/set-eclipse-juno-for-android</id>
        <link href="https://hadb.me/posts/2012/set-eclipse-juno-for-android"/>
        <updated>2012-07-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Eclipse 官方 28 日正式发布了 Eclipse 4.2，代号 Juno。同时，Eclipse 也宣布将使用 Eclipse 4.2 来开发以后的 Eclipse 版本。因此，在我升级 Eclipse 的同时，我也来制作一个 Juno 下搭建 Android 开发环境的教程，其实跟以往的教程是没多大区别的，只是比较新一点儿而已。]]></summary>
        <content type="html"><![CDATA[<p>Eclipse 官方 28 日正式发布了 Eclipse 4.2，代号 Juno。同时，Eclipse 也宣布将使用 Eclipse 4.2 来开发以后的 Eclipse 版本。因此，在我升级 Eclipse 的同时，我也来制作一个 Juno 下搭建 Android 开发环境的教程，其实跟以往的教程是没多大区别的，只是比较新一点儿而已。</p><p>前提条件，安装了 java JDK，如何安装可以自己百度下。</p><p>首先，去 <a href="http://www.eclipse.org/" rel="nofollow">Eclipse 官网</a> 下载最新的 Eclipse 安装包。</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/01.png"></img></figure><p>可以看到，首页已经变成了 Juno 的宣传。点击 Downloads 进入下载页面，我们选择 Eclipse IDE for Java Developers，其实也可以选择 Eclipse for Mobile Developers，他们相差不大。</p><p>下载好之后，解压就可以了。</p><p>打开 eclipse.exe，可以看到，新的 Logo：</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/02.png"></img></figure><p>首先，会让你设置一个工作目录</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/03.png"></img></figure><p>自己设置一下，将下面的复选框打勾，也就是将这个目录作为默认工作目录，并不再提示，这样以后新建的项目都会在这个目录中。</p><p>好，我们进入到熟悉的欢迎界面：</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/04.png"></img></figure><p>下面我们来安装汉化包，我个人还是比较喜欢中文的界面，如果喜欢英文界面的朋友可以跳过这一步。</p><p>到 <a href="http://build.eclipse.org/technology/babel/babel_language_packs/" rel="nofollow">这里</a> 可以找到 Babel 多语言项目的最新包，然后找到简体中文的部分：</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/05.png"></img></figure><p>下载对应的包，解压到 Eclipse 的安装位置，然后重启 Eclipse，就可以看到汉化之后的 Eclipse 界面了。</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/06.png"></img></figure><p>好，下面开始安装 Android 插件</p><p>到“帮助-安装新软件”里点击添加，然后如图输入 <a href="https://dl-ssl.google.com/android/eclipse/" rel="nofollow">https://dl-ssl.google.com/android/eclipse/</a> 这个地址</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/07.png"></img></figure><p>然后，选择 Developer Tools</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/08.png"></img></figure><p>然后就直接下一步下一步，有个地方 Agree 一下，然后就等待一会儿，下载安装。</p><p>安装完成之后重启一下 Eclipse。</p><p>你会发现多了些 Android 的东西，进入“窗口-首选项”，找到 Android 一栏：</p><figure><img src="https://hadb.me/static/posts/2012/20120702.set-eclipse-juno-for-android/09.png"></img></figure><p>由于我之前已经安装过 Android SDK，所以会有这些列表，如果你没有安装过 Android SDK，可以去下载一下，然后在这个 SDK Location 里选择你 SDK 所在的目录。</p><p>嗯，至此，Juno 下搭建 Android 的开发环境就完成了，如果不够详细，可以再参考下面的文章：<a href="http://blog.csdn.net/webrobot/article/details/7304831" rel="nofollow">Android 开发环境配置图文教程 (jdk + eclipse + android sdk)</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何修改 Windows 库图标]]></title>
        <id>/posts/2012/how-to-modify-windows-library-icon</id>
        <link href="https://hadb.me/posts/2012/how-to-modify-windows-library-icon"/>
        <updated>2012-07-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[相信大家也用了这么久的 Win7 了，对 Windows 的库应该也不陌生，库与传统的文件夹的差别是，文件夹是以物理位置确定的，而一个库里面可以链接多个文件夹，打个比方，你在 E 盘有个 TDDownload 文件夹，这个是迅雷默认的下载文件夹，然后你在“C:\Users\用户名\Downloads”这里也有一个下载文件夹，这个是系统默认的下载位置，然后你可以把他们都加到系统的“下载”这个库中，并且你可以给这个库设置一个默认位置，这样，你在这个库中可以同时看到两个文件夹里的文件，而你把文件放到库中的时候，它会默认保存在你设置的默认位置里。好，这是库的简单介绍。]]></summary>
        <content type="html"><![CDATA[<p>相信大家也用了这么久的 Win7 了，对 Windows 的库应该也不陌生，库与传统的文件夹的差别是，文件夹是以物理位置确定的，而一个库里面可以链接多个文件夹，打个比方，你在 E 盘有个 TDDownload 文件夹，这个是迅雷默认的下载文件夹，然后你在“C:\Users\用户名\Downloads”这里也有一个下载文件夹，这个是系统默认的下载位置，然后你可以把他们都加到系统的“下载”这个库中，并且你可以给这个库设置一个默认位置，这样，你在这个库中可以同时看到两个文件夹里的文件，而你把文件放到库中的时候，它会默认保存在你设置的默认位置里。好，这是库的简单介绍。</p><p>下面谈谈正事，Windows 库新建的时候，默认的图标是这个：</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/01.jpg"></img></figure><p>而如果你在库属性里修改它的优化设置：</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/02.jpg"></img></figure><p>你还可以得到另外四个图标：</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/03.jpg"></img></figure><p>这能满足你的要求吗？当然不能。</p><p>有人说，我会用替换系统 dll 的方式来修改图标，然而那样会修改所有图标，而且，并不能按照你指定的方式修改某个库文件的图标，比方说，你现在新建了一个“项目”库，里面包含了你所有项目的文件夹，你想给这个库修改一下图标，怎么做呢？</p><p>其实库是 Windows 7 中一种特殊的文件类型(.library-ms)，就像快捷方式(.lnk)、收藏夹(.url)一样，我们用记事本编辑它，就可以设置它的名称、包含路径和显示图标等。库文件存放在系统盘的%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Libraries，我们现在将刚刚新建的库文件拖入记事本：</p><pre><code><span class="line" line="1"><span class="sP7_E"><?</span><span class="sQzsp">xml</span><span class="s9AJx"> version</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1.0</span><span class="sjJ54">"</span><span class="s9AJx"> encoding</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">UTF-8</span><span class="sjJ54">"</span><span class="sP7_E">?>
</span></span><span class="line" line="2"><span class="sP7_E"><</span><span class="sQzsp">libraryDescription</span><span class="s9AJx"> xmlns</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">http://schemas.microsoft.com/windows/2009/library</span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">    <</span><span class="sQzsp">ownerSID</span><span class="sP7_E">></span><span class="su5hD">S-1-5-21-1202136470-2341405795-2135911285-1000</span><span class="sP7_E"></</span><span class="sQzsp">ownerSID</span><span class="sP7_E">>
</span></span><span class="line" line="4"><span class="sP7_E">    <</span><span class="sQzsp">version</span><span class="sP7_E">></span><span class="su5hD">1</span><span class="sP7_E"></</span><span class="sQzsp">version</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">    <</span><span class="sQzsp">isLibraryPinned</span><span class="sP7_E">></span><span class="su5hD">true</span><span class="sP7_E"></</span><span class="sQzsp">isLibraryPinned</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E"></</span><span class="sQzsp">libraryDescription</span><span class="sP7_E">>
</span></span></code></pre><p>你会看到，它是一段 XML 格式的文件，当然新建的库里面的结点会比较少，有的库里面的内容就会很多，比如：</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/04.jpg"></img></figure><p>不用去看懂这些是什么，总之，你只需要在 <code><isLibraryPinned>true</isLibraryPinned></code> 的后面添加一段内容：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">iconReference</span><span class="sP7_E">></span><span class="su5hD">这里填写图标位置</span><span class="sP7_E"></</span><span class="sQzsp">iconReference</span><span class="sP7_E">>
</span></span></code></pre><p>图标位置可以是 ico 的，例如上面的中文部分换成 D:\icons\hadb.ico，然后再保存，这个库的图标就变成了 D:\icons\目录下的 hadb.ico 了，当然，你还可以使用 dll 格式的图标集，例如我为我的“项目”库所使用的：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">iconReference</span><span class="sP7_E">></span><span class="su5hD">D:\My College\Projects\WindowsFolderIcons\C#\Black Agua Onyx\bin\Debug\Black Agua Onyx.dll,84</span><span class="sP7_E"></</span><span class="sQzsp">iconReference</span><span class="sP7_E">>
</span></span></code></pre><p>这里逗号前是 dll 的位置，逗号后是所要使用的图标在 dll 图标集中的位置。</p><p>这个图标集是我使用 C#制作的，就是将 N 个 ico 文件做成了 dll 图标集，怎么做呢？我将在今后有空的时候再写一个制作 dll 图标集的教程。</p><p>好了，如何修改 Windows 库的图标教程就到这里，谢谢大家！</p><p>最后，看一下修改后的效果吧~</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/05.jpg"></img></figure><p>对了，我那个 dll 里大概有 100 个我精心下载并且修改扩充的一些文件夹图标，基本上是黑色的文件夹上加上了一些不同的标志，应该能够满足日常的需要，你也可以用来为普通的文件夹修改图标哦，这个相信大家都会，直接在文件夹属性里修改即可。</p><figure><img src="https://hadb.me/static/posts/2012/20120720.how-to-modify-windows-library-icon/06.jpg"></img></figure><p>需要下载的可以在下面的链接中下载：</p><p><a href="http://dl.dbank.com/c0w9d7d4t5" rel="nofollow">http://dl.dbank.com/c0w9d7d4t5</a></p><p>原大小：29.6M，压缩后：1.94M 由于是图标集，所以压缩效果很好。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Android Wifi 的设置、连接操作]]></title>
        <id>/posts/2012/android-wifi-connection</id>
        <link href="https://hadb.me/posts/2012/android-wifi-connection"/>
        <updated>2012-07-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我项目中这部分的代码是参考的这里的：http://blog.csdn.net/cscmaker/article/details/7032277]]></summary>
        <content type="html"><![CDATA[<p>我项目中这部分的代码是参考的这里的：<a href="http://blog.csdn.net/cscmaker/article/details/7032277" rel="nofollow">http://blog.csdn.net/cscmaker/article/details/7032277</a></p><p>但是，参考了这段代码之后可没少忙活！怎么试都连不上，wifi 的信息是创建了，可就是没法连接上。百思不得其解，后来我想，会不会是设置的地方出了问题。</p><p>原来是这样设置的：</p><pre><code><span class="line" line="1"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">preSharedKey </span><span class="smGrS">=</span><span class="sjJ54"> "</span><span class="s_hVV">\"</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> Password </span><span class="smGrS">+</span><span class="sjJ54"> "</span><span class="s_hVV">\"</span><span class="sjJ54">"</span><span class="sP7_E">;
</span></span><span class="line" line="2"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">hiddenSSID </span><span class="smGrS">=</span><span class="s39Yj"> true</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">allowedAuthAlgorithms</span><span class="sP7_E">.</span><span class="sGLFI">set</span><span class="sP7_E">(</span><span class="su5hD">WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">AuthAlgorithm</span><span class="sP7_E">.</span><span class="su5hD">OPEN</span><span class="sP7_E">);
</span></span><span class="line" line="4"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">allowedGroupCiphers</span><span class="sP7_E">.</span><span class="sGLFI">set</span><span class="sP7_E">(</span><span class="su5hD">WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">GroupCipher</span><span class="sP7_E">.</span><span class="su5hD">TKIP</span><span class="sP7_E">);
</span></span><span class="line" line="5"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">allowedKeyManagement</span><span class="sP7_E">.</span><span class="sGLFI">set</span><span class="sP7_E">(</span><span class="su5hD">WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">KeyMgmt</span><span class="sP7_E">.</span><span class="su5hD">WPA_PSK</span><span class="sP7_E">);
</span></span><span class="line" line="6"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">allowedPairwiseCiphers</span><span class="sP7_E">.</span><span class="sGLFI">set</span><span class="sP7_E">(</span><span class="su5hD">WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">PairwiseCipher</span><span class="sP7_E">.</span><span class="su5hD">TKIP</span><span class="sP7_E">);
</span></span><span class="line" line="7"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">allowedProtocols</span><span class="sP7_E">.</span><span class="sGLFI">set</span><span class="sP7_E">(</span><span class="su5hD">WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">Protocol</span><span class="sP7_E">.</span><span class="su5hD">WPA</span><span class="sP7_E">);
</span></span><span class="line" line="8"><span class="su5hD">config</span><span class="sP7_E">.</span><span class="su5hD">status </span><span class="smGrS">=</span><span class="su5hD"> WifiConfiguration</span><span class="sP7_E">.</span><span class="su5hD">Status</span><span class="sP7_E">.</span><span class="su5hD">ENABLED</span><span class="sP7_E">;
</span></span></code></pre><p>我是这样检测的，我自己手动连接好一个网络，然后获取这个连接，将下面的信息输出：</p><pre><code><span class="line" line="1"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">SSID:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">SSID</span><span class="sP7_E">);
</span></span><span class="line" line="2"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">preSharedKey:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">preSharedKey</span><span class="sP7_E">);
</span></span><span class="line" line="3"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">hiddenSSID:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">hiddenSSID</span><span class="sP7_E">);
</span></span><span class="line" line="4"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">allowedAuthAlgorithms:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">allowedAuthAlgorithms</span><span class="sP7_E">);
</span></span><span class="line" line="5"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">allowedGroupCiphers:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">allowedGroupCiphers</span><span class="sP7_E">);
</span></span><span class="line" line="6"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">allowedKeyManagement:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">allowedKeyManagement</span><span class="sP7_E">);
</span></span><span class="line" line="7"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">allowedPairwiseCiphers:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">allowedPairwiseCiphers</span><span class="sP7_E">);
</span></span><span class="line" line="8"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">allowedProtocols:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">allowedProtocols</span><span class="sP7_E">);
</span></span><span class="line" line="9"><span class="su5hD">Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">status:</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> existingConfig</span><span class="sP7_E">.</span><span class="su5hD">status</span><span class="sP7_E">);
</span></span></code></pre><p>这样，就可以对比，就知道怎样的是对的，哪里不对。</p><p>Log 如下：</p><pre><code><span class="line" line="1"><span class="su5hD">Created Wifi </span><span class="sUdit">Info
</span></span><span class="line" line="2"><span class="su5hD">SSID:</span><span class="s_sjI">"HADB-ASUS"
</span></span><span class="line" line="3"><span class="su5hD">preSharedKey:*
</span></span><span class="line" line="4"><span class="su5hD">hiddenSSID:</span><span class="s39Yj">false
</span></span><span class="line" line="5"><span class="su5hD">allowedAuthAlgorithms:{}
</span></span><span class="line" line="6"><span class="su5hD">allowedGroupCiphers:{</span><span class="s39Yj">0</span><span class="su5hD">, </span><span class="s39Yj">1</span><span class="su5hD">, </span><span class="s39Yj">2</span><span class="su5hD">, </span><span class="s39Yj">3</span><span class="su5hD">}
</span></span><span class="line" line="7"><span class="su5hD">allowedPairwiseCiphers:{</span><span class="s39Yj">1</span><span class="su5hD">, </span><span class="s39Yj">2</span><span class="su5hD">}
</span></span><span class="line" line="8"><span class="su5hD">allowedProtocols:{</span><span class="s39Yj">0</span><span class="su5hD">, </span><span class="s39Yj">1</span><span class="su5hD">}
</span></span><span class="line" line="9"><span class="su5hD">status:</span><span class="s39Yj">0
</span></span><span class="line" line="10"><span class="su5hD">bRet=</span><span class="s39Yj">true
</span></span></code></pre><p>当然，除了 <code>preSharedKey</code> 输出的是被隐藏了的 <code>"*"</code>，因为安全性问题，密码是无法输出的，其它的项有的并不止一个值，后来看文档，发现，这些值其实都是有默认值的，根本不需要手动去设置它们，只需要将 <code>status</code> 设为 <code>WifiConfiguration.Status.ENABLED</code> 就可以了，密码也是要设一下的，其它的都可以注释掉。</p><p>于是，问题完美解决了……啊哈哈哈哈……</p><p>这次也给了我自己一个经验，那就是别人的代码也不能完全相信，还是要自己亲自实践才行。但参考代码这个步骤是必须的，因为它能带给你无数的灵感，还能指引你方向，因为有时候你根本不知道从何下手，参考一些代码之后，就会大体了解怎么去做，需要引用哪些包，然后在对这些包进行搜索，查看官方文档，很快，问题就可以迎刃而解了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[C# 获取指定网卡的 IP 地址]]></title>
        <id>/posts/2012/csharp-get-ipaddress</id>
        <link href="https://hadb.me/posts/2012/csharp-get-ipaddress"/>
        <updated>2012-07-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近几天都在玩游戏，没怎么编程，感觉好空虚啊！主要是之前在 Android 上建立 wifi 热点出现了一些问题，难以进展下去，于是便耽搁了，今天决定先跳过那个问题，Android 手机之间的传输先暂时不考虑，先做一下 Android 与 PC 之间的数据传输。]]></summary>
        <content type="html"><![CDATA[<p>最近几天都在玩游戏，没怎么编程，感觉好空虚啊！主要是之前在 Android 上建立 wifi 热点出现了一些问题，难以进展下去，于是便耽搁了，今天决定先跳过那个问题，Android 手机之间的传输先暂时不考虑，先做一下 Android 与 PC 之间的数据传输。</p><p>刚刚主要完成了这么一件事情，根据指定的网卡获取其 ip，以便之后的 socket 使用。</p><p>会出现这个问题是因为我是利用 Win7 的 netsh 功能建立的虚拟网卡，它与其它网卡可以同时存在，这就意味着这台主机可能拥有多个 ip 地址，然而我们需要的只是我们建立的虚拟网卡的那个 ip 地址，实现方法如下：</p><pre><code><span class="line" line="1"><span class="sbgvK">NetworkInterface</span><span class="sP7_E">[]</span><span class="sbgvK"> adapters</span><span class="smGrS"> =</span><span class="su5hD"> NetworkInterface</span><span class="sP7_E">.</span><span class="sGLFI">GetAllNetworkInterfaces</span><span class="sP7_E">();</span><span class="sutJx"> //获取本机所有网卡对象
</span></span><span class="line" line="2"><span class="sVHd0">foreach</span><span class="sP7_E"> (</span><span class="sbgvK">NetworkInterface</span><span class="sbgvK"> adapter</span><span class="sVHd0"> in</span><span class="su5hD"> adapters</span><span class="sP7_E">)
</span></span><span class="line" line="3"><span class="sP7_E">{
</span></span><span class="line" line="4"><span class="sVHd0">    if</span><span class="sP7_E"> (</span><span class="su5hD">adapter</span><span class="sP7_E">.</span><span class="su5hD">Description</span><span class="sP7_E">.</span><span class="sGLFI">Contains</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">Virtual</span><span class="sjJ54">"</span><span class="sP7_E">))</span><span class="sutJx"> //枚举条件：描述中包含 "Virtual"
</span></span><span class="line" line="5"><span class="sP7_E">    {
</span></span><span class="line" line="6"><span class="sbgvK">        IPInterfaceProperties</span><span class="sbgvK"> ipProperties</span><span class="smGrS"> =</span><span class="su5hD"> adapter</span><span class="sP7_E">.</span><span class="sGLFI">GetIPProperties</span><span class="sP7_E">();</span><span class="sutJx"> //获取 IP 配置
</span></span><span class="line" line="7"><span class="sbgvK">        UnicastIPAddressInformationCollection</span><span class="sbgvK"> ipCollection</span><span class="smGrS"> =</span><span class="su5hD"> ipProperties</span><span class="sP7_E">.</span><span class="su5hD">UnicastAddresses</span><span class="sP7_E">;</span><span class="sutJx"> //获取单播地址集
</span></span><span class="line" line="8"><span class="sVHd0">        foreach</span><span class="sP7_E"> (</span><span class="sbgvK">UnicastIPAddressInformation</span><span class="sbgvK"> ip</span><span class="sVHd0"> in</span><span class="su5hD"> ipCollection</span><span class="sP7_E">)
</span></span><span class="line" line="9"><span class="sP7_E">        {
</span></span><span class="line" line="10"><span class="sVHd0">            if</span><span class="sP7_E"> (</span><span class="su5hD">ip</span><span class="sP7_E">.</span><span class="su5hD">Address</span><span class="sP7_E">.</span><span class="su5hD">AddressFamily </span><span class="smGrS">==</span><span class="su5hD"> AddressFamily</span><span class="sP7_E">.</span><span class="su5hD">InterNetwork</span><span class="sP7_E">)</span><span class="sutJx"> //只要 ipv4 的
</span></span><span class="line" line="11"><span class="su5hD">                ipAddress </span><span class="smGrS">=</span><span class="su5hD"> ip</span><span class="sP7_E">.</span><span class="su5hD">Address</span><span class="sP7_E">;</span><span class="sutJx"> //获取 ip
</span></span><span class="line" line="12"><span class="sP7_E">        }
</span></span><span class="line" line="13"><span class="sP7_E">    }
</span></span><span class="line" line="14"><span class="sP7_E">}
</span></span></code></pre><p>任务完成！下面开始学习 socket 通信。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Android 单元测试]]></title>
        <id>/posts/2012/android-unit-test</id>
        <link href="https://hadb.me/posts/2012/android-unit-test"/>
        <updated>2012-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天终于向让我退缩了很久的单元测试前进了一步。]]></summary>
        <content type="html"><![CDATA[<p>今天终于向让我退缩了很久的单元测试前进了一步。</p><p>很早就知道 Android 里可以建立测试项目，但我一直不明白怎么去用，也总觉得我的这些个小项目，需要用到测试这么高端的东西吗？今天突然对之前搁置很久的全能计算器的重构有了一些灵感，写了个计算专用的工具类，然而，之前项目中有不少错误，现在是没法运行的，于是，我只想对这个工具类进行测试，怎么做呢？</p><p>如果不用单元测试的话，得先把整个项目的错误改掉，编译成功，然后通过日志输出的方式来测试那个类，但相当繁琐。如果使用单元测试的话，就相当轻松啦。</p><p>Android 里的单元测试有两种方式，一种是建立一个新的测试项目（Android Test Project），那个似乎是对整个项目进行测试的，没有仔细去了解，还是比较庞大，跟我们这里的要求不符。</p><p>另一种方式，则是在需要测试的项目里新建一个测试类，继承 <code>AndroidTestCase</code>，然后运行时使用 Android JUnit Test 的方式运行就可以了。</p><p>下面开始看代码：</p><p>首先，需要对项目的 <code>AndroidManifest.xml</code> 文件进行一些改动</p><p>在 <code><application></code> 结点里加入：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">uses-library</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.test.runner</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span></code></pre><p>在 <code><application></code> 结点外加入：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">instrumentation
</span></span><span class="line" line="2"><span class="s9AJx">    android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.test.InstrumentationTestRunner</span><span class="sjJ54">"
</span></span><span class="line" line="3"><span class="s9AJx">    android</span><span class="stp6e">:</span><span class="s9AJx">label</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">Test</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="s9AJx">    android</span><span class="stp6e">:</span><span class="s9AJx">targetPackage</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">你的包名</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span></code></pre><p>注意，此处的包名一定要与最上方的</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">manifest</span><span class="s9AJx"> xmlns</span><span class="stp6e">:</span><span class="s9AJx">android</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">http://schemas.android.com/apk/res/android</span><span class="sjJ54">"
</span></span><span class="line" line="2"><span class="s9AJx">    package</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">包名</span><span class="sjJ54">"
</span></span><span class="line" line="3"><span class="s9AJx">    android</span><span class="stp6e">:</span><span class="s9AJx">versionCode</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">版本号</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="s9AJx">    android</span><span class="stp6e">:</span><span class="s9AJx">versionName</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">版本名</span><span class="sjJ54">"</span><span class="sP7_E"> >
</span></span></code></pre><p>这里的包名一致。</p><p>同时，还需要添加权限，也在 <code><application></code> 外：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">uses-permission</span><span class="s9AJx"> android</span><span class="stp6e">:</span><span class="s9AJx">name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">android.permission.RUN_INSTRUMENTATION</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span></code></pre><p>下面，新建一个类，我是新建的一个 CalcTest 类，用来测试 Calc 类的运行情况：</p><pre><code><span class="line" line="1"><span class="sbsja">public</span><span class="sbsja"> class</span><span class="sbgvK"> CalcTest</span><span class="sbsja"> extends</span><span class="sbgvK"> AndroidTestCase
</span></span><span class="line" line="2"><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="sbsja">    private</span><span class="sbsja"> static</span><span class="sbsja"> final</span><span class="s_bVq"> String</span><span class="su5hD"> TAG </span><span class="smGrS">=</span><span class="sjJ54"> "</span><span class="s_sjI">CalcTest</span><span class="sjJ54">"</span><span class="sP7_E">;
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sbsja">    public</span><span class="sbsja"> void</span><span class="sGLFI"> testPlus</span><span class="sP7_E">()
</span></span><span class="line" line="6"><span class="sP7_E">    {
</span></span><span class="line" line="7"><span class="s_bVq">        String</span><span class="su5hD"> answer </span><span class="smGrS">=</span><span class="su5hD"> Calc</span><span class="sP7_E">.</span><span class="sGLFI">add</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">1.31</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">4.63</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span><span class="line" line="8"><span class="su5hD">        Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="su5hD"> answer</span><span class="sP7_E">);
</span></span><span class="line" line="9"><span class="su5hD">        Assert</span><span class="sP7_E">.</span><span class="sGLFI">assertEquals</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">5.94</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="su5hD"> answer</span><span class="sP7_E">);
</span></span><span class="line" line="10"><span class="sP7_E">    }
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="sbsja">    public</span><span class="sbsja"> void</span><span class="sGLFI"> testMinus</span><span class="sP7_E">()
</span></span><span class="line" line="13"><span class="sP7_E">    {
</span></span><span class="line" line="14"><span class="s_bVq">        String</span><span class="su5hD"> answer </span><span class="smGrS">=</span><span class="su5hD"> Calc</span><span class="sP7_E">.</span><span class="sGLFI">subtract</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">1.7</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">1.6</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span><span class="line" line="15"><span class="su5hD">        Log</span><span class="sP7_E">.</span><span class="sGLFI">i</span><span class="sP7_E">(</span><span class="su5hD">TAG</span><span class="sP7_E">,</span><span class="su5hD"> answer</span><span class="sP7_E">);
</span></span><span class="line" line="16"><span class="su5hD">        Assert</span><span class="sP7_E">.</span><span class="sGLFI">assertEquals</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">0.1</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="su5hD"> answer</span><span class="sP7_E">);
</span></span><span class="line" line="17"><span class="sP7_E">    }
</span></span><span class="line" line="18"><span class="sP7_E">}
</span></span></code></pre><p>Assert 是 junit 里的一个类，比如我使用的是 assertEquals 方法，如果两个参数的值相等，则运行时不会报错，如果值不等，则会报错。</p><p>编写好测试类之后，右击，运行方式里选择 Android JUnit Test，然后等待即可。</p><p>测试成功的截图：</p><figure><img src="https://hadb.me/static/posts/2012/20120731.android-unit-test/01.png"></img></figure><p>如果我们把 testMinus()中的 Assert.assertEquals(“0.1″, answer)改成 Assert.assertEquals(“0.2″, answer)，再运行的话，就会出现：</p><figure><img src="https://hadb.me/static/posts/2012/20120731.android-unit-test/02.png"></img></figure><p>可以在故障跟踪里看到到底哪里出了问题。</p><p>好了，简单的 Android 单元测试就介绍到这里，以后可以很轻松地对某个类进行测试了，而不需要运行整个项目。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[近期开发小结]]></title>
        <id>/posts/2012/brief-summary</id>
        <link href="https://hadb.me/posts/2012/brief-summary"/>
        <updated>2012-08-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[很久没有写博客了，主要是这些天在忙着开发校友录的网站，从 php、js 的门外汉，逐渐跨进了门儿，虽然开始的框架、布局、样式都是在模仿，布局大多是模仿的 github 的网站，因为我发现它的个人资料修改的界面很合我意，简单清爽而且不老土，于是花了一两天才把网站的基本框架界面搭好了，要知道，我对 CSS 以前几乎是一窍不通的，哈哈，还是很有成就感的。由于有 java 的基础，在接触 js 的时候也就没什么困难了，也只有 ajax 操作那块有个异步操作让我费了不少心，显示的提示图片总是不正确，后来就没有用异步，直接等完成之后再继续下面的代码。]]></summary>
        <content type="html"><![CDATA[<p>很久没有写博客了，主要是这些天在忙着开发校友录的网站，从 php、js 的门外汉，逐渐跨进了门儿，虽然开始的框架、布局、样式都是在模仿，布局大多是模仿的 github 的网站，因为我发现它的个人资料修改的界面很合我意，简单清爽而且不老土，于是花了一两天才把网站的基本框架界面搭好了，要知道，我对 CSS 以前几乎是一窍不通的，哈哈，还是很有成就感的。由于有 java 的基础，在接触 js 的时候也就没什么困难了，也只有 ajax 操作那块有个异步操作让我费了不少心，显示的提示图片总是不正确，后来就没有用异步，直接等完成之后再继续下面的代码。</p><p>本来还信誓旦旦暑假要把二维码传送门的项目做完的，但是在 socket 和 wifi 传输文件的部分还是遇到了一些技术难题，让我有些退缩，加上后来要做校友录，于是就搁置了也没有再继续，本还想参加谷歌开发者大赛的，也不知道能不能按时完成了，开学过去得多用用心了。</p><p>校友录那边等我把基本功能都完成了再在博客上认真写一篇开发文档，把详细的开发流程什么的都写一下，方便大家学习。昨天申请了网易网站联盟的广告，通过审核了，以后可以在网站上放广告了，希望能给我带来些许零花钱。这就得提高我的网站的流量才行，而方法很简单，就是丰富内容，希望我多写一些教程类的文章能够吸收一些正在学习的开发者们来关注我的博客，也就增加了网站的流量。</p><p>其实我还在策划做一个星际 2 的技术网站，因为是自己的爱好嘛，在上面放一些打法、战术，转一些贴，放一些新闻什么的，这个好处是，一来我自己也可以学习整理一些战术，二来，受众是星际 2 玩家，广告的投放也可以针对些，点击率可能会高些，这个等我有时间了再来搞。</p><p>心里面还是有点不甘心，二维码传送门还是希望自己能够尽早完成，毕竟辛苦了那么久，还没能看到成效呢。</p><p>这两天都在玩星际，家人都说，怎么一天到晚就玩，也不找点正事做，也是，玩多了自己也有点浪费生命的感觉，既然这样，还得硬着头皮，努力学习啊！前方的路还很长，android 上我还是个菜鸟，php 只能刚刚好满足我自己的需求，摸爬滚打，边摸索边学习边实践，倒也蛮开心的。</p><p>这次也来看看，网站上的广告点击效果如何，反正我知道，手机广告带来的收入绝对是很可观的，不知道网站上的效果如何，算是做个对比吧，这样以后也有侧重一些。</p><p>移动广告的专业联盟倒是蛮多的，什么多盟啊、谷歌啊、有米啊什么的，价格也不错，点击率也高；网页广告联盟多也多，但要么门槛高，比如谷歌广告，居然审核不通过，百度广告吧，还必须得备案过的网站才行，这不让我尴尬么，找来找去找了个网易联盟，也有些许限制，像我这个网站现在就不能投放 CPC、CPM 和 CPV 的广告，就只能放 CPS 和 CPA 类的，也就是说，我现在的广告只能按效果计费，就是比如游戏广告，玩家得注册了玩了才能获得佣金，商品类的广告，用户得实际购买了我才能有钱拿。像 CPC 就只要点击了就能拿钱，CPM 是弹窗类型，只要弹出了就拿钱，CPV 是那种右下角的弹窗，这几个类型的广告我估计得是网站流量很高他们才开放，这个我还得慢慢努力，因为 CPS 和 CPA 的收入效果确实是不大好。</p><p>Mars 老师的 android 教学视频也不大更新了，即使更新，我发现也只是教的一些入门级的知识，真正实用的还是要靠自己去融会贯通，去不断尝试。</p><p>努力，加油！HADB！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用监控宝实时监控你的网站访问情况]]></title>
        <id>/posts/2012/jiankongbao</id>
        <link href="https://hadb.me/posts/2012/jiankongbao"/>
        <updated>2012-08-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[前几天发现了个好东西，监控宝]]></summary>
        <content type="html"><![CDATA[<p>前几天发现了个好东西，<a href="http://www.jiankongbao.com/" rel="nofollow">监控宝</a></p><p>可以实时监控网站的访问情况：</p><figure><img src="https://hadb.me/static/posts/2012/20120829.jiankongbao/01.png" alt="概述"></img><figcaption>概述</figcaption></figure><figure><img src="https://hadb.me/static/posts/2012/20120829.jiankongbao/02.png" alt="响应时间统计"></img><figcaption>响应时间统计</figcaption></figure><p>在网站出故障的时候，还会短信提醒</p><p>不过，免费版只有 10 条短信限额，所幸的是，你可以接受我的邀请注册，那样我们都可以额外获得 10 条免费提醒短信。</p><p>还在等什么？<a href="http://www.jiankongbao.com/invite/x2vcdo" rel="nofollow">http://www.jiankongbao.com/invite/x2vcdo</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[暑期工作小结]]></title>
        <id>/posts/2012/summer-summary</id>
        <link href="https://hadb.me/posts/2012/summer-summary"/>
        <updated>2012-09-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[时间过得真快，暑假飕飕就过去了……其实这个暑假过得还是挺充实的，也学到了不少，尤其是 PHP、JS 和网站建设方面，当然，在 Android 和 C#上也有提升。今天下午就要上飞机回学校了，趁着这会儿有点儿时间，匆匆写个小结，个人认为，写小结是件很有用的事情，当然，不是那种老师布置的小结，自己给自己写的小结才是最有用的。]]></summary>
        <content type="html"><![CDATA[<p>时间过得真快，暑假飕飕就过去了……其实这个暑假过得还是挺充实的，也学到了不少，尤其是 PHP、JS 和网站建设方面，当然，在 Android 和 C#上也有提升。今天下午就要上飞机回学校了，趁着这会儿有点儿时间，匆匆写个小结，个人认为，写小结是件很有用的事情，当然，不是那种老师布置的小结，自己给自己写的小结才是最有用的。</p><ol><li>最得意的莫过于星际争霸 2 战术资源站的建立，这个是我这个星期才开始做的，也是暑假所有进行的项目中，唯一一个称得上是圆满完成的，因为它比较简单，弄个 WP 的模版，简单修改下就行了，当然，还有广告的植入，也是恰巧知道网易有个广告联盟，而且它的门槛不高，又恰巧它对游戏相关的网站提供 CPC、CPV 等等各种类型的广告，这才让我动了心，加上星际 2 本身也是由网易运营的，所以投放网易的广告再合适不过了。接下来要做的，只是不断搜集星际 2 的战术，发表上去就可以了，最好是找几个有共同爱好的，一同搜集和编辑，不断扩大网站的知名度，之前在星际 2 的百度贴吧和 NEOTV 的星际 2 论坛的反响都不错，这也给了我很大的鼓励和信心，我想，这个我会一直做下去，就像少帮主说的那样，“打一辈子星际”，我想，我也会一直持续下去，即使它并不能给我带来多少收入，因为它是我的爱好，而且我还有个要进大师组的梦想。</li><li>排在第二的，要数校友录的开发了，尽管目前尚未完全开发完毕，但以我的估算，应该也有 60%吧，目前登录功能和个人信息的修改保存功能都已经完成了，信息的安全、加密也都进行得很顺利，校友信息的共享和查询还暂时未进行，但对于暑假前对 php 几乎一窍不通的我来说，还是很有成就感的，我也对自己更加有信心了，我感兴趣的想学什么都能学好，虽然学校的考试并不是考得特别好，那或许是我真的不感兴趣。校友录目前是受吴老和顾老所托，专为钱伟长学院设计的，顾老当时对我说，经费的问题尽管对吴老师开口，他那里有好多的自由经费……虽然我并不一定需要他们的经费，不过还是给了我很大的鼓励。因为，我的想法是做一个通用的校友录，尽管在 SNS 如此发达的今天，想要找找老同学的资料还并不是很方便，因为并不是所有人都活跃在 SNS 上，甚至好多老同学你连 QQ 号都无从得知，人人、网易他们也都有校友录，不过我暂时还没去看他们的具体功能，但是我并没有感觉身边有多少人在使用。虽然我的校友录开发好，最多目前也只是院内使用，不过我也会持续更新下去，推广下去，希望以后有一天，在毕业之前，老师们会对同学们说，大家不要忘记去“好易思特校友录”上，登录我们的班级，把自己的信息资料填写好，这样大家今后联系起来也会很方便。倘若真的能到这一步，那我也真心满意足了。</li><li>第三要数星际 2 客户端切换器的开发，也并没有花我太多时间，一两天，主要是进行的工作并不是很多，只是对 Variables.txt 文档的编辑，当然健壮性还是很强的，我考虑到了很多情况，比如文件不存在，文件只读，里面指定行不存在……等等，当然这个软件目前需求量并不是很大，大家都希望只用一个客户端就能切换所有服务器，人都是贪心的，至少之前星际大脚是完成了这样的壮举，不知道 1.5.0 之后他们还能不能做出这样的效果，我也很期待，这款软件也受到了批评，的确，他并不是很优秀，我也不一定能深刻去研究暴雪的客户端多语言机制，不过至少，我自己是从中受益了，我一直很主张的一点是，多花点时间写代码，更方便地去生活。这个软件我不一定会持续做下去，不过我可能会去模仿 SIMON 大神制作的跨服软件，因为他是开源的，我可以从中学习和研究下。</li><li>视频秀 CaptureShow，把它放在第四说，因为我已经开发完成很久了，它还是我在那个小桌子上完成的，因为我来成都，一开始是在我房间里的那个小桌子上编程和玩游戏的，后来觉得那个桌子太小，椅子太矮，不大习惯，我才改到客厅的大桌子上玩电脑，所以我说是在小桌子上完成的，意思就是它是我刚到成都的时候开发的，我刚看了下是 7 月 20 号，那是我刚到成都的一个星期左右，应该是这个暑假开发的第一款软件。</li><li>另外昨天还把好易思特的主页重新改版了，改版的原因待会儿在第 6 条我也会说。但是改版并没有完全结束，所以我还未发表公开的声明，不过相比以前的主页，现在这个要好看点，并且是动态的，有三个滚动大图片，用以显示重点产品，就跟很多大型公司的主页那种类型差不多。</li><li>二维码传送门。把他放在最后说，因为它真的让我有点儿沮丧。暑假前我兴致勃勃，雄心壮志，胸有成竹，信誓旦旦地说要把它开发好，并且还希望能够大赚一笔再加上参加大赛得的奖金可以给自己买个 macbook pro retina 的，虽然是夸张了点，但从中也可以看到我对它的期望和评价，因为它真的是一款很有潜力的软件，也正因为如此，它的开发也并不像我想象中地那么容易，第一个原因是我真的想把它开发好，我不想轻易地发布，在功能没有完善的情况下，因为那样只会流失很大一部分用户。第二个原因是，我的确遇到了很大的技术难题，市场上手机与 PC 通过 wifi 传输文件的软件很少的原因就是因为技术上并不是那么容易实现，而与二维码结合起来，也只有我想到了，所以我很欣慰，但我同时也有一丝的害怕，害怕别人知道之后先我一步开发出来，因为高手很多，不管是民间的还是官方的，像我以前有个想法，就是 QQ 视频聊天的步骤很复杂，像我爷爷这样的就不会用，如果把它单独弄出来，不管是用 QQ 的服务还是独立建立的视频服务，只要简单操作就能跟家人聊天，那样对于老年人这样不大会用电脑的岂不是很方便？岂不是很有市场？当然这个想法还是我一两年前想到的，我都已经忘却了。结果前几天，我在 im.qq.com 上看到，腾讯已然出了 QQ 视频桌面版，专为老年人设计，简单的操作完成视频功能。我感到一丝失落，同时也觉得自己的想法很不错，至少跟人家腾讯想到一起了。二维码与 Wifi 的结合，目前还没有一件这样的软件，如果我开发完成，那绝对应该是破天荒的。因此，我最想完成的项目其实就是它了，但我暑假却并没有花多少时间在上面，因为我遇到一些问题后退缩了，加之后来有其他项目的想法，所以一直就搁置了，我也很抱歉，之前向一些同学朋友还有我家人吹牛过……牛皮都吹爆了……还在官网上弄了一张很大的图片，上面说“今夏，我会给你一个惊喜”，这也是我昨天为什么要撤掉这个横幅的原因……很遗憾很抱歉这个项目暑假没能完成，我之前也是太小瞧它了，谷歌 Android 大学生挑战赛和腾讯的创新应用大赛，我之前还想用这款软件参赛的，目前来看，大赛截止日期也不远了，今年的大赛怕是参加不了了，我希望明年能够带着它继续参赛，十年磨一剑嘛，时间长的必是精品，就如暴雪开发星际 2 一直跳票，跳了这么多年，我一直觉得，跳票是一种负责任的态度，因为想推出更优秀的产品。我这是在为自己辩解么？算是吧。其实，有时候想想，又有多少人在关注我呢？我按时推出与推迟一年再推出又有多少人会在意？有多少人会记得我曾经说，这款软件会创造历史？其实并没有多少人会在意。但作为一个负责任的人，我还是要对大家说声对不起，我会继续努力，尽早开发完成，让大家能用上这款软件。这样，我自己心里也好受些，我不想让别人因为相信我，在一直期盼着这款软件却没能等到它在暑假推出。不过，今后的一年我会一直努力，在它推出的那天，我会让你们都知道。</li></ol><p>下午就得走了，还是有点舍不得，但又满怀期待，校园的生活还是很期盼的，大学生涯也过去了一半了，再毕业之前好好珍惜这两年。我知道，就算我说再多要好好学习之类的话，也不一定真会说到做到，但我希望我能更认真一点，多花一点时间去学习，编程上可以少花点时间，因为编程的日子还很长。</p><p>相信学校大多数同学都已经到了，wait, I’ll meet you tonight!</p><p>（若有错别字啥的见谅哈，时间紧迫，我还想再打几局星际，所以没有仔细检查）</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于 sqlite error no such table 错误的解决办法]]></title>
        <id>/posts/2012/sqlite-error-no-such-table</id>
        <link href="https://hadb.me/posts/2012/sqlite-error-no-such-table"/>
        <updated>2012-09-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天开始尝试为“星际 2 客户端切换器”添加 Win7 下的 JumpList 功能，如图：]]></summary>
        <content type="html"><![CDATA[<p>今天开始尝试为“星际 2 客户端切换器”添加 Win7 下的 JumpList 功能，如图：</p><figure><img src="https://hadb.me/static/posts/2012/20120925.sqlite-error-no-such-table/01.png"></img></figure><figure><img src="https://hadb.me/static/posts/2012/20120925.sqlite-error-no-such-table/02.png"></img></figure><p>这样可以在任务栏和开始菜单中快速启动相应的客户端，而不需要启动软件。</p><p>在测试过程中遇到一个错误，<code>sqlite error no such table…</code>，百思不得其解，一直以为是由于 JumpList 在未启动程序的情况下调用程序内的方法导致数据库没有加载的问题，但后来始终没有弄明白，一直在搜索关于 JumpList 调用的问题。后来干脆直接搜 <code>sqlite error no such table</code> 的错误原因，才发现问题所在。我的数据库调用使用的是相对地址：</p><pre><code><span class="line" line="1"><span class="sbgvK">SQLiteConnection</span><span class="sbgvK"> conn</span><span class="smGrS"> =</span><span class="smGrS"> new</span><span class="sbgvK"> SQLiteConnection</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">Data Source = settings.db;</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span></code></pre><p>在直接打开软件是没有任何问题的，但是通过 JumpList 调用的话，我怀疑启动的位置不一样，所以导致找不到数据库，于是换成：</p><pre><code><span class="line" line="1"><span class="sbgvK">SQLiteConnection</span><span class="sbgvK"> conn</span><span class="smGrS"> =</span><span class="smGrS"> new</span><span class="sbgvK"> SQLiteConnection</span><span class="sP7_E">(</span><span class="sjJ54">@"</span><span class="s_sjI">Data Source =</span><span class="sjJ54">"</span><span class="smGrS"> +</span><span class="su5hD"> System</span><span class="sP7_E">.</span><span class="su5hD">Windows</span><span class="sP7_E">.</span><span class="su5hD">Forms</span><span class="sP7_E">.</span><span class="su5hD">Application</span><span class="sP7_E">.</span><span class="su5hD">StartupPath </span><span class="smGrS">+</span><span class="sjJ54"> "</span><span class="s_hVV">\\</span><span class="s_sjI">settings.db;</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span></code></pre><p>问题搞定！</p><p>今后数据库文件还是用绝对位置比较妥当。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[好易思特 HAOest 祝大家中秋节快乐！]]></title>
        <id>/posts/2012/haoest-happy-moon-festival</id>
        <link href="https://hadb.me/posts/2012/haoest-happy-moon-festival"/>
        <updated>2012-09-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[好易思特 HAOest 官网已经正式上线，期待您的光临：http://www.haoest.com]]></summary>
        <content type="html"><![CDATA[<p>好易思特 HAOest 官网已经正式上线，期待您的光临：<a href="http://www.haoest.com" rel="nofollow">http://www.haoest.com</a></p><p>好易思特已上线的产品：</p><ol><li>星际争霸 2 战术资源站：<a href="http://sc2.haoest.com" rel="nofollow">http://sc2.haoest.com</a></li><li>星际争霸 2 客户端切换器：<a href="http://sc2.haoest.com/archives/184" rel="nofollow">http://sc2.haoest.com/archives/184</a></li><li>好易思特 HAOest 官方博客：<a href="http://blog.haoest.com" rel="nofollow">http://blog.haoest.com</a></li><li>视频秀 CaptureShow：<a href="http://www.haoest.com/products/capture-show/" rel="nofollow">http://www.haoest.com/products/capture-show/</a></li></ol><p>正在开发中的产品有：</p><ol><li>星际争霸 2 录像分析小工具（开发中）</li><li>星际争霸 2 战术大全手机版（调研中）</li><li>二维码传送门：（<a href="http://portal.haoest.com/" rel="nofollow">前往查看最新进展</a>）</li><li>钱伟长学院校友录：（<a href="http://alumni.haoest.com/updates" rel="nofollow">前往查看最新更新</a>）</li></ol><p>由于目前为了节约开支，服务器租在海外，有时会短暂出现无法访问的情况，还请大家见谅！等租赁期到期我会迁移到国内来，届时，访问速度和稳定性能会大大提升。</p><p>好易思特 HAOest 会一如既往开发新的产品，目前主要集中于星际争霸 2 相关的产品，因为这是我最喜欢的游戏~</p><p>如果您一直是好易思特 HAOest 的粉丝，相信您对好易思特的节日福利并不陌生，还记得当年的中秋贺卡么？</p><p>如果您对好易思特还不太了解，欢迎前往好易思特博客关注最新动态，或者关注邓斌 HADB 的人人~</p><p>今后，好易思特会在节日里为您送上最诚挚的祝福~</p><p>再次，祝贺您中秋佳节幸福、快乐！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于 Ubuntu 下音乐列表乱码的解决方法]]></title>
        <id>/posts/2012/ubuntu-mp3-messy-code</id>
        <link href="https://hadb.me/posts/2012/ubuntu-mp3-messy-code"/>
        <updated>2012-11-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[使用 Ubuntu 听音乐的时候，播放列表不少歌曲显示为乱码，非常不爽。]]></summary>
        <content type="html"><![CDATA[<p>使用 Ubuntu 听音乐的时候，播放列表不少歌曲显示为乱码，非常不爽。</p><p>简单的方法就是将 MP3 标签转换为 Unicode 编码，要使用到 <code>python-mutagen</code>，在新立得软件管理中可以直接找到，也可以用以下的命令进行安装：</p><pre><code><span class="line" line="1"><span class="sbgvK">sudo</span><span class="s_sjI"> apt-get</span><span class="s_sjI"> install</span><span class="s_sjI"> python-mutagen
</span></span></code></pre><p>使用方法：</p><p>在终端中进入音乐文件所在的目录，执行：</p><pre><code><span class="line" line="1"><span class="sbgvK">mid3iconv</span><span class="stzsN"> -e</span><span class="s_sjI"> gbk</span><span class="s_hVV"> *</span><span class="s_sjI">.mp3
</span></span></code></pre><p>如果有子目录的话，再执行：</p><pre><code><span class="line" line="1"><span class="sbgvK">mid3iconv</span><span class="stzsN"> -e</span><span class="s_sjI"> GBK</span><span class="s_hVV"> *</span><span class="s_sjI">/</span><span class="s_hVV">*</span><span class="s_sjI">.mp3
</span></span></code></pre><p>现在再看看，是不是搞定啦？</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[未能加载文件或程序集 “XXXXXXX” 或它的某一个依赖项。试图加载格式不正确的程序的解决方法]]></title>
        <id>/posts/2012/could-not-load-file-or-assembly</id>
        <link href="https://hadb.me/posts/2012/could-not-load-file-or-assembly"/>
        <updated>2012-11-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[好久没有写博客了。]]></summary>
        <content type="html"><![CDATA[<p>好久没有写博客了。</p><p>这个问题之前遇到过一次，但是当时没有记下来，导致今天遇到这个问题的时候，我已经想不起来是什么原因了，又花了好长时间搜索、查找，才终于解决，所以我决定把它记下来。</p><p>无法加载文件或程序集，可能有多种原因，网上多数是因为 DLL 没有复制到 bin 文件夹下，或者程序集名称不一致等等，但这些并不是我的项目里所发生的。</p><p>我的问题其实是这样的，我的 A 项目引用了我自己写的一个类库 B，B 里面引用了一个 DLL，但是这个 DLL 的目标平台是 x86，而我的 A 项目的目标平台是 Any CPU，导致无法运行，所以只需要把 A 项目的目标平台修改为 x86，就 OK 了。至于类库 B，还是默认的 Any CPU，好像也没有什么问题。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[静态成员初始化异常的解决办法]]></title>
        <id>/posts/2012/type-initialization-exception</id>
        <link href="https://hadb.me/posts/2012/type-initialization-exception"/>
        <updated>2012-12-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天遇到了这样一个问题，在调用某个类的静态方法时，产生了这样一个异常“未处理 TypeInitializationException “XXXXX”的类型初始值设定项引发异常。”然而，这个方法我是做了异常处理的，可就是无法捕获到这个异常的位置。后来发现，是由于这个类里的静态成员初始化产生了异常，这个要如何解决呢？]]></summary>
        <content type="html"><![CDATA[<p>今天遇到了这样一个问题，在调用某个类的静态方法时，产生了这样一个异常“未处理 TypeInitializationException “XXXXX”的类型初始值设定项引发异常。”然而，这个方法我是做了异常处理的，可就是无法捕获到这个异常的位置。后来发现，是由于这个类里的静态成员初始化产生了异常，这个要如何解决呢？</p><p>我们可以利用静态构造函数来解决这个问题，静态构造函数和实例构造函数之间的区别在于静态构造函数是由 CLR 调用执行的，所以静态构造函数只能是一个，同时不能有参数。</p><p>使用方法如下：</p><pre><code><span class="line" line="1"><span class="sbsja">public</span><span class="sG8yY"> class</span><span class="sbgvK"> Command
</span></span><span class="line" line="2"><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="sbsja">    private</span><span class="sbsja"> static</span><span class="sbgvK"> SimpleDatabase</span><span class="sbgvK"> simpleDatabase</span><span class="sP7_E">;
</span></span><span class="line" line="4"><span class="sbsja">    static</span><span class="sGLFI"> Command</span><span class="sP7_E">()</span><span class="sutJx"> //前面不能有修饰符
</span></span><span class="line" line="5"><span class="sP7_E">    {
</span></span><span class="line" line="6"><span class="sVHd0">        try
</span></span><span class="line" line="7"><span class="sP7_E">        {
</span></span><span class="line" line="8"><span class="su5hD">            simpleDatabase </span><span class="smGrS">=</span><span class="smGrS"> new</span><span class="sbgvK"> SimpleDatabase</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">settings.db</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">mima</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span><span class="line" line="9"><span class="sP7_E">        }
</span></span><span class="line" line="10"><span class="sVHd0">        catch
</span></span><span class="line" line="11"><span class="sP7_E">        {
</span></span><span class="line" line="12"><span class="sutJx">            //处理异常
</span></span><span class="line" line="13"><span class="sP7_E">        }
</span></span><span class="line" line="14"><span class="sP7_E">    }
</span></span><span class="line" line="15"><span class="sP7_E">}
</span></span></code></pre><p>注意， 在 catch 到异常之后，我刚开始还想 <code>throw new Exception("SimpleDatabase初始化失败")</code>，但是这样是不行的，依旧会在外层报出 “TypeInitializationException” 的异常。所以异常只能在静态构造函数内部处理掉，我的解决办法是，既然我不能打开自己建立的数据库，那么这个数据库文件肯定损坏了，或者并不是我原先生成的数据库，那我就重新建立一个数据库文件好了，因为我这个数据库文件里只是存放的一些简单配置，所以无妨。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Mono For Android 试用感想]]></title>
        <id>/posts/2012/try-mono-for-android</id>
        <link href="https://hadb.me/posts/2012/try-mono-for-android"/>
        <updated>2012-12-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天发现一个很厉害的工具，Mono，可以使用 C#来写 Android 程序，如果可以用 Visual Studio 来写 Android 程序，那简直太爽了！]]></summary>
        <content type="html"><![CDATA[<p>昨天发现一个很厉害的工具，Mono，可以使用 C#来写 Android 程序，如果可以用 Visual Studio 来写 Android 程序，那简直太爽了！</p><p>很兴奋，花了很长时间把搭建环境需要的东西都下载好了。早上花了一早上把环境搭建好了，测试了一个 Demo，很伤心。</p><p>以为它生成的 apk 文件是可以直接在机器上运行的，然而发现它还需要在 Android 上安装它所需要的环境，需要安装十几兆的东西，这个不能忍。也不知道是不是免费版的原因，总之不令我满意。Over。还是乖乖用 Eclipse 和 Java 吧。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[【WPF 学习日记】SnapsToDevicePixels 属性]]></title>
        <id>/posts/2012/snaps-to-device-pixels</id>
        <link href="https://hadb.me/posts/2012/snaps-to-device-pixels"/>
        <updated>2012-12-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天在给 ListBoxItem 添加 Border 的时候，发现 BorderThickness = 1 时，出现了意料之外的问题。]]></summary>
        <content type="html"><![CDATA[<p>今天在给 ListBoxItem 添加 Border 的时候，发现 <code>BorderThickness = 1</code> 时，出现了意料之外的问题。</p><p>我给每个 ListBoxItem 的 Border 设置为，上、左、右的厚度为 0，下方的厚度为 1，这样的效果是把 ListBoxItem 分隔开来，但是我发现他们之间的分割线，居然不一样厚，有点模糊，不是点阵的线，而是有那种抗锯齿的感觉，很不爽。</p><p>后来发现 Border 有 <code>SnapsToDevicePixels</code> 这么个属性，看名字感觉或许有效果，把它打勾之后，发现果然问题解决了。它的作用是使像素与显示器对齐，我的理解就是点阵效果。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2013 寒假计划]]></title>
        <id>/posts/2013/2013-winter-holiday-plan</id>
        <link href="https://hadb.me/posts/2013/2013-winter-holiday-plan"/>
        <updated>2013-01-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[放假已经有两天了，明早回家，正式开始我的寒假生活。]]></summary>
        <content type="html"><![CDATA[<p>放假已经有两天了，明早回家，正式开始我的寒假生活。</p><p>由于衣服太多，占了行李箱太多空间，导致我不能带太多书回去了，而且我压根儿也没想带什么书回去，还记得大一那年寒假，带了 N 本书回去，行李箱重得要死，结果呢，带回去的书连封面都没翻开过。</p><p>当然，那本利用图书馆自助服务无限续借的 bug 一直霸占着的超级厚的《Microsoft Visual C# 2010 Step by Step》我还是带回去了，外国人写的书就是不一样，实在是面面俱到，有太多我还不熟悉的地方。好好打牢基础也是不错的。ListManager 会使用 WPF 来写界面也正是我看了这本书之后才想去尝试的，没想到让我和 WPF 竟有种相见恨晚的感觉。</p><p>我已经决定今后开发的产品都基于.NET，虽说并不是每台机子上都会有.NET，但它实在太方便太强大让我无法割舍，我都做个安装包就是了，没.NET 的给自动下载安装个就是了。其实我一直不清楚微软为什么不把.NET4.0 作为 Win7 的更新给强制安装下，那样大家就方便多了。这点上，我跟小天想法不一样，小天选择了另外一条路，他要从 C++这条路上开辟一条血路，从底层开始写。用惯了 C#之后，我实在是不想写 C++，尤其是接触了 WPF 强大的 UI 绘制能力之后，更是不想用 C++来写界面了。我相信，随着.NET 的普及，C#的使用会越来越广，尤其是 WPF 中的 XAML。第一次听说 XAML 是在听 Windows8 的某个开发者大会上，当时 Windows8 刚出来，开发者在讲解如何为 Windows8 开发 Metro 界面下的应用（当时还叫做 Metro 界面），记得很清楚，当时在食堂，大概是为了避暑吹空调，边吃饭边插着耳机看着电脑，吃力地听着英语。当时就想，我一定要好好学习 XAML，开发 Windows8 应用。然而当时还只是想法，因为并没有真正去做。当后来接触到 WPF，发现 WPF 里用的就是 XAML 的时候，我就决定好好学一下。小时候总听大人们说，世上无难事，其实真是这样，真的开始学，慢慢摸索，解决问题之后，发现也并没有想象中那么遥不可及。ListManager 就是我尝试 WPF 的练笔，打算等 Surface Pro 到了之后就尝试着把 ListManager 移植到 Win8 上。</p><p>前些日子在论坛偶然看到个叫火车头的采集软件，百度了一下，还真不得了，如获至宝。新版叫火车采集器，可以批量采集网站中的文章，并自动发布到服务器上。之前就想过去做个吸金的网站，利用搜索引擎带来流量，赚点零花钱。垃圾点不要紧，不求太多回头客，每次搜索来一次就够了。这也可以说是站长们口中的“垃圾站”，当然，垃圾不会从我手上产生，我肯定会精益求精做好一点的。研究了一天火车采集器，主要对于不同网站，需要自己分析网页代码，针对不同网站要自己配置采集位置、采集过滤，还有对于自己的站点还要配置发布策略等等，研究了一天，还是很有收获的，DedeCMS 和 Wordpress 上都测试成功，可以发布上去。寒假好好努力下，做个好易思特女性频道，文章内容就从新浪女性、腾讯女性这些站点采集。为什么做女性频道，感觉女性频道流量会更多一点，比如可以放一些 sexy 又不过分的图片来吸引点击，相比星际 2 的那个站点来说，流量肯定会多很多，毕竟星际 2 属于小众游戏。</p><p>寒假大概这么规划，开通并开始运营好易思特女性频道，当然，这个我希望等域名备案审核下来之后搞，所以，先搞 ListManager，把桌面版更新到 1.2，解决 1.1 中的一些 bug 修复，另外界面的列表部分重写，自定义的控件，可以支持更为丰富的列表，将会把任务级别用图标来显示，而不是单调的文字，1.2 会更加友好，启动时的加载过程会用一个 SplashScreen 来过渡，另外我还打算加入数据的导入导出功能，可以跟朋友分享列表中的内容，比如小天上课时 ListManager 记录了作业，他可以把作业这一项导出，然后在我电脑上导入一下就行了。1.2 发布之后会推送更新，1.1 版本的都可以自动更新上。另外，1.2 会以安装包的形式发布，届时会检测用户系统是否有.NET4.0，没有的话在线下载安装，这样会大大减小安装包的体积。至于 Win8 版的移植，这个要看 surface pro 什么时候发售了，如果寒假能买到的话，我肯定会迫不及待地尝试着去做一下的。很看好 Win8 的应用商店，希望以后开发 Windows 上的软件也能获得收入。虽然 Win8 开发者许可证要有 Visa 信用卡才能获取，不过，总可以解决的。</p><p>至于寒假结束之后再过两周就要到来的考试，我想，到时候再说吧。寒假把韩语复习下，一定要学会打韩语字，跟那个韩国同学用韩语交流几句，总让他打中文，感觉有点说不过去。</p><p>至于我的二维码传送门项目，我想又得延后了，当初学 WPF 就是为了重写二维码传送门的界面，等我把这一切都弄好，网站运营起来，收入稳定起来，surface 到手，考试也结束之后，我就开始天天抱着 surface 专心写我的二维码传送门，我觉得它可以作为我的毕业设计，以及毕业后的主要项目，我相信它很有市场，如果我能做到理想中的完美的话。当然，我相信，只要我肯花功夫，有像元旦那些天写 ListManager 那样一天工作十几个小时的那种斗志的话，我想，我会实现的。试想，今后大家手机之间、手机与电脑之间，与平板之间，总之就是各个终端之间，要传输文件的时候，打开二维码传送门，扫描一下就可以开始传输了。每当我想到这个场景，我总会很开心，很有力量，不管多难我都要去做，而且随着我的坚持，那个场景越来越清晰。希望在明年，我能实现基本功能，先做 Windows7 跟 Android 的部分，至于其它终端和平台，以后再慢慢去添加。</p><p>当然，寒假里，打星际 2 也是必不可少的，既然是放假，游戏还是要玩的。</p><p>总之，希望寒假能够过得充实！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[利用路由器 WDS 桥接扩大无线范围，让你家全方位 WiFi 覆盖！]]></title>
        <id>/posts/2013/use-wds-to-cover-your-house-with-wifi</id>
        <link href="https://hadb.me/posts/2013/use-wds-to-cover-your-house-with-wifi"/>
        <updated>2013-01-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[前段时间，老家终于通了光纤，送了个无线光猫，感觉好强大，发现信号很好，装在二楼，我在一楼，照样能搜到信号，但是却只有三格，感觉不爽。于是准备试验一下路由器的 WDS 桥接功能。]]></summary>
        <content type="html"><![CDATA[<p>前段时间，老家终于通了光纤，送了个无线光猫，感觉好强大，发现信号很好，装在二楼，我在一楼，照样能搜到信号，但是却只有三格，感觉不爽。于是准备试验一下路由器的 WDS 桥接功能。</p><p>先看图，这个是无线光猫的设置页面：</p><figure><img src="https://hadb.me/static/posts/2013/20130121.use-wds-to-cover-your-house-with-wifi/01.png"></img></figure><p>记住，无线信道要手动选，这里我选择 8。</p><p>然后连接到路由器上，我用的是 FAST FW153R，还有个 TP-Link 的，但是太老了，WDS 设置不是很方便，所以就用 FAST 的这个来试验。</p><figure><img src="https://hadb.me/static/posts/2013/20130121.use-wds-to-cover-your-house-with-wifi/02.png"></img></figure><p>SSID 和桥接的 SSID 填写一样的，密码也是，这样当你电脑移动位置时，就不用重新连接，因为用的是同样的 SSID 和密码。注意，信道和频段带宽都要选择一样的。之前我频段带宽这里用的是自动，结果无线网络搜索的时候有两个 ChinaNet-HADB，信号格数不一样，明显是两个信号没有合并，估计是因为有两个频段的原因。在设置完成之后，重启路由器，然后就会发现，网络连接里的 ChinaNet-HADB 信号已经变成满格了~这是因为由于路由器在中间进行了一次搭桥，扩大了无线的范围。</p><figure><img src="https://hadb.me/static/posts/2013/20130121.use-wds-to-cover-your-house-with-wifi/03.png"></img></figure><p>哈哈，好了，WDS 桥接试验成功！</p><p>这只是第一步，其实这样桥接对我来说并没有太大作用，因为原本就可以搜到三格信号，对网络的影响也没什么。我要试验桥接，是为了更重要的事情。</p><p>由于是单宽带，所以一台电脑拨号是可以的，但是两台就不行了，手机也不行。而光猫装在楼上，利用一个路由器进行 PPPoE 拨号，它信号根本传不到楼下，因为我的路由器信号实在是不咋的，跟光猫的信号强度无法比。于是，我决定在楼上放一个路由器，用来拨号，并发送无线网络，然后利用另一个路由器对它的信号进行桥接，让我在楼下也能搜到，当然我还是比较担心的，因为这个距离实在是太远了，我原来的那两个路由器穿墙的功能都不是很强。我家光猫在楼上爸妈的房间里，他们的房间到楼上的客厅，有道墙，楼上客厅到楼梯有道墙，楼梯下来就是楼下的客厅，然后距离我的房间又是一道墙，我和爸妈的房间在一个对角线上，而且还不在同一层，拐弯抹角，实在是太远。再加上我的无线路由器没有无线 WAN 功能，也就是说它得用线连接在光猫上，这样一来，第一个路由器只能放在爸妈的房间了。感觉两个小路由器不够用啊！看来得让爸回来时把他们那里的路由器也带回来，这样二楼客厅放一个对第一个路由器进行桥接，一楼客厅再放一个对二楼客厅的路由器桥接，这样一来，一楼二楼就可以全方位 Wifi 覆盖了！爽！</p><p>感觉学校的 ShuWlan 就是这样的原理，不管在哪个教室都可以搜到信号，多个路由器的覆盖范围是有重叠的，但是却只会显示一个 ShuWlan，就是因为它们进行的是桥接，而不是各自独立。这样，只要第一个路由器有接入信号，其他路由器甚至只需要供电就行了，就可以无限扩大覆盖范围了。当然，学校的无线覆盖应该没有这么简单，应该有其它更高级的设置，不过应该就是利用的桥接的原理。</p><p>怎样？大家看了是不是有点蠢蠢欲动呢？在家里走到哪儿都能上网是不是很爽？当然，不同的路由器桥接的设置不大一样，大家可以根据自己的路由器型号百度下 WDS 设置就行了。</p><p>有问题可以留言哦！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[服务器迁移完毕]]></title>
        <id>/posts/2013/server-migration-finished</id>
        <link href="https://hadb.me/posts/2013/server-migration-finished"/>
        <updated>2013-01-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[搞了一晚上了，终于把新服务器安顿好了，内容已经全部迁移到新服务器上了，尽管新服务器的响应问题还是让我有些不满，但毕竟比原来美国的那个服务器要好多了。先将就将就吧，毕竟价格很便宜。]]></summary>
        <content type="html"><![CDATA[<p>搞了一晚上了，终于把新服务器安顿好了，内容已经全部迁移到新服务器上了，尽管新服务器的响应问题还是让我有些不满，但毕竟比原来美国的那个服务器要好多了。先将就将就吧，毕竟价格很便宜。</p><p>备案通过后，百度联盟也成功审核了，开始用上了百度的广告，百度的广告比网易联盟的要丰富多了，而且对于网站也没有限制，不管是什么站点都可以放任何广告，像网易联盟的你要想放 CPV 的广告，你的网站还必须得是游戏或者别的什么类的，反正我这个博客类的是不可以放 CPV 广告的，搞得我很伤感。</p><p>接下来要做的就是好好安抚搜索引擎了，好好搞下 SEO，这些日子折腾来折腾去，又是换 ip，又是网站暂停的，索引量一直上不去，尤其是百度，收录比谷歌慢得不是一丁半点。要多弄一些原创性内容。</p><p>正在筹划 lady.haoest.com，打算用 DedeCMS 的模版，因为感觉 WordPress 不适合用来做那种类型的网站，WordPress 适合做博客和产品主页、公司主页等，对于信息量非常大的站点来说，Wordpress 有点力不从心了。</p><p>另外，星际 2 战术资源站还要对部分文章内容进行重新编辑，当初是直接用文章采集器采集的，尽管我已经最大可能地优化了采集数据，但是还有部分排版的小问题，以及图片的问题，我打算把图片全部下载下来放到自己空间里，毕竟用外链不大可靠。</p><p>嗯，心里一块石头落了地。晚安！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[卸载 Orbit 之后 Chrome 下载仍指向 Orbit 的解决方法]]></title>
        <id>/posts/2013/uninstall-orbit-plugin-for-chrome</id>
        <link href="https://hadb.me/posts/2013/uninstall-orbit-plugin-for-chrome"/>
        <updated>2013-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前使用过 Orbit，外国的一款 P2P 下载软件，比较优秀精简，大可替代迅雷。我之前用它是使用他的 Grab++功能，可以捕捉 own3D 上的视频，这个让我用得很爽。说起迅雷，我总是很不爽，迅雷的界面实在是太土了，一味追求绚丽的效果，界面总是卡死，这一点上，Orbit 实在是好多了，简洁的界面，从不会卡死。然而，迅雷在国内的下载速度确实是应该比 Orbit 好一点，具体我也没有去测试，可以确定的是 Orbit 是不会在后台默默上传的，所以相对来说，可能下载速度会稍微差一点点。所以后来我也就不用了，卸载了。]]></summary>
        <content type="html"><![CDATA[<p>之前使用过 Orbit，外国的一款 P2P 下载软件，比较优秀精简，大可替代迅雷。我之前用它是使用他的 Grab++功能，可以捕捉 own3D 上的视频，这个让我用得很爽。说起迅雷，我总是很不爽，迅雷的界面实在是太土了，一味追求绚丽的效果，界面总是卡死，这一点上，Orbit 实在是好多了，简洁的界面，从不会卡死。然而，迅雷在国内的下载速度确实是应该比 Orbit 好一点，具体我也没有去测试，可以确定的是 Orbit 是不会在后台默默上传的，所以相对来说，可能下载速度会稍微差一点点。所以后来我也就不用了，卸载了。</p><p>然而，卸载之后却发生了问题，我在 Chrome 下直接点击一些链接，还是会出现“Download transferred to Orbit Downloader. Click ‘Previous’ to go back and keep browsing.”，如下图：</p><figure><img src="https://hadb.me/static/posts/2013/20130216.uninstall-orbit-plugin-for-chrome/01.png"></img></figure><p>导致我一直以为我没有卸载 Orbit，后来我找来找去，都找不到 Orbit 的卸载文件，也找不到它的安装目录，我想，应该是卸载了的呀！后来一想，既然是在 Chrome 下出现问题，估计是插件的原因，于是在 Chrome 地址栏输入：<code>chrome://plugins/</code>进入 Chrome 的插件管理，果然发现一个 Orbit Downloader 的插件，如下图，停用，问题解决。</p><figure><img src="https://hadb.me/static/posts/2013/20130216.uninstall-orbit-plugin-for-chrome/02.png"></img></figure><p>当然，可以去插件的目录，把 nporbit.dll 删除即可。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Sublime Text 2 超级强大的代码编辑器，如获至宝！]]></title>
        <id>/posts/2013/sublime-text-2-supergood-code-editor</id>
        <link href="https://hadb.me/posts/2013/sublime-text-2-supergood-code-editor"/>
        <updated>2013-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[好久没有写代码了，今天稍微温习了下 PHP，依旧是选择 CodeIgniter 的框架，今天看了一个 CodeIgniter 的教学视频：20 分钟创建一个小型博客 http://codeigniter.org.cn/tutorials/watch/blog，作者是 Derek Jones，虽然不知道他是谁，不过讲得还是不错的。但是，看了这个视频，我最大的收获是，竟然还有如此完美的代码编辑器，听了几遍都没有听出作者介绍的这个 IDE 叫什么，后来多方搜索，终于找到了这个 IDE 的名字 TextMate，很完美，但是只适用于 Mac 平台的，并没有 Windows 版的。]]></summary>
        <content type="html"><![CDATA[<p>好久没有写代码了，今天稍微温习了下 PHP，依旧是选择 CodeIgniter 的框架，今天看了一个 CodeIgniter 的教学视频：20 分钟创建一个小型博客 <a href="http://codeigniter.org.cn/tutorials/watch/blog" rel="nofollow">http://codeigniter.org.cn/tutorials/watch/blog</a>，作者是 Derek Jones，虽然不知道他是谁，不过讲得还是不错的。但是，看了这个视频，我最大的收获是，竟然还有如此完美的代码编辑器，听了几遍都没有听出作者介绍的这个 IDE 叫什么，后来多方搜索，终于找到了这个 IDE 的名字 TextMate，很完美，但是只适用于 Mac 平台的，并没有 Windows 版的。</p><p>不过，所幸的是，我查到了另外一个 IDE，也就是今天的主角：Sublime Text 2，感觉比 TextMate 还要强大，而且是跨平台的。其界面之优美、功能之庞大让我爱不释手，更重要的是，它的速度特别快，就像 Notepad++一样，这也是我不喜欢 Eclipse 的原因，太臃肿。</p><p>下面我要引用一些网上的内容来介绍一下 Sublime Text 2：</p><p><strong>语法高亮、代码提示补全、代码折叠、自定义皮肤/配色方案、多便签页：</strong></p><p>SublimeText2 支持但不限于 C, C++, C#, CSS, D, Erlang, HTML, Groovy, Haskell, HTML, Java, JavaScript, LaTeX, Lisp, Lua, Markdown, Matlab, OCaml, Perl, PHP, Python, R, Ruby, SQL, TCL, Textile and XML 等主流编程语言的语法高亮。ST2 拥有优秀的代码自动完成功能（自动补齐括号，大括号等配对符号；自动补全已经出现的单词；自动补全函数名），非常智能；另外 ST2 也拥有代码片段（Snippet）的功能，可以将常用的代码片段保存起来，在需要时随时调用。当然，语法高亮、代码折叠、行号显示、自定义皮肤、配色方案等这些已经是一款现代编辑器应有的标配功能了，所以这里就不多做介绍了。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/01.png" alt="实用方便的代码提示补全功能"></img><figcaption>实用方便的代码提示补全功能</figcaption></figure><p><strong>代码地图、多种界面布局与全屏免打扰模式：</strong></p><p>Sublime Text 2 在界面上比较有特色的是支持多种布局和代码地图，也提供了 F11 和 Shift+F11 进入全屏免打扰模式，如下面几幅图：</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/02.png" alt="代码地图与多标签页"></img><figcaption>代码地图与多标签页</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/03.png" alt="多种布局设置，在大屏幕或需同时编辑多文件时尤为方便"></img><figcaption>多种布局设置，在大屏幕或需同时编辑多文件时尤为方便</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/04.png" alt="全屏免打扰模式，更加专心于编辑"></img><figcaption>全屏免打扰模式，更加专心于编辑</figcaption></figure><p><strong>完全开放的用户自定义配置与神奇实用的编辑状态恢复功能：</strong></p><p>Sublime Text 2 的各种配置均由配置文件控制，完完全全的可以由用户自定义，如果你愿意折腾，甚至可以将它改得于原版完全不一样的操作体验。看下面的图，Setting – Default 菜单会打开默认的软件配置文件（这个文件会记录一些诸如使用什么字体等很多很多配置信息），Key Bindings – Default 是默认的快捷键配置文件，大家可以打开它们看看原本的一些设置是怎样配置的，但非常不建议直接在这里修改！你可以在 – User 结尾的文件（也就是用户自定义配置的意思）里面照样画葫芦那样来改，如果两边有相同的项目，它会以 – User 文件里面定义的为准。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/05.png" alt="Key Bindings"></img><figcaption>Key Bindings</figcaption></figure><p>这个例子里我在 Key Bindings – User 里面的第一行：<code>{ "keys": ["alt+up"], "command": "swap_line_up" }</code>, 意思就是按 Alt+方向键上时将当前选择的那一行向上移（与上一行交换位置），如果语法神马的没问题，保存好这个文件之后马上就可以使用这个快捷键了。</p><p>Sublime Text 2 的快捷键还支持双重组合，譬如默认情况下，将选中的文字改成大写的热键是“Ctrl+K, Ctrl+U”，意思是当你先按下 Ctrl+K 之后迅速再按 Ctrl+U 进行触发（只按下 Ctrl+K 是没有作用的），这样可以避免很多热键冲突，也可以更灵活更多选择地进行热键自定义。不过在这里就不打算教大家怎样修改各种配置或修改热键了，这恐怕能出一个手册的，这里有一个套比较完整的官方文档(鸟语)，有兴趣的朋友可以去参考一下。</p><p>另外，SublimeText  还有一个值得一提的细节——”编辑状态恢复”，就是当你修改了一个文件，但没有保存，这时退出软件，ST2 是不会很烦人地提示你要不要保存的，因为无论是用户自发退出还是意外崩溃退出，下次启动软件后，你之前的编辑状态都会被完整恢复，就像你退出前一样。这个细节我认为非常非常的赞！因为我经常会尝试性地去修改一些地方，但在确保没有问题之前又不想保存，这时如果有些事情打断自己，需要离开时，这个特性就很有用了，直接退出就行，不用烦，下次回来打开软件继续编辑即可。当然，有了这个恢复特性再也不怕系统崩溃、断电了吧，真心让人觉得 ST2 特别特别的安全可靠，妥妥的啊！</p><p><strong>强大的多行选择和多行编辑：</strong></p><p>在写代码的过程中，我们经常需要同时编辑多行代码或者多个变量。在 Sublime Text 2 中拥有非常实用的多行操作技巧，灵活运用可以大大提高编辑速度哟！相信日后你可能会这样问自己：“当年没有这种方式的编辑器时我究竟是怎么活过来的？！”</p><p>下面是一些我所了解的多行编辑方法：</p><ul><li>鼠标选中多行，按下 Ctrl+Shift+L (Command+Shift+L) 即可同时编辑这些行；</li><li>鼠标选中文本，反复按 CTRL+D (Command+D) 即可继续向下同时选中下一个相同的文本进行同时编辑；</li><li>鼠标选中文本，按下 Alt+F3 (Win) 或 Ctrl+Command+G(Mac) 即可一次性选择全部的相同文本进行同时编辑；</li><li>Shift+鼠标右键 (Win) 或 Option+鼠标左键 (Mac) 或使用鼠标中键可以用鼠标进行竖向多行选择；</li><li>Ctrl+鼠标左键(Win) 或 Command+鼠标左键(Mac) 可以手动选择同时要编辑的多处文本</li><li>类似的技巧还有很多，求大家补充……</li></ul><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/06.png" alt="Shift+鼠标右键轻松实现多行编辑，批量给变量加前缀了"></img><figcaption>Shift+鼠标右键轻松实现多行编辑，批量给变量加前缀了</figcaption></figure><p><strong>雷电般快速的文件切换：</strong></p><p>如果你同时打开了多个文件，或者你的项目里经常需要编辑不同的文件，在文件数量较多的时候，在过去往往需要花费很多的精力去寻找，很是烦人。而现在，Sublime Text 2 里只需按下 Ctrl+P(Win) 或 Shift+Command+P(Mac) 即可调出文件切换面板，接着你只需输入文件名，回车后即可瞬间切换过去！并且它支持模糊匹配，只需输入你记得的一部分即可，譬如我想要找一个“<a href="http://www.iplaysoft.com.php%E2%80%9D%E7%9A%84%E6%96%87%E4%BB%B6%E6%9D%A5%E7%BC%96%E8%BE%91%EF%BC%8C%E9%82%A3%E4%B9%88%E4%BD%A0%E5%8F%AA%E9%9C%80%E8%A6%81%E8%BE%93%E5%85%A5%E2%80%9Cipl%E2%80%9D%E6%88%96%E8%80%85%E6%98%AF%E2%80%9Dips.c%E2%80%9D%E8%BF%99%E6%A0%B7%E7%9A%84%E5%AD%97%E7%AC%A6%E9%83%BD%E8%83%BD%E5%8C%B9%E9%85%8D%E5%87%BA%E6%9D%A5%EF%BC%8C%E8%BF%99%E4%B8%AA%E7%89%B9%E6%80%A7%E9%9D%9E%E5%B8%B8%E9%9D%9E%E5%B8%B8%E7%9A%84%E6%A3%92%EF%BC%81" rel="nofollow">www.iplaysoft.com.php”的文件来编辑，那么你只需要输入“ipl”或者是”ips.c”这样的字符都能匹配出来，这个特性非常非常的棒！</a></p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/07.png" alt="图中 ca 匹配了 capabilities.php 和 classese.php，选择就能快速切换"></img><figcaption>图中 ca 匹配了 capabilities.php 和 classese.php，选择就能快速切换</figcaption></figure><p>类似的功能，我只在类似 Eclipse 等大型(笨重)的 IDE 中才见到过，然而小巧快速的编辑器中，我还是首次遇到。使用这个功能，你除了可以在已打开的文件中切换之外，如果你使用项目管理（将一个文件夹设置成一个项目），它还能懂得去搜索匹配项目文件夹下未被打开过的文件。现在你还需要用鼠标去一个一个点标签页来切换吗？你还要打开“我的电脑”慢慢在各个不同文件夹去找需要编辑的文件吗？使用 ST2，你只需输入几个字符即可～只有一句话：前所未有的方便！</p><p><strong>随心所欲的跳转：快速罗列与定位函数/HTML 的元素、跳转到指定行：</strong></p><p>使用上面介绍的快速文件切换功能，可以很轻易地打开/切换到自己想要编辑的文档了，但如果这个文件的代码很长很长，想要轻松跳到要编辑的地方又有什么好方法呢？ Sublime Text 2 早就帮你想好了，同样是按下前面所说的 Ctrl+P(Win) 或 Shift+Command+P(Mac)，这次试试先输入一个 @ 号看看？嗯，好样的！这列表马上帮你罗列出这文件里全部的 Function 了！同样使用模糊匹配，快速输入几个关键字，马上就能定位到那个 Function 去了！！！在需要不停在多个 Function 之间跳转的时候这个功能尤显实用～妈妈再也不用担心我找函数找到蛋疼了！当你编辑的是 HTML 时，这货给你罗列的则是 HTML 的各个 ID 元素，相信搞前端的同学们都鸡冻了吧。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/08.png" alt="Ctrl+P之后输入@号或者 直接按Ctrl+R，即可列出该文件里的全部function"></img><figcaption>Ctrl+P之后输入@号或者 直接按Ctrl+R，即可列出该文件里的全部function</figcaption></figure><p>输入@号开始有此般神奇功效，那么再试试输入一个英文冒号 : 开始吧，然后再输入一个数字，嗯，这次则可以跳到指定的行数了；输入一个#号开始，可以罗列/搜索文本；而且你还可以使用更快速的快捷键，譬如快速列出/跳转函数就是 Ctrl+R (Mac 下是 Command+R)，它完全等同于 Ctrl+P 之后输入@；跳转到指定行号是 Ctrl+G (Mac 是 Command+G)。</p><p>而且更让人叫绝的是，这些切换定位方法你还可以配合在一起使用！譬如我有一个名为”hello-iplaysoft.js”的文件，里面其中有一个 function 叫做”visit_iplaysoft_com”，我现在想要编辑这个函数，那么我只需按下 Ctrl+P，然后输入“heip@vi”回车（模糊匹配，注意前面有颜色的字符），ST2 马上就给我到打开这个文件并定位进去了！够方便了吧？！熟记这几个快捷键，你可以很一气呵成地进行文件切换和编辑，你会发现世界更美好哦亲……</p><p><strong>集所有功能于一身的命令面板：</strong></p><p>Sublime Text 2 的一大特色是拥有一个相当强大的命令面板，它几乎无所不能！任何时候，按下 Ctrl+Shift+P(Win) 或 Command+Shift+P(Mac) 即可调出。利用它，你可以实现很多很多很多很多很多功能，例如“Set Syntax:PHP”即可将当前文档设置成 PHP 语法高亮；“Convert Case: Swap Case”可以将选中的文本大小写反转；“File: Save All”可以一次保存全部文件；“File: Close All”一次关闭全部文件等等……而且，这里的列表一样支持模糊匹配（这货真心是个好东西啊！）。因为这里面命令实在太多了，覆盖的作用范围也很广，我这里实在不能一一介绍，大家如果有兴趣，可以经常调个面板出来看看列表中都有些什么命令，多多去了解、尝试、再慢慢消化，相信它会让你再也离不开它。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/09.png" alt="强大的命令面板，可以在这里调用一切SublimeText提供的功能"></img><figcaption>强大的命令面板，可以在这里调用一切SublimeText提供的功能</figcaption></figure><p><strong>Package Control（绝不可错过的扩展包管理器）：</strong></p><p>Sublime Text 2 除了自身拥有无数实用功能和特性之外，它还能安装使用各种扩展/皮肤/配色方案等来增强自己。现在介绍的这个 Package Control 可以看做是一个 ST2 的扩展管理器，使用它，你可以用非常神奇、非常简单方便的方法去下载、安装、删除 Sublime Text 2 的各种插件、皮肤等，相信我，想更好地使用 ST2 绝对不能没有它！不过 ST2 本身并没有自带这个工具，我们需要自行安装它，方法很简单：</p><ol><li>在 SublimeText2 的目录里面找到 Data > Installed Packages 的文件夹 (如没有请手动新建)</li><li>在这里下载 Package Control.sublime-package 文件</li><li>将下载到的文件放进去 Installed Packages 里面</li><li>重新启动 Sublime Text 即可</li></ol><p>如果 Package Control 已经安装成功，那么 Ctrl+Shift+P 调用命令面板，我们就会找到一些以“Package Control:”开头的命令，我们常用到的就是几个 Install Package (安装扩展)、List Packages (列出全部扩展)、Remove Package (移除扩展)、Upgrade Package (升级扩展)。</p><p>但如果你按照上面的方法确实搞不定，可以试试按键盘 Ctrl+~ （数字 1 左边的按键）调出控制台，然后拷贝下面的代码进去并回车，它会自动帮你新建文件夹并下载文件的，与上面的方法最终效果是一样的：</p><pre><code><span class="line" line="1"><span class="sVHd0">import</span><span class="su5hD"> urllib2</span><span class="sP7_E">,</span><span class="su5hD">os;
</span></span><span class="line" line="2"><span class="su5hD">pf</span><span class="smGrS">=</span><span class="sjJ54">'</span><span class="s_sjI">Package Control.sublime-package</span><span class="sjJ54">'</span><span class="srjyR">;
</span></span><span class="line" line="3"><span class="su5hD">ipp</span><span class="smGrS">=</span><span class="su5hD">sublime</span><span class="sP7_E">.</span><span class="slqww">installed_packages_path</span><span class="sP7_E">()</span><span class="srjyR">;
</span></span><span class="line" line="4"><span class="su5hD">os</span><span class="sP7_E">.</span><span class="slqww">makedirs</span><span class="sP7_E">(</span><span class="slqww">ipp</span><span class="sP7_E">)</span><span class="sVHd0"> if</span><span class="smGrS"> not</span><span class="su5hD"> os</span><span class="sP7_E">.</span><span class="skxfh">path</span><span class="sP7_E">.</span><span class="slqww">exists</span><span class="sP7_E">(</span><span class="slqww">ipp</span><span class="sP7_E">)</span><span class="sVHd0"> else</span><span class="s39Yj"> None</span><span class="srjyR">;
</span></span><span class="line" line="5"><span class="su5hD">urllib2</span><span class="sP7_E">.</span><span class="slqww">install_opener</span><span class="sP7_E">(</span><span class="slqww">urllib2</span><span class="sP7_E">.</span><span class="slqww">build_opener</span><span class="sP7_E">(</span><span class="slqww">urllib2</span><span class="sP7_E">.</span><span class="slqww">ProxyHandler</span><span class="sP7_E">()))</span><span class="srjyR">;
</span></span><span class="line" line="6"><span class="sptTA">open</span><span class="sP7_E">(</span><span class="slqww">os</span><span class="sP7_E">.</span><span class="skxfh">path</span><span class="sP7_E">.</span><span class="slqww">join</span><span class="sP7_E">(</span><span class="slqww">ipp</span><span class="sP7_E">,</span><span class="slqww">pf</span><span class="sP7_E">),</span><span class="sjJ54">'</span><span class="s_sjI">wb</span><span class="sjJ54">'</span><span class="sP7_E">).</span><span class="slqww">write</span><span class="sP7_E">(</span><span class="slqww">urllib2</span><span class="sP7_E">.</span><span class="slqww">urlopen</span><span class="sP7_E">(</span><span class="sjJ54">'</span><span class="s_sjI">http://sublime.wbond.net/</span><span class="sjJ54">'</span><span class="smGrS">+</span><span class="slqww">pf</span><span class="sP7_E">.</span><span class="slqww">replace</span><span class="sP7_E">(</span><span class="sjJ54">'</span><span class="sjJ54"> '</span><span class="sP7_E">,</span><span class="sjJ54">'</span><span class="s_sjI">%20</span><span class="sjJ54">'</span><span class="sP7_E">)).</span><span class="slqww">read</span><span class="sP7_E">())</span><span class="su5hD">; </span><span class="sptTA">print</span><span class="sjJ54"> '</span><span class="s_sjI">Please restart Sublime Text to finish installation</span><span class="sjJ54">'
</span></span></code></pre><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/10.png" alt="成功安装 Package Control 之后，在命令面板里会出现以其命名开头的一系列命令"></img><figcaption>成功安装 Package Control 之后，在命令面板里会出现以其命名开头的一系列命令</figcaption></figure><p>在命令面板输入 “Package Control: Install Package“即会列出全部可以安装的扩展（必需连接网络，如下图），从列表可以看到，4GL、AAAPackageDev 那些就是插件的名称，选择它们就可以进行下载安装了。从该列表可以看到，目前 ST2 的各种扩展已经非常丰富了！此外，你还可以在<a href="http://wbond.net/sublime_packages/community" rel="nofollow">这里</a>看到 Web 版的扩展列表和详细的说明 （这俩列表的数据应该是同步的。在截稿为止 2012-7-8，这里已经收集了 482 个扩展包了）</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/11.png" alt="plugins"></img><figcaption>plugins</figcaption></figure><p>我这里以安装“JsFormat”插件为例，简单介绍一下 SublimeText 里面怎样安装与使用插件吧。JsFormat 的功能就是可以将一些凌乱的 JavaScript 代码重新排版，以方便更好地阅读与编辑。使用 Ctrl+Shift+P 调用命令面板，输入“Package Control: Install Package”(安装扩展包)，在插件列表中选择安装“JsFormat”(可以输入字符过滤)，待提示成功之后即已完成安装。随便打开一个 js 文件（最好是换行、对齐特别凌乱的那种），按下 Ctrl+Shift+P 调用命令面板，你会发现已经多了一项命令叫做“Format: Javascript”，如图：</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/12.png" alt="使用 JSFormat 插件的 Format: Javascript 命令"></img><figcaption>使用 JSFormat 插件的 Format: Javascript 命令</figcaption></figure><p>使用之后，你的代码瞬间就变整齐了有木有！你也可以使用这个插件的热键“CTRL+ALT+F”进行整理（命令面板右方可以看到）。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/13.png" alt="使用 JSFormat 之后的 JS 代码效果，注意上图的JS代码是一堆的"></img><figcaption>使用 JSFormat 之后的 JS 代码效果，注意上图的JS代码是一堆的</figcaption></figure><p>当然，不同的扩展，使用的方法与表现的形式都不一样，这个就只能去 web 版查一下这个插件的一些具体的使用说明了，这里是不能一概而论的。不过大体上，安装和使用插件就是这么的简单。通过各种插件，你几乎可以实现任何你想要的功能。而且 ST2 也开放了插件 API，如果你有能力，也可以试试开发一个，可以参考<a href="http://www.sublimetext.com/docs/2/api_reference.html" rel="nofollow">这里</a>的 API 文档。本文后面会推荐一些实用的插件。</p><p><strong>更换主题或配色方案：</strong></p><p>如果你看腻了 SublimeText 的原版皮肤，也可以折腾一下换肤的。譬如下图是一款比较流行的主题 Theme – Soda，和安装插件基本上一样，使用 Package Control 进入 Install Package 的列表里面找到它进行安装即可。你也可以在网上找到一些 ST2 的主题，下载回来放到安装目录的 Data\Packages 文件夹里面，然后选择切换主题。配色方案的操作也是类似，大家自己研究研究吧，这里不多做介绍了。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/14.png" alt="theme-soda-light"></img><figcaption>theme-soda-light</figcaption></figure><p>另外，SublimeText 还有很给力的一点，就是它能原生支持 TextMate 的 Bundle 和配色方案，同样也是放在 Packages 文件夹里即可使用。TextMate 的 Bundle 和配色方案资源都比较丰富，网上可以找到不少。</p><p><strong>下面来介绍几款有用的 Sublime Text 2 的插件：</strong></p><p><a href="https://bitbucket.org/sublimator/sublime-2-zencoding" rel="nofollow">Zen Coding</a></p><p>这个，不解释了，还不知道 ZenCoding 的同学强烈推荐去看一下：《<a href="http://www.qianduan.net/zen-coding-a-new-way-to-write-html-code.html" rel="nofollow">Zen Coding: 一种快速编写 HTML/CSS 代码的方法</a>》。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/15.png" alt="zen"></img><figcaption>zen</figcaption></figure><p>PS:Zen Coding  for Sublime  Text 2 插件的开发者已经停止了在 Github 上共享了，现在只有通过 Package Control 来安装。</p><p><a href="https://github.com/mrmartineau/Jquery" rel="nofollow">jQuery Package for sublime Text</a></p><p>如果你离不开 jQuery 的话，这个必备～～</p><p><a href="https://github.com/wbond/sublime_prefixr" rel="nofollow">Sublime Prefixr</a></p><p>Prefixr，CSS3 私有前缀自动补全插件，显然也很有用哇</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/16.png" alt="Prefixr"></img><figcaption>Prefixr</figcaption></figure><p><a href="https://github.com/jdc0589/JsFormat" rel="nofollow">JS Format</a></p><p>一个 JS 代码格式化插件。</p><p><a href="https://github.com/kronuz/SublimeLinter/" rel="nofollow">SublimeLinter</a></p><p>一个支持 lint 语法的插件，可以高亮 linter 认为有错误的代码行，也支持高亮一些特别的注释，比如“TODO”，这样就可以被快速定位。（IntelliJ IDEA 的 TODO 功能很赞，这个插件虽然比不上，但是也够用了吧）</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/17.png" alt="Linter"></img><figcaption>Linter</figcaption></figure><p><a href="https://github.com/mrmartineau/Placeholders" rel="nofollow">Placeholders</a></p><p>故名思意，占位用，包括一些占位文字和 HTML 代码片段，实用。</p><p><a href="https://github.com/wbond/sublime_alignment" rel="nofollow">Sublime Alignment</a></p><p>用于代码格式的自动对齐。传说最新版 Sublime 已经集成。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/18.png" alt="Alignment"></img><figcaption>Alignment</figcaption></figure><p><a href="https://github.com/kemayo/sublime-text-2-clipboard-history" rel="nofollow">Clipboard History</a></p><p>粘贴板历史记录，方便使用复制/剪切的内容。</p><p><a href="https://github.com/phillipkoebbe/DetectSyntax" rel="nofollow">DetectSyntax</a></p><p>这是一个代码检测插件。</p><p><a href="https://github.com/weslly/Nettuts-Fetch" rel="nofollow">Nettuts Fetch</a></p><p>如果你在用一些公用的或者开源的框架，比如 Normalize.css 或者 modernizr.js，但是，过了一段时间后，可能该开源库已经更新了，而你没有发现，这个时候可能已经不太适合你的项目了，那么你就要重新折腾一遍或者继续用陈旧的文件。Nettuts Fetch 可以让你设置一些需要同步的文件列表，然后保存更新。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/19.jpg"></img></figure><p><a href="https://github.com/cgutierrez/JsMinifier" rel="nofollow">JsMinifier</a></p><p>该插件基于 Google Closure compiler，自动压缩 js 文件。</p><p><a href="https://github.com/Kronuz/SublimeCodeIntel" rel="nofollow">Sublime CodeIntel</a></p><p>代码自动提示</p><p><a href="https://github.com/facelessuser/BracketHighlighter" rel="nofollow">Bracket Highlighter</a></p><p>类似于代码匹配，可以匹配括号，引号等符号内的范围。</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/20.png"></img></figure><p><a href="https://github.com/atadams/Hex-to-HSL-Color" rel="nofollow">Hex to HSL</a></p><p>自动转换颜色值，从 16 进制到 HSL 格式，快捷键 Ctrl+Shift+U</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/21.png"></img></figure><p><a href="http://www.sublimetext.com/forum/viewtopic.php?f=5&p=22274" rel="nofollow">GBK to UTF8</a></p><p>将文件编码从 GBK 转黄成 UTF8，快捷键 Ctrl+Shift+C</p><p><a href="https://github.com/kemayo/sublime-text-2-git" rel="nofollow">Git</a></p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/22.png"></img></figure><p>该插件基本上实现了 git 的所有功能。</p><p><a href="http://wbond.net/sublime_packages/sftp" rel="nofollow">SFTP</a></p><p>超级强大的 FTP 插件。安装方法：</p><p>按下 Ctrl+Shift+P 调出命令面板，输入 install 调出 Install Package 选项并回车，然后输入 ftp，下拉列表中会出现一些相关的插件，选中 sftp 进行安装就行了，装好后还需进行配置，选择菜单栏中的 File->SFTP/FTP->Set up Server，然后会出现一个配置窗口，将相关内容修改成你的配置，最后保存成任意文件名即可，下面给出我的一个 ftp 配置文件：</p><figure><img src="https://hadb.me/static/posts/2013/20130217.sublime-text-2-supergood-code-editor/23.png"></img></figure><p>另外，在左侧栏本地项目上右击，有个 SFTP/FTP，选择 Add Remote Mapping，然后会生成一个 sftp-config.json 的文件，之后你就可以通过项目的 SFTP/FTP 菜单里的 Sync Local -> Remote 来进行文件的同步了，很是方便！</p><p><strong>附上一段 Sublime Text 2 的介绍视频（作者：大城小胖）：</strong></p><p><a href="http://v.youku.com/v_show/id_XMzU5NzQ5ODgw.html" rel="nofollow">http://v.youku.com/v_show/id_XMzU5NzQ5ODgw.html</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[明早回学校，结束寒假生活]]></title>
        <id>/posts/2013/tomorrow-go-back-to-school-to-finish-my-winter-life</id>
        <link href="https://hadb.me/posts/2013/tomorrow-go-back-to-school-to-finish-my-winter-life"/>
        <updated>2013-02-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天把 Blog 的主题重新换了一个，使用了星际 2 那个站点的主题，另外和网上几个不错的刚起步的博客站点交换了友链，开始 SEO 优化之路。]]></summary>
        <content type="html"><![CDATA[<p>今天把 Blog 的主题重新换了一个，使用了星际 2 那个站点的主题，另外和网上几个不错的刚起步的博客站点交换了友链，开始 SEO 优化之路。</p><p>看了不少优秀的博客，发现牛人真是好多呀！一定要虚心学习，取长补短。</p><p>明早 6 点要起床，7 点半的汽车，中午到上海。要早点睡，不多废话了。</p><p>到学校之后好好写个寒假总结。以后要坚持天天写博客，像他们学习！嗯！</p><p>另外，梅志伟，等你新主机弄好了，一定要找我交换友链哦！</p><p>晚安！:-)</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[虚拟主机如何禁止目录访问]]></title>
        <id>/posts/2013/forbid-index-of-your-host</id>
        <link href="https://hadb.me/posts/2013/forbid-index-of-your-host"/>
        <updated>2013-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前http://www.haoest.com/上有一个 bug，最早的时候，我有个 Wordpress 的 Page，固定链接是http://www.haoest.com/products/，然后有一些 Page 是在 products 下的，如http://www.haoest.com/products/capture-show/。原本是没有什么问题的。但是后来，为了减少子域名数量，我不想建立太多诸如http://listmanager.haoest.com/这样的网站，而改为http://www.haoest.com/products/listmanager/这样的形式，把这些独立的站点都放到 products 文件夹里。只给一些重要的产品设置单独的子域名，因为我这个虚拟主机的子域名数量是有限的。但自从建立了 products 文件夹之后，问题出现了。]]></summary>
        <content type="html"><![CDATA[<p>之前<a href="http://www.haoest.com/" rel="nofollow">http://www.haoest.com/</a>上有一个 bug，最早的时候，我有个 Wordpress 的 Page，固定链接是<a href="http://www.haoest.com/products/" rel="nofollow">http://www.haoest.com/products/</a>，然后有一些 Page 是在 products 下的，如<a href="http://www.haoest.com/products/capture-show/" rel="nofollow">http://www.haoest.com/products/capture-show/</a>。原本是没有什么问题的。但是后来，为了减少子域名数量，我不想建立太多诸如<a href="http://listmanager.haoest.com/" rel="nofollow">http://listmanager.haoest.com/</a>这样的网站，而改为<a href="http://www.haoest.com/products/listmanager/" rel="nofollow">http://www.haoest.com/products/listmanager/</a>这样的形式，把这些独立的站点都放到 products 文件夹里。只给一些重要的产品设置单独的子域名，因为我这个虚拟主机的子域名数量是有限的。但自从建立了 products 文件夹之后，问题出现了。</p><p>打开<a href="http://www.haoest.com/products/" rel="nofollow">http://www.haoest.com/products/</a>之后并不是显示原来的 products 的那个 page，而是显示一个 Index of 的页面，把我 products 文件夹的列表显示出来了，很不友好，也很不安全。发现这个问题后，我立马想到应该可以通过.htaccess 文件来解决，但后来不知道因为什么事情而耽搁了，就搞忘记了。后来，@梅崇华 提醒我这个问题，我才再次想起来这个事情。前些日子，通过一句简单的代码搞定了这个问题，今天把它记录下来，以便大家学习。</p><p>如果是自己的服务器的话，可以修改 httpd.conf 配置，把</p><pre><code><Directory "D:/xx/xx/xx">
    Options Indexes FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>
</code></pre><p>中的<code>Options Indexes FollowSymLinks</code>改为<code>Options -Indexes FollowSymLinks</code>就可以了，也就是在<code>Indexes</code>前面加了个减号，减号表示关闭，加号表示开启，不加符号表示默认。</p><p>但是，在虚拟主机中没办法修改 httpd.conf 文件，我们可以修改.htaccess 文件，大多数虚拟主机都是可以使用.htaccess 的。 在 <a href="http://www.haoest.com" rel="nofollow">www.haoest.com</a> 的目录下添加.hatccess 文件，如果没有的话，然后添加一句</p><p><code>Options -Indexes</code></p><p>就可以了。 至于网上还有的教程是添加</p><pre><code><Files *>
    Options -Indexes
</Files>
</code></pre><p>这个 Files 是什么意思，目前还不是很清楚，不过也是可以的。</p><p>给大家推荐一个 Wordpress 的 SEO 插件，叫 Yoast WordPress SEO，超级强大，其设置里有个“编辑文件”，可以直接编辑 robots.txt 文件和.htaccess 文件，很方便。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[反思：不要偷懒，要自力更生]]></title>
        <id>/posts/2013/self-reflection-about-lazy-and-friendship</id>
        <link href="https://hadb.me/posts/2013/self-reflection-about-lazy-and-friendship"/>
        <updated>2013-03-06T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure><img src="https://hadb.me/static/posts/2013/20130306.self-reflection-about-lazy-and-friendship/cover.png"></img></figure><p>心情很糟糕，今天的事，真的很不应该，自我检讨，希望以后吸取教训，也希望能够给大家做一个反面教材。</p><p>事情是这样的。今天要验收数据库的实验大作业，我和小天前些天都偷懒，没怎么做，后来发现飞哥花大心血整整一天终于做完了，于是我俩偷懒就问飞哥搞过来了，飞哥千叮咛万嘱咐，你们要多改改啊，我们很爽快地答应了。</p><p>大作业是用 PowerBuilder 做一个数据库连接的小应用，关于 PowerBuilder，不得不说是很土鳖的一个软件，无非就是对于数据处理方便了些，仅适合教学，对于开发来说没啥前途，所以我们也就混混作业应付了事，才偷懒搞了飞哥的程序。修改倒的确是修改了，图片、界面、按钮、字体等等。</p><p>但是，验收时，由于我和小天在前面验收，飞哥紧接着我们验收的，老师验收到飞哥的时候，说，你这个跟前面两个有什么不同？巴拉巴拉~加上飞哥第二个实验去年验收的时候老师似乎没有打分，然后因为这次的验收，老师看飞哥不爽，第二个实验说已经过期了也不给验了，可飞哥去年的确是验了的……唉，总之就是很伤感。</p><p>飞哥心里很不爽，我和小天也非常内疚。而我在不恰当时候这样安慰飞哥，“平时分低点没关系，这可以激励你好好复习，好好考试”，我的嬉皮笑脸让飞哥很不爽，我自己都觉得这话说得太不对，安慰自己还可以，安慰别人这么说就像风凉话了。</p><p>总之心里很复杂很内疚。</p><p>飞哥一直留在那里想再争取下，我们实在尴尬就先回来了。后来飞哥回来，我问他后来怎么样了，飞哥把宿舍门一摔，也不理我。</p><p>以后可怎么面对飞哥。</p><p>以后还是不要偷懒了，还是要自力更生。尤其拿别人的作业来改，发生这样的事，真的很伤感，别人自己辛辛苦苦做出来的东西，被老师说成抄袭的，太委屈太憋屈了。</p><p>这次是小事，有句古话，“小洞不补，大洞吃苦”，还是要吸取教训，不要等以后酿成大错才反思。方便了自己，却伤害了朋友，心里真不好受。</p><p>飞哥，Sorry！考完试请你吃饭。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Win8 应用开发学习记录 —— Logo 设计]]></title>
        <id>/posts/2013/win8-app-develop-study-design-logo</id>
        <link href="https://hadb.me/posts/2013/win8-app-develop-study-design-logo"/>
        <updated>2013-04-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Surface Pro 到手也有一段时间了，也把玩了这么些天，今天终于开始来写 Win8 的应用了。]]></summary>
        <content type="html"><![CDATA[<p>Surface Pro 到手也有一段时间了，也把玩了这么些天，今天终于开始来写 Win8 的应用了。</p><p>环境搭建什么的就不详述了，Windows 8 Pro+Visual Studio 2012。</p><p>我想做的是 Win8 上的二维码应用，应用商店里有不少，但良莠不齐，大多都是英文的，中文的寥寥无几，而且质量都比较差。所以我准备做一款优秀的 QR Code For Win8 的应用。</p><p>一个好的应用，很重要的一点是用户体验，而用户体验的第一点就是 Logo，尤其在 Win8 界面下，Logo 是尤为重要的，下面是几个应用商店中我认为很不合格的 Logo：</p><p>（以下是在应用商店里搜索 Qr Code 出现的第一列应用）</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/01.png" alt="Win8应用商店里搜索Qr Code出现的第一列结果"></img><figcaption>Win8应用商店里搜索Qr Code出现的第一列结果</figcaption></figure><p>除了“Magnet QR Code Generator”勉强符合 Win8 风格外，其余均是不合格的。不合格原因是五颜六色或者过于杂乱。</p><p>说到二维码，第一感觉自然是以二维码本身作为 Logo 最为直观，我起初也是这样的打算，下面是我的第一个想法：</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/02.png"></img></figure><p>想法是，QR Code For Win8，于是中间一个 Win8 的标志，外面是二维码，二维码本身采用的是“<a href="http://www.haoest.com%E2%80%9D%E7%9A%84%E9%93%BE%E6%8E%A5%E3%80%82" rel="nofollow">http://www.haoest.com”的链接。</a></p><p>然而，它在 Win8 开始菜单中显示效果格格不入。</p><p>下面是 Win8 自带的几个应用的 Logo：</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/03.png" alt="Win8自带的几个应用的Logo"></img><figcaption>Win8自带的几个应用的Logo</figcaption></figure><p>其特点是简洁、清爽，以白色前景色为图案，彩色为背景色。</p><p>于是，经过一段时间的构思和设计，我后来重新设计了 Logo 如下：</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/04.png" alt="新设计的二维码Logo"></img><figcaption>新设计的二维码Logo</figcaption></figure><p>外面一个 Q，里面是个 Win8 的标志。</p><p>Logo 原型设计好了，下面开始为 Visual Studio 的需要进行适配了。</p><p>在 Visual Studio 中，你需要为 Win8 应用准备多种尺寸的 Logo：</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/05.png" alt="徽标——最基本的Logo，开始屏幕的方格Logo"></img><figcaption>徽标——最基本的Logo，开始屏幕的方格Logo</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/06.png" alt="宽徽标——在开始屏幕中放大后占两个的Logo"></img><figcaption>宽徽标——在开始屏幕中放大后占两个的Logo</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/07.png" alt="小徽标"></img><figcaption>小徽标</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/08.png" alt="应用商店徽标"></img><figcaption>应用商店徽标</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/09.png" alt="徽章徽标——用于锁屏应用在锁屏时显示，不是必要的"></img><figcaption>徽章徽标——用于锁屏应用在锁屏时显示，不是必要的</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/10.png" alt="初始屏幕徽标——打开应用时出现的Logo"></img><figcaption>初始屏幕徽标——打开应用时出现的Logo</figcaption></figure><p>需要注意的是，你所生成的这些不同尺寸的图片只需要前景色的白色就行了，无需背景色，因为背景色可以在前面进行统一设置，这样的好处是，你可以随时把背景色换成你喜欢的颜色。</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/11.png" alt="可方便更改背景色"></img><figcaption>可方便更改背景色</figcaption></figure><p>在这点上，我吃了大亏，之前的背景色是蓝色的，我也没有注意到这点，导致生成的所有图片都带有蓝色背景。再后来我想改成深灰色的时候，费了好长时间，最后发现了这个问题，又花了同样的时间，把深灰色背景色去掉，换成透明背景色。</p><p>上图中的“短名称”是显示在开始屏幕的 Logo 左下角的名称，前景文本指的就是这个名称的颜色，有深浅两种选择，分别为黑白两色。图块中的背景色指的就是上面所有的 Logo 徽标的背景色。而初始屏幕的背景色可以单独设置，如果为空，则使用图块的背景色。这样一来，你就可以方便的更改背景色了。</p><p>下面分别是我的 Logo 的两种显示效果：</p><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/12.png" alt="QR Code Logo 显示效果1"></img><figcaption>QR Code Logo 显示效果1</figcaption></figure><figure><img src="https://hadb.me/static/posts/2013/20130419.win8-app-develop-study-design-logo/13.png" alt="QR Code Logo 显示效果2"></img><figcaption>QR Code Logo 显示效果2</figcaption></figure><p>怎么样，是不是与 Win8 风格很一致呢？</p><p>好的 Logo 是一个应用的开始，嗯，今天的工作就到这里，我也该睡觉了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[为 WordPress 主题添加阅读统计的功能]]></title>
        <id>/posts/2013/post-views-for-wordpress-themes</id>
        <link href="https://hadb.me/posts/2013/post-views-for-wordpress-themes"/>
        <updated>2013-05-10T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure><img src="https://hadb.me/static/posts/2013/20130510.post-views-for-wordpress-themes/cover.png"></img></figure><p>今天为星际 2 战术资源站和本博客的主题添加了一个阅读统计的功能，效果如下图：</p><figure><img src="https://hadb.me/static/posts/2013/20130510.post-views-for-wordpress-themes/01.png" alt="阅读统计效果"></img><figcaption>阅读统计效果</figcaption></figure><p>我是采用子主题的方式，在子主题中添加 functions.php 文件，在里面加入下面两个函数：</p><pre><code><span class="line" line="1"><span class="smGrS"><?</span><span class="s_hVV">php
</span></span><span class="line" line="2"><span class="sutJx">/* 访问计数 */
</span></span><span class="line" line="3"><span class="sbsja">function</span><span class="sGLFI"> record_visitors</span><span class="sP7_E">()
</span></span><span class="line" line="4"><span class="sP7_E">{
</span></span><span class="line" line="5"><span class="sVHd0">    if</span><span class="sP7_E"> (</span><span class="sGLFI">is_singular</span><span class="sP7_E">())
</span></span><span class="line" line="6"><span class="sP7_E">    {
</span></span><span class="line" line="7"><span class="sbsja">        global</span><span class="sP7_E"> $</span><span class="su5hD">post</span><span class="sP7_E">;
</span></span><span class="line" line="8"><span class="sP7_E">        $</span><span class="su5hD">post_ID </span><span class="smGrS">=</span><span class="sP7_E"> $</span><span class="su5hD">post</span><span class="smGrS">-></span><span class="su5hD">ID</span><span class="sP7_E">;
</span></span><span class="line" line="9"><span class="sVHd0">        if</span><span class="sP7_E">($</span><span class="su5hD">post_ID</span><span class="sP7_E">)
</span></span><span class="line" line="10"><span class="sP7_E">        {
</span></span><span class="line" line="11"><span class="sP7_E">            $</span><span class="su5hD">post_views </span><span class="smGrS">=</span><span class="sP7_E"> (</span><span class="sbsja">int</span><span class="sP7_E">)</span><span class="sGLFI">get_post_meta</span><span class="sP7_E">($</span><span class="su5hD">post_ID</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">views</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="s39Yj"> true</span><span class="sP7_E">);
</span></span><span class="line" line="12"><span class="sVHd0">            if</span><span class="sP7_E">(</span><span class="smGrS">!</span><span class="sGLFI">update_post_meta</span><span class="sP7_E">($</span><span class="su5hD">post_ID</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">views</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sP7_E"> ($</span><span class="su5hD">post_views</span><span class="smGrS">+</span><span class="srdBf">1</span><span class="sP7_E">)))
</span></span><span class="line" line="13"><span class="sP7_E">            {
</span></span><span class="line" line="14"><span class="sGLFI">                add_post_meta</span><span class="sP7_E">($</span><span class="su5hD">post_ID</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">views</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="srdBf"> 1</span><span class="sP7_E">,</span><span class="s39Yj"> true</span><span class="sP7_E">);
</span></span><span class="line" line="15"><span class="sP7_E">            }
</span></span><span class="line" line="16"><span class="sP7_E">        }
</span></span><span class="line" line="17"><span class="sP7_E">    }
</span></span><span class="line" line="18"><span class="sP7_E">}
</span></span><span class="line" line="19"><span class="sGLFI">add_action</span><span class="sP7_E">(</span><span class="sjJ54">'</span><span class="s_sjI">wp_head</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">record_visitors</span><span class="sjJ54">'</span><span class="sP7_E">);
</span></span><span class="line" line="20"><span emptyLinePlaceholder>
</span></span><span class="line" line="21"><span class="sutJx">/* 取得文章的阅读次数 */
</span></span><span class="line" line="22"><span class="sbsja">function</span><span class="sGLFI"> post_views</span><span class="sP7_E">($</span><span class="su5hD">echo </span><span class="smGrS">=</span><span class="srdBf"> 1</span><span class="sP7_E">)
</span></span><span class="line" line="23"><span class="sP7_E">{
</span></span><span class="line" line="24"><span class="sbsja">    global</span><span class="sP7_E"> $</span><span class="su5hD">post</span><span class="sP7_E">;
</span></span><span class="line" line="25"><span class="sP7_E">    $</span><span class="su5hD">post_ID </span><span class="smGrS">=</span><span class="sP7_E"> $</span><span class="su5hD">post</span><span class="smGrS">-></span><span class="su5hD">ID</span><span class="sP7_E">;
</span></span><span class="line" line="26"><span class="sP7_E">    $</span><span class="su5hD">views </span><span class="smGrS">=</span><span class="sP7_E"> (</span><span class="sbsja">int</span><span class="sP7_E">)</span><span class="sGLFI">get_post_meta</span><span class="sP7_E">($</span><span class="su5hD">post_ID</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">views</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="s39Yj"> true</span><span class="sP7_E">);
</span></span><span class="line" line="27"><span class="sVHd0">    if</span><span class="sP7_E"> ($</span><span class="su5hD">echo</span><span class="sP7_E">)</span><span class="sVHd0"> return</span><span class="sjJ54"> '</span><span class="s_sjI">阅读:</span><span class="sjJ54">'</span><span class="smGrS">.</span><span class="sptTA"> number_format</span><span class="sP7_E">($</span><span class="su5hD">views</span><span class="sP7_E">)</span><span class="smGrS">.</span><span class="sjJ54"> '</span><span class="s_sjI">次</span><span class="sjJ54">'</span><span class="sP7_E">;
</span></span><span class="line" line="28"><span class="sVHd0">    else</span><span class="sVHd0"> return</span><span class="sP7_E"> $</span><span class="su5hD">views</span><span class="sP7_E">;
</span></span><span class="line" line="29"><span class="sP7_E">}
</span></span><span class="line" line="30"><span class="smGrS">?>
</span></span></code></pre><p>这段代码是网上找的，不过我进行了一些小修改，原先它返回是用的 echo，会导致显示效果的问题，而是用 return 就没有问题，之前检查了半天才发现这个问题。</p><p>然后在需要输出阅读次数的地方调用<code>post_views()</code>就可以了。不同的主题调用的地方和方法有些不一样，这个需要大家自己调试才行。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[工行插件导致 Chrome 下方有莫名空行的问题]]></title>
        <id>/posts/2013/icbc-plugin-cause-blank-line-in-chrome</id>
        <link href="https://hadb.me/posts/2013/icbc-plugin-cause-blank-line-in-chrome"/>
        <updated>2013-05-14T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure><img src="https://hadb.me/static/posts/2013/20130514.icbc-plugin-cause-blank-line-in-chrome/cover.png"></img></figure><p>今天在调试自己的网页的时候，发现浏览器下方总是有一行 20px 高度的空格，见下图：</p><figure><img src="https://hadb.me/static/posts/2013/20130514.icbc-plugin-cause-blank-line-in-chrome/01.png" alt="浏览器最下方有一个20px高的空行"></img><figcaption>浏览器最下方有一个20px高的空行</figcaption></figure><p>找来找去发现我并没有这样的 padding 或者 margin，后来发现，html 最后面竟然多了这么个 DIV：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">div</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">  <</span><span class="sQzsp">object</span><span class="s9AJx"> id</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">ClCache</span><span class="sjJ54">"</span><span class="s9AJx"> click</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">sendMsg</span><span class="sjJ54">"</span><span class="s9AJx"> host</span><span class="sP7_E">=</span><span class="sjJ54">""</span><span class="s9AJx"> width</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">0</span><span class="sjJ54">"</span><span class="s9AJx"> height</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">0</span><span class="sjJ54">"</span><span class="sP7_E">></</span><span class="sQzsp">object</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E"></</span><span class="sQzsp">div</span><span class="sP7_E">>
</span></span></code></pre><p>不服气百度一下，发现这个是由于工行插件 ICBCChromeExtension 导致的，停用插件，问题解决。</p><p>我只想说，工行你太坑爹了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[设置 Win8 文件资源管理器默认打开我的电脑]]></title>
        <id>/posts/2013/set-win8-explorer-to-my-computer</id>
        <link href="https://hadb.me/posts/2013/set-win8-explorer-to-my-computer"/>
        <updated>2013-08-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在 Win8 中，任务栏上的文件资源管理器默认打开的是“库”，而我找文件的习惯是直接从我的电脑开始，库的功能用的并不多，因为我的文件放置都很有条理。怎么做呢？其实很简单。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2013/20130818.set-win8-explorer-to-my-computer/cover.png" alt="封面" /><p>在 Win8 中，任务栏上的文件资源管理器默认打开的是“库”，而我找文件的习惯是直接从我的电脑开始，库的功能用的并不多，因为我的文件放置都很有条理。怎么做呢？其实很简单。</p><p>Shift+右键资源管理器的图标，会弹出此快捷方式的右键菜单，而不是 JumpList 菜单，如下图。至于 JumpList 是什么，不必深究。</p><figure><img src="https://hadb.me/static/posts/2013/20130818.set-win8-explorer-to-my-computer/01.png" alt="Shift+右键的菜单"></img><figcaption>Shift+右键的菜单</figcaption></figure><p>在快捷方式属性里，将目标改为<code>explorer.exe shell:MyComputerFolder</code>，确定即可。</p><figure><img src="https://hadb.me/static/posts/2013/20130818.set-win8-explorer-to-my-computer/02.png" alt="设置文件资源管理器的目标位置"></img><figcaption>设置文件资源管理器的目标位置</figcaption></figure>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[IIS 下 WordPress 中文 URL 无法访问的解决方法]]></title>
        <id>/posts/2013/fix-chinese-url-in-wordpress-in-iis</id>
        <link href="https://hadb.me/posts/2013/fix-chinese-url-in-wordpress-in-iis"/>
        <updated>2013-10-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这个问题是因为 IIS 和 wordpress 对 url 的编码不一致的问题，导致传到 wordpress 里的 URL 中的中文是乱码，所以 wordpress 无法给出正确的页面。解决方法很简单：]]></summary>
        <content type="html"><![CDATA[<p>这个问题是因为 IIS 和 wordpress 对 url 的编码不一致的问题，导致传到 wordpress 里的 URL 中的中文是乱码，所以 wordpress 无法给出正确的页面。解决方法很简单：</p><p>在网站根目录下建立一个 php 文件，名字自定，例如 chineseUrl.php，内容如下：</p><pre><code><span class="line" line="1"><span class="smGrS"><?</span><span class="s_hVV">php
</span></span><span class="line" line="2"><span class="sVHd0">    if</span><span class="sP7_E"> (</span><span class="sptTA">isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">HTTP_X_ORIGINAL_URL</span><span class="sjJ54">'</span><span class="sP7_E">]))</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="sutJx">        // IIS Mod-Rewrite
</span></span><span class="line" line="4"><span class="sP7_E">        $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">REQUEST_URI</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">HTTP_X_ORIGINAL_URL</span><span class="sjJ54">'</span><span class="sP7_E">];
</span></span><span class="line" line="5"><span class="sP7_E">    }
</span></span><span class="line" line="6"><span class="sVHd0">    else</span><span class="sVHd0"> if</span><span class="sP7_E"> (</span><span class="sptTA">isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">HTTP_X_REWRITE_URL</span><span class="sjJ54">'</span><span class="sP7_E">]))</span><span class="sP7_E"> {
</span></span><span class="line" line="7"><span class="sutJx">        // IIS Isapi_Rewrite
</span></span><span class="line" line="8"><span class="sP7_E">        $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">REQUEST_URI</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">HTTP_X_REWRITE_URL</span><span class="sjJ54">'</span><span class="sP7_E">];
</span></span><span class="line" line="9"><span class="sP7_E">    }
</span></span><span class="line" line="10"><span class="sVHd0">    else</span><span class="sP7_E"> {
</span></span><span class="line" line="11"><span class="sutJx">        // Use ORIG_PATH_INFO if there is no PATH_INFO
</span></span><span class="line" line="12"><span class="sP7_E">        (</span><span class="smGrS">!</span><span class="sptTA">isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">])</span><span class="smGrS"> &&</span><span class="sptTA"> isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">ORIG_PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]))</span><span class="smGrS"> &&</span><span class="sP7_E"> ($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">ORIG_PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]);
</span></span><span class="line" line="13"><span class="sutJx">        // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
</span></span><span class="line" line="14"><span class="sVHd0">        if</span><span class="sP7_E"> (</span><span class="sptTA">isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]))</span><span class="sP7_E"> {
</span></span><span class="line" line="15"><span class="sP7_E">                ($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> ==</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">SCRIPT_NAME</span><span class="sjJ54">'</span><span class="sP7_E">])</span><span class="smGrS"> ?</span><span class="sP7_E"> ($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">REQUEST_URI</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">])</span><span class="smGrS"> :</span><span class="sP7_E"> ($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">REQUEST_URI</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">SCRIPT_NAME</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> .</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">PATH_INFO</span><span class="sjJ54">'</span><span class="sP7_E">]);
</span></span><span class="line" line="16"><span class="sP7_E">        }
</span></span><span class="line" line="17"><span class="sutJx">        // Append the query string if it exists and isn't null
</span></span><span class="line" line="18"><span class="sP7_E">        (</span><span class="sptTA">isset</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">QUERY_STRING</span><span class="sjJ54">'</span><span class="sP7_E">])</span><span class="smGrS"> &&</span><span class="smGrS"> !</span><span class="sptTA">empty</span><span class="sP7_E">($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">QUERY_STRING</span><span class="sjJ54">'</span><span class="sP7_E">]))</span><span class="smGrS"> &&</span><span class="sP7_E"> ($</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">REQUEST_URI</span><span class="sjJ54">'</span><span class="sP7_E">]</span><span class="smGrS"> .=</span><span class="sjJ54"> '</span><span class="s_sjI">?</span><span class="sjJ54">'</span><span class="smGrS"> .</span><span class="sP7_E"> $</span><span class="su5hD">_SERVER</span><span class="sP7_E">[</span><span class="sjJ54">'</span><span class="s_sjI">QUERY_STRING</span><span class="sjJ54">'</span><span class="sP7_E">]);
</span></span><span class="line" line="19"><span class="sP7_E">    }
</span></span><span class="line" line="20"><span class="sVHd0">    require</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">index.php</span><span class="sjJ54">"</span><span class="sP7_E">);
</span></span><span class="line" line="21"><span class="smGrS">?>
</span></span></code></pre><p>修改根目录下的 web.config 文件，在 rules 节点里添加如下节点：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">rule</span><span class="s9AJx"> name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">chineseUrl</span><span class="sjJ54">"</span><span class="s9AJx"> stopProcessing</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">true</span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">match</span><span class="s9AJx"> url</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">^(tag|category)/(.*)$</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="3"><span class="sP7_E">    <</span><span class="sQzsp">action</span><span class="s9AJx"> type</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">Rewrite</span><span class="sjJ54">"</span><span class="s9AJx"> url</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">chineseurl.php</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="4"><span class="sP7_E"></</span><span class="sQzsp">rule</span><span class="sP7_E">>
</span></span></code></pre><p>这里的<code>(tag|category)</code>根据具体情况做相应调整。</p><p>搞定！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[写在 2014 年伊始]]></title>
        <id>/posts/2014/2014-beginning</id>
        <link href="https://hadb.me/posts/2014/2014-beginning"/>
        <updated>2014-01-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[不知不觉又写代码写到凌晨。]]></summary>
        <content type="html"><![CDATA[<p>不知不觉又写代码写到凌晨。</p><p>自从高中毕业，就再也没怎么写过文章了。大学里，有太多的事情要去做，太多的东西会分散精力。2013 年匆匆过去了，不记下点东西，总觉得像失去了些什么。</p><p>这一年，我党员转正了。从当初决定入党，到如今，波波折折，确实是花了不少精力和时间，虽然有点抵触，但一颗积极向上的心让我坚持了下来。转正后，没有那么多活动了，按时出席下支部大会，为学弟学妹们举手表决下就没啥别的事了，轻松不少。</p><p>这一年，我找到工作了。暑期实践通过了简单的面试去了新蛋，之后便一直留了下来，从当初的 8 人组，到如今只剩下我和 Fiona 俩人。虽然和之前相比，孤单了不少，但新蛋的环境，工作的氛围，还是让我很欣慰。在新蛋，我学习得很快。先是做了个小项目 Zergling，当时其实刚接触 ASP.NET，之前只有 WinForm 和 WPF 的基础，不过上手很快。Zergling 算是入门作业。在 Newegg 网站的框架下，几个还没毕业的小朋友，初次尝试多人合作，功能是基本完成了，但代码质量很低。后来完成了 Zergling，我回成都过了一个月的暑假。开学时再回到了新蛋，我去 Special Team 帮忙，在那里，我第一次接触到微软的 MVC 框架，从此我便深深地爱上了它，实在太强大，用起来太爽。Special Team 的源代码管理用的是 Git，在那段时间，我也把 Git 用得风生水起。MVC 和 Git，我后来自己写代码一直在使用。再后来，继续回到 WWW Team，开始尝试跟着大部队维护 Newegg 网站，跟着 Kerwin 一起做项目。Kerwin 对我帮助很大，很和蔼，很耐心，也超厉害。后来 Rex 和 Zwei 走了，我接手 Pacific，我在后台看到 Peter 给 Kerwin 的评语是“老 PM，很有经验，带新人很有一套”。Kerwin 是个非常棒的老师。还有 Ben，也很厉害，我问他问题，他总能一语中的，一下子就能定位问题所在。另外，在新蛋，我还拥有了一个英文名，Bean。之前给自己取过几个，没人喊你英文名，搞到最后自己都忘记了。入职时，我实在想不出啥英文名，就以我名字的发音，取了个 Bean，我很喜欢憨豆先生，像他一样能逗别人开心也挺好的，就叫 Bean 了。在公司，大家都叫英文名。Bean 本来不是我的名字，叫的人多了，也就成了我的名字。</p><p>这一年，我发布了我有史以来最满意的产品。整个暑假，我基本上都在打星际，第一次打到大师组，也算完成了我一个心愿。当时比较关心天梯排名，国内没有这样的网站，外国有两个这样的网站，把服务器上所有的人的分数汇总，列出总的排行榜。当时我就想自己也做个，暑假期间，做了一些简单的研究。暑假一回来，我立马开始写，9 月 2 号租了阿里云的服务器，9 月 19 号那天正式上线了 0.2 版，当时是用户访问，实时抓取外国网站上的页面，进行转换，以我的样式展示出来，相当于加了个壳，汉化了下。尽管如此，当时在贴吧和 NeoTV 论坛里发了之后，立马反响很高，给了我很大的鼓励。后来在公司接触到 MVC 之后，我立马换成 MVC 框架，不再是等用户访问时才去抓数据了，而是后台有一个 app，一直去抓数据，然后到我的数据库里，这样就可以进行搜索以及更多的操作。一点点更新，不断完善，添加新功能，用户反响很好，在我放了一个捐赠链接之后，也有不少热心玩家给我捐赠，至少服务器的开销是可以抵掉了。在做这个网站的时候，我也学到了许多东西，也在不断完善代码，是我目前最满意的产品。很多功能都是我以前想都不敢想的，当真正开始做的时候，就发现，没有什么是做不了的！给了自己很大的信心。后来，北京的一个星际 2 俱乐部经理还特地联系我，打电话给我鼓励，给我提建议，给了我很大的力量，当我在贴子里说下个版本会重磅推出组织比赛的功能的时候，他很激动。有人在期待着我，让我很有动力。写这篇文章之前我还在写组织比赛的功能，已经完成一半了，争取明天能上线。</p><p>关于暑假之前，我已没有太多的记忆，从博客里信息来看，我主要在捣鼓虚拟主机，后来实在用着不爽，才换成了阿里云的服务器。年初的时候我发布了 ListManager，那是我学 WPF 的上手作品，后面好几门课我都直接用的这个作品当的大作业，其实做得还不错，只是发布起来很不方便，因为是.NET 4.0 写的，很多人机器上并没有.NET 4.0，还得先装.NET 环境，所以用户并不多。在我接触了 MVC 之后，我已决定今后的重心是 Web 应用的开发了，毕竟跨平台性更好一点。</p><p>2013 年已经过去，总结一下，这一年我学习了很多，成长了很多，进步了很多，我可以自豪地对自己说，我没有虚度！</p><p>希望 2014 年更上一层楼。加油！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[站在人生的十字路口]]></title>
        <id>/posts/2014/at-the-crossroads-of-life</id>
        <link href="https://hadb.me/posts/2014/at-the-crossroads-of-life"/>
        <updated>2014-02-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近，有点犹豫。]]></summary>
        <content type="html"><![CDATA[<p>最近，有点犹豫。</p><p>我想，还是花时间写点东西，相信写完之后我可以坚定自己的选择。</p><p>小时候，记不清是从何时起，我已烙下创业的念头。从小就有一个当 CEO，管理别人、赚大钱的梦想。</p><p>虽然在大学，我在学业上并没有太认真，也花了不少时间打游戏。然而，所幸的是，我的技术发展还是挺快的，或许是因为接触代码早的缘故吧，我学起新东西非常快。任何难关都能克服，而且执行力也足够强，想做一件事，也能持之以恒地做下去。</p><p>我想，每个优秀的程序员的内心，都应该有个创业的梦想吧，关键是，何时真正去实施。而我，希望能够早点创业。</p><p>在新蛋实习半年多了，也接触了大公司的环境，大公司项目的开发流程，也真正发现了自己想做什么，不想做什么。</p><p>家人希望我能够边上班边搞创业的事，以工作为重。虽然那样也很有道理，可是我想全身心投入。至少是从现在开始到毕业前这段时间。</p><p>这段时间，我不想再实习，我想静下心来，把驾照学了，静下心来好好做产品，静下心来好好规划一下未来。我只剩下最后这一小段完全自由的时间了。我希望认真把握，成了，我便开开心心开始踏踏实实创业，不成，希望能再回新蛋，过几年再试。当然，我内心希望，也相信我能成功。</p><p>至少，大学的这最后一段时光，我希望认真度过。</p><p>加油吧！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[WebKit 浏览器 table 里的 white-space:nowrap 的问题]]></title>
        <id>/posts/2014/webkit-white-spacenowrap</id>
        <link href="https://hadb.me/posts/2014/webkit-white-spacenowrap"/>
        <updated>2014-08-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[参考链接]]></summary>
        <content type="html"><![CDATA[<p><a href="http://www.w3help.org/zh-cn/causes/RT5004" rel="nofollow">参考链接</a></p><p>今天遇到一个类似的问题，在 table 里面，设置的 <code>white-space:nowrap</code> 导致一行被撑大，解决办法在 table 上设置 <code>table-layout:fixed</code>。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[HADB.ME 归来！]]></title>
        <id>/posts/2015/hadb-me-back</id>
        <link href="https://hadb.me/posts/2015/hadb-me-back"/>
        <updated>2015-01-28T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<pre><code><span class="line" line="1"><span class="sbgvK">Hello,</span><span class="s_sjI"> World!
</span></span></code></pre><p>重新注册了<a href="https://hadb.me/" rel="nofollow">hadb.me</a>域名，决定用 Hexo 重建个人博客，部署在 GitHub 上。</p><p>感觉 Hexo 很酷！不过安装以及部署中也遇到了不少问题，过段时间写个教程。</p><p>今天先到这里，下班！</p><p>Cool! YDS!</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 GitHub 上为 Hexo 配置自定义域名]]></title>
        <id>/posts/2015/use-custom-domain-for-hexo-on-github</id>
        <link href="https://hadb.me/posts/2015/use-custom-domain-for-hexo-on-github"/>
        <updated>2015-01-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天尝试了下 Hexo，感觉很酷。主要有以下几个特点：]]></summary>
        <content type="html"><![CDATA[<p>昨天尝试了下 Hexo，感觉很酷。主要有以下几个特点：</p><ol><li>无需数据库，所有文章都可以基于 git 来存储</li><li>纯静态，在编写好文章之后，生成静态文件，对服务器基本没要求</li><li>部署简单，<code>hexo generate --deploy</code>，轻松完成生成、部署功能</li><li>主题丰富，界面简洁，相比臃肿的 WordPress，爽多了</li></ol><p>安装很简单，按照<a href="http://hexo.io/docs/" rel="nofollow">这里</a>的教程，进行就可以了。前提是确保 npm 和 git 功能都能用就行。我主要分享一下在配置自定义域名所遇到的问题。</p><p>我是将其部署到 GitHub 上的，没有采用 hadb.github.io 作为 repo 名，因为我是想将网站叫做 HADB.ME，所以我就创建了一个 HADB.ME 的 repo。这和使用 hadb.github.io 有点区别。</p><p>使用 hadb.github.io 的话，master 分支是作为页面显示的分支的，而使用 HADB.ME 的话，是使用 ph-pages 分支作为显示的分支的。这时，只需在 ph-pages 分支的根目录放一个 CNAME 文件，内容就是<code>hadb.me</code>，然后将域名 cname 到 hadb.github.io，github 会自动判断出 HADB.ME 这个项目中有一个 CNAME，里面就是配的<code>hadb.me</code>，于是<a href="https://hadb.me/" rel="nofollow">hadb.me</a>就成功地变成了 HADB.ME 项目页面的域名了。这时，我们在_config.yml 中，就可以将 url 配为<code>http://hadb.me/</code>，root 为<code>/</code>。当然，这样会有一个问题，就是直接访问 <a href="http://hadb.github.io/HADB.ME/" rel="nofollow">http://hadb.github.io/HADB.ME/</a> 的时候，无法显示样式。这个可以这样解决，当访问路径是 <a href="http://hadb.github.io/HADB.ME/" rel="nofollow">http://hadb.github.io/HADB.ME/</a> 时，通过 js 直接跳转到 <a href="https://hadb.me/" rel="nofollow">https://hadb.me/</a> 上就可以了，在主题的 head.ejs 文件中，<code></head></code>前加入如下代码：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">script</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sutJx">  // Redirect to hadb.me
</span></span><span class="line" line="3"><span class="sVHd0">  if</span><span class="su5hD"> (window</span><span class="sP7_E">.</span><span class="su5hD">location</span><span class="sP7_E">.</span><span class="su5hD">hostname </span><span class="smGrS">===</span><span class="sjJ54"> '</span><span class="s_sjI">hadb.github.io</span><span class="sjJ54">'</span><span class="su5hD">) </span><span class="sP7_E">{
</span></span><span class="line" line="4"><span class="su5hD">    window</span><span class="sP7_E">.</span><span class="su5hD">location</span><span class="sP7_E">.</span><span class="su5hD">href</span><span class="smGrS"> =</span><span class="sjJ54"> '</span><span class="s_sjI">http://hadb.me/</span><span class="sjJ54">'
</span></span><span class="line" line="5"><span class="sP7_E">  }
</span></span><span class="line" line="6"><span class="sP7_E"></</span><span class="sQzsp">script</span><span class="sP7_E">>
</span></span></code></pre><p>还有一个问题是，每当我在 ph-pages 中创建一个 CNAME，每次 hexo deploy 之后，ph-pages 中的 commit 历史会重建，CNAME 文件就丢失了，为此我很苦恼。后来发现，只需要将 CNAME 文件放到<code>source</code>文件夹下，就可以了，每次 deploy 会自动放到根目录。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[跨站点迁移多说评论]]></title>
        <id>/posts/2015/move-duoshuo-comments</id>
        <link href="https://hadb.me/posts/2015/move-duoshuo-comments"/>
        <updated>2015-02-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[近日在捣鼓HADB.ME的个人博客，之前所有的博客都在blog.haoest.com里，如今想把一些技术分享以及个人的小结什么的单独抽出来放到HADB.ME里，而和好易思特有关的博客还放在blog.haoest.com里。这涉及到一个问题，那就是要将原先多说里属于blog.haoest.com的评论移动到HADB.ME里。]]></summary>
        <content type="html"><![CDATA[<p>近日在捣鼓<a href="https://hadb.me/" rel="nofollow">HADB.ME</a>的个人博客，之前所有的博客都在<a href="http://blog.haoest.com/" rel="nofollow">blog.haoest.com</a>里，如今想把一些技术分享以及个人的小结什么的单独抽出来放到<a href="https://hadb.me/" rel="nofollow">HADB.ME</a>里，而和好易思特有关的博客还放在<a href="http://blog.haoest.com/" rel="nofollow">blog.haoest.com</a>里。这涉及到一个问题，那就是要将原先多说里属于<a href="http://blog.haoest.com/" rel="nofollow">blog.haoest.com</a>的评论移动到<a href="https://hadb.me/" rel="nofollow">HADB.ME</a>里。</p><p>经过思考，发现可以这么搞。在多说<a href="http://blog.haoest.com/" rel="nofollow">blog.haoest.com</a>站点的后台中，将所有评论导出，然后导入到多说<a href="https://hadb.me/" rel="nofollow">HADB.ME</a>站点的后台中。</p><p>不过，多说的管理页面有个很坑爹的 ThreadKey，其实没啥作用，我一开始以为是用 ThreadKey 作为文章的 Id 的，结果发现，其实有个隐藏的 ThreadId 才是关键。于是打开从<a href="http://blog.haoest.com/" rel="nofollow">blog.haoest.com</a>中导出的评论，是个 json 格式的文件。注意，只需要评论，而无需导出文章列表，因为导出的文章列表的 id、url 等都是旧数据。我们可以依次打开新站点的一篇文章，以及旧站点中对应的文章，分别通过<code>$('input[name="thread_id"]').val()</code>来获取其 ThreadId，然后在 json 文件中进行替换，将旧的 ThreadId 替换成新的 ThreadId，最后在导入到多说<a href="https://hadb.me/" rel="nofollow">HADB.ME</a>站点的后台中即可。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2015 工作计划]]></title>
        <id>/posts/2015/2015-start-working</id>
        <link href="https://hadb.me/posts/2015/2015-start-working"/>
        <updated>2015-02-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[上班第一天。]]></summary>
        <content type="html"><![CDATA[<p>上班第一天。</p><p>原本昨天上班，家里有事，请了一天假。结果发现，今天来的人还是不多。暂时没有啥事，写点东西。前段时间一直在忙结婚的事，都没啥时间写写年末小结和新年寄语啥的。今天抽点时间写一下。</p><p>过了个年，感觉整个人都懒了很多。很久没有畅快淋漓地写代码了。主要是心没有静下来，一堆事情，零散的时间写代码的效率只有 10%，只好用来消遣，帮客户修复一些小 bug。</p><p>接下来一段时间，要做的事情还是比较多的，四箭齐发项目有一个比较大的功能点需要做，翼书网项目才将框架搭好，界面以及功能还基本没动，接下来需要重点完成。希望在 4 月，能够将翼书网成功上线，挺有挑战的，有不少东西是没做过的，需要学习。</p><p>公司的项目三月底 release，只剩下一些修 bug 的任务，再抽空研究下 IE 下截屏插件的小项目，近期就没啥压力了。</p><p>早早地结了婚，接下来一年希望能够静下心来，趁着还没娃，再任性一段时间，多写写代码。星际战区今年一定要做个新版本，改用 Redis 来做数据更新，提升性能，另外还要把一些拖着没做的功能都给做一下，自己的项目也要有计划才行。可以少接一些别的项目，专心搞搞自己的东西。HAOest 首页和博客都要重新做一下，就用 Hexo，感觉足够了。Blog 文章的迁移需要多注意一下。另外，HAOest 的 Server 要抽空升级下系统，升到 2012R2。</p><p>生命短暂又脆弱，黄金时期也就那么十几二十年。好好努力吧！活出自己的精彩！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Windows 关屏小工具]]></title>
        <id>/posts/2015/windows-close-monitor-tool</id>
        <link href="https://hadb.me/posts/2015/windows-close-monitor-tool"/>
        <updated>2015-03-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[有时候下班的时候不想关机，有很多原因，比如有 N 个网页 Tab 开着，有些可能还需要进一步查阅，关了的话从历史里不太好找，又或者，VS 开着调试，没做完，而第二天重新跑一下要很久。于是便有了挂机。可是公然挂机其实并不好，公司有规定下班自觉关机。按显示器按钮太 LOW，高端人士怎么能用这么粗鲁的方法呢！以前我用的是设置 Windows 关屏时间，5 分钟不动鼠标就关闭屏幕。这个坏处是不够及时。有什么办法能立马关闭屏幕呢？于是找到了如下代码：]]></summary>
        <content type="html"><![CDATA[<p>有时候下班的时候不想关机，有很多原因，比如有 N 个网页 Tab 开着，有些可能还需要进一步查阅，关了的话从历史里不太好找，又或者，VS 开着调试，没做完，而第二天重新跑一下要很久。于是便有了挂机。可是公然挂机其实并不好，公司有规定下班自觉关机。按显示器按钮太 LOW，高端人士怎么能用这么粗鲁的方法呢！以前我用的是设置 Windows 关屏时间，5 分钟不动鼠标就关闭屏幕。这个坏处是不够及时。有什么办法能立马关闭屏幕呢？于是找到了如下代码：</p><pre><code><span class="line" line="1"><span class="sVHd0">#pragma</span><span class="s9AJx"> comment</span><span class="su5hD">( </span><span class="s9AJx">linker</span><span class="su5hD">, </span><span class="sjJ54">"</span><span class="s_sjI">/subsystem:</span><span class="s_hVV">\"</span><span class="s_sjI">windows</span><span class="s_hVV">\"</span><span class="s_sjI"> /entry:</span><span class="s_hVV">\"</span><span class="s_sjI">mainCRTStartup</span><span class="s_hVV">\"</span><span class="sjJ54">"</span><span class="su5hD"> )
</span></span><span class="line" line="2"><span class="sVHd0">#include</span><span class="sjJ54"> <</span><span class="s_sjI">windows.h</span><span class="sjJ54">>
</span></span><span class="line" line="3"><span class="sbsja">int</span><span class="sGLFI"> main</span><span class="sP7_E">()
</span></span><span class="line" line="4"><span class="sP7_E">{
</span></span><span class="line" line="5"><span class="su5hD">    ::</span><span class="sGLFI">SendMessageA</span><span class="sP7_E">(</span><span class="su5hD">HWND_BROADCAST</span><span class="sP7_E">,</span><span class="su5hD"> WM_SYSCOMMAND</span><span class="sP7_E">,</span><span class="su5hD"> SC_MONITORPOWER</span><span class="sP7_E">,</span><span class="sP7_E"> (</span><span class="su5hD">LPARAM</span><span class="sP7_E">)</span><span class="srdBf">2</span><span class="sP7_E">);
</span></span><span class="line" line="6"><span class="su5hD">    ::</span><span class="sGLFI">Sleep</span><span class="sP7_E">(</span><span class="srdBf">200</span><span class="sP7_E">);
</span></span><span class="line" line="7"><span class="sGLFI">    LockWorkStation</span><span class="sP7_E">();
</span></span><span class="line" line="8"><span class="sVHd0">    return</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="9"><span class="sP7_E">}
</span></span></code></pre><p>建一个 C++控制台程序，插入上面的代码，搞定，双击一下自动关屏+锁屏！</p><p>Cool！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Entity Framework Code First 两个字段关联到同一张表]]></title>
        <id>/posts/2015/entity-framework-code-first-two-foreign-keys-from-same-table</id>
        <link href="https://hadb.me/posts/2015/entity-framework-code-first-two-foreign-keys-from-same-table"/>
        <updated>2015-03-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前也遇到过类似的问题，属于 Code First 中稍微复杂点的关系处理，现将解决方法记录下来。]]></summary>
        <content type="html"><![CDATA[<p>之前也遇到过类似的问题，属于 Code First 中稍微复杂点的关系处理，现将解决方法记录下来。</p><h4 id="场景">场景</h4><p>某网上书城欲推出书券功能，书券购买之后，会有一个唯一的 Id，可用来直接兑换某本书。书券可以自己兑换，也可以将 ID 送给朋友来兑换。现在我们需要将书券的购买者和兑换者都记录下来。</p><h4 id="类的设计和注意事项">类的设计和注意事项</h4><pre><code><span class="line" line="1"><span class="sbsja">public</span><span class="sG8yY"> class</span><span class="sbgvK"> BookCoupon
</span></span><span class="line" line="2"><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="sP7_E">    [</span><span class="sbgvK">Key</span><span class="sP7_E">]
</span></span><span class="line" line="4"><span class="sP7_E">    [</span><span class="sbgvK">DatabaseGeneratedAttribute</span><span class="sP7_E">(</span><span class="su5hD">DatabaseGeneratedOption</span><span class="sP7_E">.</span><span class="su5hD">Identity</span><span class="sP7_E">)]
</span></span><span class="line" line="5"><span class="sP7_E">    [</span><span class="sbgvK">Index</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">BookCouponIdIndex</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="6"><span class="sbsja">    public</span><span class="sbgvK"> Guid</span><span class="sbgvK"> BookCouponId</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="7"><span emptyLinePlaceholder>
</span></span><span class="line" line="8"><span class="sP7_E">    [</span><span class="sbgvK">Index</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">BookIdIndex</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="9"><span class="sbsja">    public</span><span class="smGrS"> int</span><span class="sbgvK"> BookId</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="10"><span emptyLinePlaceholder>
</span></span><span class="line" line="11"><span class="sP7_E">    [</span><span class="sbgvK">ForeignKey</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">BookId</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="12"><span class="sbsja">    public</span><span class="sbsja"> virtual</span><span class="sbgvK"> Book</span><span class="sbgvK"> Book</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="13"><span emptyLinePlaceholder>
</span></span><span class="line" line="14"><span class="sP7_E">    [</span><span class="sbgvK">Index</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">BuyUserIdIndex</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="15"><span class="sbsja">    public</span><span class="smGrS"> string</span><span class="sbgvK"> BuyUserId</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="16"><span emptyLinePlaceholder>
</span></span><span class="line" line="17"><span class="sP7_E">    [</span><span class="sbgvK">ForeignKey</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">BuyUserId</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="18"><span class="sbsja">    public</span><span class="sbsja"> virtual</span><span class="sbgvK"> User</span><span class="sbgvK"> BuyUser</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="19"><span emptyLinePlaceholder>
</span></span><span class="line" line="20"><span class="sP7_E">    [</span><span class="sbgvK">Index</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">RedeemUserIdIndex</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="21"><span class="sbsja">    public</span><span class="smGrS"> string</span><span class="sbgvK"> RedeemUserId</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="22"><span emptyLinePlaceholder>
</span></span><span class="line" line="23"><span class="sP7_E">    [</span><span class="sbgvK">ForeignKey</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">RedeemUserId</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="24"><span class="sbsja">    public</span><span class="sbsja"> virtual</span><span class="sbgvK"> User</span><span class="sbgvK"> RedeemUser</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="25"><span emptyLinePlaceholder>
</span></span><span class="line" line="26"><span class="sbsja">    public</span><span class="sbgvK"> DateTime</span><span class="sbgvK"> BuyTime</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="27"><span emptyLinePlaceholder>
</span></span><span class="line" line="28"><span class="sbsja">    public</span><span class="sbgvK"> DateTime</span><span class="sbgvK"> RedeemTime</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="29"><span class="sP7_E">}
</span></span></code></pre><pre><code><span class="line" line="1"><span class="sbsja">public</span><span class="sG8yY"> class</span><span class="sbgvK"> User
</span></span><span class="line" line="2"><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="sP7_E">    [</span><span class="sbgvK">Key</span><span class="sP7_E">]
</span></span><span class="line" line="4"><span class="sP7_E">    [</span><span class="sbgvK">DatabaseGeneratedAttribute</span><span class="sP7_E">(</span><span class="su5hD">DatabaseGeneratedOption</span><span class="sP7_E">.</span><span class="su5hD">Identity</span><span class="sP7_E">)]
</span></span><span class="line" line="5"><span class="sP7_E">    [</span><span class="sbgvK">Index</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">UserIdIndex</span><span class="sjJ54">"</span><span class="sP7_E">)]
</span></span><span class="line" line="6"><span class="sbsja">    public</span><span class="sbgvK"> Guid</span><span class="sbgvK"> UserId</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="7"><span emptyLinePlaceholder>
</span></span><span class="line" line="8"><span class="sbsja">    public</span><span class="smGrS"> string</span><span class="sbgvK"> UserName</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="9"><span emptyLinePlaceholder>
</span></span><span class="line" line="10"><span class="sbsja">    public</span><span class="sbsja"> virtual</span><span class="sbgvK"> ICollection</span><span class="sP7_E"><</span><span class="sbgvK">BookCoupon</span><span class="sP7_E">></span><span class="sbgvK"> BuyCoupons</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="sbsja">    public</span><span class="sbsja"> virtual</span><span class="sbgvK"> ICollection</span><span class="sP7_E"><</span><span class="sbgvK">BookCoupon</span><span class="sP7_E">></span><span class="sbgvK"> RedeemCoupons</span><span class="sP7_E"> {</span><span class="sG8yY"> get</span><span class="sP7_E">;</span><span class="sG8yY"> set</span><span class="sP7_E">;</span><span class="sP7_E"> }
</span></span><span class="line" line="13"><span emptyLinePlaceholder>
</span></span><span class="line" line="14"><span class="su5hD">    ......
</span></span><span class="line" line="15"><span class="sP7_E">}
</span></span></code></pre><p>另外，在 DbContext 中需要 override OnModelCreating 方法：</p><pre><code><span class="line" line="1"><span class="sbsja">protected</span><span class="sbsja"> override</span><span class="smGrS"> void</span><span class="sGLFI"> OnModelCreating</span><span class="sP7_E">(</span><span class="sbgvK">DbModelBuilder</span><span class="sbgvK"> modelBuilder</span><span class="sP7_E">)
</span></span><span class="line" line="2"><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="s_hVV">    base</span><span class="sP7_E">.</span><span class="sGLFI">OnModelCreating</span><span class="sP7_E">(</span><span class="su5hD">modelBuilder</span><span class="sP7_E">);
</span></span><span class="line" line="4"><span class="su5hD">    modelBuilder</span><span class="sP7_E">.</span><span class="sGLFI">Entity</span><span class="sP7_E"><</span><span class="sbgvK">ApplicationUser</span><span class="sP7_E">>().</span><span class="sGLFI">HasMany</span><span class="sP7_E">(</span><span class="sbgvK">u</span><span class="smGrS"> =></span><span class="su5hD"> u</span><span class="sP7_E">.</span><span class="su5hD">BuyCoupons</span><span class="sP7_E">).</span><span class="sGLFI">WithRequired</span><span class="sP7_E">(</span><span class="sbgvK">n</span><span class="smGrS"> =></span><span class="su5hD"> n</span><span class="sP7_E">.</span><span class="su5hD">BuyUser</span><span class="sP7_E">).</span><span class="sGLFI">WillCascadeOnDelete</span><span class="sP7_E">(</span><span class="syTEX">false</span><span class="sP7_E">);
</span></span><span class="line" line="5"><span class="su5hD">    modelBuilder</span><span class="sP7_E">.</span><span class="sGLFI">Entity</span><span class="sP7_E"><</span><span class="sbgvK">ApplicationUser</span><span class="sP7_E">>().</span><span class="sGLFI">HasMany</span><span class="sP7_E">(</span><span class="sbgvK">u</span><span class="smGrS"> =></span><span class="su5hD"> u</span><span class="sP7_E">.</span><span class="su5hD">RedeemCoupons</span><span class="sP7_E">).</span><span class="sGLFI">WithRequired</span><span class="sP7_E">(</span><span class="sbgvK">n</span><span class="smGrS"> =></span><span class="su5hD"> n</span><span class="sP7_E">.</span><span class="su5hD">RedeemUser</span><span class="sP7_E">).</span><span class="sGLFI">WillCascadeOnDelete</span><span class="sP7_E">(</span><span class="syTEX">false</span><span class="sP7_E">);
</span></span><span class="line" line="6"><span class="sP7_E">}
</span></span></code></pre><p>注意最后的<code>.WillCascadeOnDelete(false)</code>，因为在这样多对多的绑定中，使用级联删除会报错。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 Windows Server 2012 (R2) 上显示 “计算机”]]></title>
        <id>/posts/2015/show-computer-on-desktop-in-windows-server</id>
        <link href="https://hadb.me/posts/2015/show-computer-on-desktop-in-windows-server"/>
        <updated>2015-03-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Windows Server 2012 (R2)的桌面上，默认是没有“计算机”的，而且桌面上右键也是不会出现个性化的，如下方法可以迅速打开“桌面图标设置”：]]></summary>
        <content type="html"><![CDATA[<p>Windows Server 2012 (R2)的桌面上，默认是没有“计算机”的，而且桌面上右键也是不会出现个性化的，如下方法可以迅速打开“桌面图标设置”：</p><p>1、在左下角 Windows 徽标上右键，点击“运行”</p><p>2、在“运行”中输入如下代码：</p><pre><code><span class="line" line="1"><span class="sbgvK">rundll32.exe</span><span class="s_sjI"> shell32.dll,Control_RunDLL</span><span class="s_sjI"> desk.cpl,,0
</span></span></code></pre><p>3、将“计算机”的图标勾上</p><p>Coooool!</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于拍牌]]></title>
        <id>/posts/2015/about-paipai</id>
        <link href="https://hadb.me/posts/2015/about-paipai"/>
        <updated>2015-03-21T00:00:00.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>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[AuthenticationManager 无法注销用户的问题]]></title>
        <id>/posts/2015/authenticationmanager-signout-not-working</id>
        <link href="https://hadb.me/posts/2015/authenticationmanager-signout-not-working"/>
        <updated>2015-03-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近遇到一个很诡异的问题，在最近的一个新项目中，发现在 MVC5 下，偶尔会出现登陆的用户无法注销的问题，经检查发现AuthenticationManager.SignOut()执行之后并没有删除 Cookie，手动删除 Cookie 之后，该功能又正常了，又能正常登陆、注销了。前面几次出现这个问题我都是手动删除 Cookie，发现恢复了之后，我也就没在意。]]></summary>
        <content type="html"><![CDATA[<p>最近遇到一个很诡异的问题，在最近的一个新项目中，发现在 MVC5 下，偶尔会出现登陆的用户无法注销的问题，经检查发现<code>AuthenticationManager.SignOut()</code>执行之后并没有删除 Cookie，手动删除 Cookie 之后，该功能又正常了，又能正常登陆、注销了。前面几次出现这个问题我都是手动删除 Cookie，发现恢复了之后，我也就没在意。</p><p>刚刚又出现这个问题，我怒了，决定 Google 下。Google 了一番之后，发现这个问题还挺普遍，但是都没有什么好的答案。后来看到有人说，用<code>AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie)</code>可以解决这个问题，一试，还真解决了。可是官方的例子里并没有传这个参数，而且我以前的几个站点，都是用的官方例子里的<code>AuthenticationManager.SignOut()</code>，而且都没有出现这个问题。百思不得其解，好挫折。不弄清楚睡不着觉。于是又继续搜，终于发现了一个帖子，<a href="https://aspnetidentity.codeplex.com/workitem/2347" rel="nofollow">https://aspnetidentity.codeplex.com/workitem/2347</a>。</p><p>当初这个问题应该是发生在 Microsoft.AspNet.Identity 2.0/2.1 rc + Microsoft.Owin.3.0 rc 版中，我以前项目用的 Identity2.2 + Owin 3.0 中，应该是没有这个问题。最近刚更新的 Identity2.3 + Owin 3.0.1 中，又有了这个问题。</p><p>哈哈！突然感觉我已经走在 MVC 的前沿了。记下这个问题，为今后遇到这个问题的朋友们排忧解难！</p><p>解决方案就是，在 LogOff 方法里，用<code>AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie)</code>就行了。</p><p>当然，我这个项目并没有用到第三方登陆，如果用到了第三方登陆，可能还需要添加<code>AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);</code>。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[写在 3 月底]]></title>
        <id>/posts/2015/written-at-the-end-of-march</id>
        <link href="https://hadb.me/posts/2015/written-at-the-end-of-march"/>
        <updated>2015-03-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[一个悠闲的午后，加湿器的嘴里正吐着白雾，咖啡上浮着的一层白沫，也正一点一点消失不见，耳边的音乐里不断上演着一个又一个旋律……]]></summary>
        <content type="html"><![CDATA[<p>一个悠闲的午后，加湿器的嘴里正吐着白雾，咖啡上浮着的一层白沫，也正一点一点消失不见，耳边的音乐里不断上演着一个又一个旋律……</p><p>刚刚在果壳上看到这样一篇文章《<a href="http://www.guokr.com/article/440082/" rel="nofollow">“人类的”纪元会登入地质年代表吗？</a>》。顿时又对奇妙而又短暂的生命有了点感触，想写点什么。</p><p>我常常有这样的习惯，就是在有压力的时候，偏偏不去做最该做的事，而喜欢把精力放在别的地方。正如大学时，临近考试，我却迟迟不开始复习，反而开始认真写起代码或者收拾书桌、整理电脑里的文件，做着以前一直想做却拖着没做的事。往往这个时候，空闲时不想做的事情突然变得很容易去做了，而且做得很起劲。或许，这是我自己缓解压力、GTD 的方法吧。</p><p>月底，“华文翼书”要上线内测，不知从何时起，我的自信心已经爆棚到觉得已经没有我不能解决的技术问题了。这个项目，相当于一个豆瓣阅读，相当于一个起点网。一个人，一个月不到的时间。其实一开始时间很多的，但我之前一直没有完全投入，代码产出几乎为零，当然也做了一些思考以及设计。直到两个星期前，才开始大刀阔斧 Coding。把剩余要做的功能全部列下来，每天早上进行任务重排，哪些任务是昨天没完成的，剩余的时间里如何重新安排剩余任务，那些功能可以等到内测之后再完善……每天都做着这样的事，任务列表里的任务一点点减少，眼前的迷雾渐渐消失，如同游戏里整张地图已经快探索完，已经大体知道剩余的成就去哪些地方完成了。心中有数之后，终于敢静下心来，做点以前早就该做却一直拖着没做的事了。</p><p>没错，就是写点东西，不是简单地记录今天如何如何解决了啥问题，亦或直抒胸臆、记下牢骚，而是真正用心写点文字，写点除了简单地表达之外的东西。</p><p>46 亿年，地球不知不觉悄悄地转了那么多年。</p><p>视线逐渐飞到太空中，耳边突然“噌”的一下，彻底静了，没有任何声音，只有地球在那里不知不觉地转着。没有时间，只有永远。</p><p>每个人都是匆匆的过客。你在别人眼里甚至都不存在。一个人再伟大再出名，也还是会有人不认识，亦或你在他人眼里只有一个名字以及这个名字所做的一些事，对他来说，什么都不是。每个人都是活在自己的世界里，自己的交际圈，自己的生活方式，自己的心思。</p><p>很多人一生都在为了生存而挣扎着。工作只是为了拿工资，拿了工资是为了更好的生活。为什么要活着，为了什么而活着，活着要做些什么，或许永远没有想过这些问题，至少没人知道你想过。</p><p>我见过很多人，毕业后甚至不知道自己要做什么。如果大学 4 年连方向都没有想清楚，那上大学还有什么意义？</p><p>生命对每个人来说，都是公平的。无论是喜是悲，无论低调与喧哗，无论平凡与伟大，总有结束的一天。</p><p>我看过不少书，可都是工具类的，都是实用的。却很少有走心的。所以我此刻唯一能想到的可以借鉴的，也就是《钢铁是怎样炼成的》里面所说的话，它告诉我人的一生应当这样度过，那就是当回首往事时，不因虚度年华而悔恨，也不因碌碌无为而羞愧。我时常想到这句话，每每都是我虚度了年华之后。</p><p>昨晚躺在床上突然睡不着。想了想一天都做了什么，我发现竟然没有什么值得我去回味的。活在浑浑噩噩中。产出了点代码，可是是拷贝的我以前写的内容。没有创造任何东西。我作为一个有思考的程序猿，我觉得我活着就是为了改变世界的。我的工作是无比高尚的，我是用来创造新东西的，用我的智慧，创造虚拟的建筑，让人们在里面用着各种各样的功能。</p><p>弹钢琴的有两种，一种是只会弹别人的曲子的，一种是创造新曲子的。这两类唯一的不同就是，前一种都是可以轻松替代的，而第二种，就是不可替代的。没有他，或许其他人照样会谱出曲子，却永远谱不出和他一样的曲子，他产出的永远是世界上独一无二的。而程序猿也有两种，一种是不断重复别人的工作的，一种是创造新东西的。而我要做的，就是成为第二种。</p><p>一个人再怎么努力，也不可能让时间为你停留一刻。</p><p>岁月就像一把无情的刻刀，会渐渐磨平一切。刻骨铭心、痛不欲生、欣喜若狂、信誓旦旦、壮志凌云……终究会被岁月打磨，变成记忆。岁月吞没了多少爱情，撕毁了多少承诺，磨灭了多少理想。</p><p>每个人都曾有过幻想，而其中一部分人决定去实现它，于是有了理想，而这里面的一部分人在实现的过程中放弃了，于是理想又重新变成了幻想。理想只有永远不放弃，才是理想。那样回忆往事时，可以自豪的说，虽然我这辈子没能实现我的理想，但我一直在为我的理想而努力。</p><p>我觉得拿 RTS 游戏里的战争迷雾来形容理想最合适不过。理想是一张充满了迷雾的地图，幻想是你能想到的迷雾下地图的面貌，哪里有资源，哪里有矿物。而奋斗就是在地图上探索，去消除迷雾，从而找到资源的过程。这个过程中你可能突然遇到敌人，你可能一直绕圈理不出任何头绪，甚至有可能你的出生点就在一个死胡同里，永远不可能走完整个地图，正如有些理想永远不可能实现一样。可只要你一直在奋斗，理想永远是理想。</p><p>有时候会幻想，假如时间可以静止就好了，多给我点时间玩游戏，多给我点时间做作业，多给我点时间复习，多给我点时间让我完成我没时间做的事。可时间，永远不会给你这样作弊的机会。你在做梦的时候，它正在偷偷流逝。</p><p>前段时间还在知乎看到一个问题，《<a href="http://www.zhihu.com/question/26630283" rel="nofollow">假如把一个人粉碎成原子再组合，这个人还是原来的人吗？</a>》，这其实是“特修斯之船”的问题，里面有个回答，主要讲“我”是什么。在我的理解里，我们每天晚上都在经历着死亡。因为你睡着之后，昨天的那个你，已经永远是过去了，他无法得知第二天自己会经历的事，甚至不知道自己第二天还会不会醒来。每天醒来的你，只是继承了昨天的你所有的记忆。把每天都当做是最后一天来过，每天回过头来想想今天的你到底做了什么，有没有价值，有没有虚度年华，羞不羞愧。曾子每日三省其身，我们每天做到回顾一次就很不错了。</p><p>有时候我常常因为所剩的时间太零碎，导致不愿意去做一些事，只愿意用来玩。白白浪费了不少时间，之后要多多珍惜这些零散的时间。把一些任务更细化，细化到可以利用零散时间来做。这样，效率会高很多。</p><p>不知不觉说了一大堆。好久没这么畅快淋漓地打字了。虽然期间也被打断了好几次。好吧，这次就说这么多吧。接下来还得努力。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[再见，美罗]]></title>
        <id>/posts/2015/good-bye-mylo</id>
        <link href="https://hadb.me/posts/2015/good-bye-mylo"/>
        <updated>2015-03-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[美罗走了。]]></summary>
        <content type="html"><![CDATA[<p>美罗走了。</p><p>第一次，因为一个人的离职，让我鼻头酸酸的，眼里噙着泪水。</p><p>他是我职业生涯第一个老大。</p><p>从我在公司实习以来，见证了一个又一个员工的离职，和今天的感觉完全不一样。</p><p>他坐在我斜后面，虽然我常常会感觉，总有一双眼睛在盯着我。因为我偶尔也是会逛逛贴吧，逛逛知乎，逛逛和工作无关的东西，我总觉得不正大光明。甚至我做过一个小工具，可以把屏幕黑掉，我的屏幕是光面的，很反光，可以当镜子用，我可以看看头发乱没乱，还可以看看美罗是不是在偷看我。而我每次都只看到他盯着自己的屏幕。</p><p>他是那样的平易近人。每天中午，和我们一起吃饭。周末，一起踢球。甚至，还拉我们一起玩 COC。</p><p>我来面试的时候，见他第一眼，我在想，为何这个老大这么潮，当时他好像是染着红头发。后来我看到他以前的照片，简直就像古惑仔，哈哈。</p><p>一开始，我常常帮他做小工具。那时的我，虽说整天忙忙碌碌，却忙得不亦乐乎，很充实。那时他还没有坐在我后面，在离我很远很远的地方。我常常屁颠屁颠地绕很远很远的路到他那里，给他汇报今天的情况。</p><p>他总是会和我分享很多人生的道理。就连我那个来面试被拒的室友，都和我说，“感觉 Mylo 人还是很不错的，和我讲了很多道理，告诉我哪里不行，哪里需要提高。”他还告诉我怎么拍车牌，虽然我到现在还没拍到。</p><p>我记得，有一次突然下雨，而我没带伞。他走的时候和我说，我那里还有一把伞，你待会儿走的时候拿去用好了。而那晚，我和别人约好，出去见个面。那是我接的第一个比较大的私活，3W。对于刚毕业的我来说，已经是很大的数字了。那晚回来时还下着小雪，在公交站旁的路灯下，我望着天上飘着的雪，心里满满的感动。我在想，若不是美罗的伞，我今晚要冻成狗了。</p><p>从我第一天上班那天起，我就发现，这个老大很不一样。由于是第一天上班，我自然是比较积极。那时我还在学校，还没毕业。我早早就起了床。出了地铁等公交，遇到了他。那时我还不记得他叫什么名字，只知道他是老大。我喊了声，老大早。和他一起坐下了，我的公交卡放在钱包里，刷完卡后，钱包握在手里。他和我说，钱包要收起来，这样不安全。他和我聊了很多，我说我喜欢自己捣鼓网站，有自己的博客，他说他也是。他还告诉我以前解决了一个大问题之后，是多么多么开心，有成就感。而我也深有感触。当时就感觉，这是一个懂技术的老大，有共同语言啊。到公司之后发现，公司还没几个人。那会儿距离上班时间还有半个多小时。</p><p>后来，我请了几个月的假，搞毕业设计以及学驾照。当我再次回到公司的时候，已经离职了不少人，美罗的位置也搬到了我后面。几乎每天，他都是很早很早就到公司，而我每次也都是很早就来。有时我早，有时他早。而每天不变的就是：“早啊，Mylo！”、“小 Bean 早！”渐渐地，这已成了习惯。今年过完年刚过来的那几天，美罗没来，我还真有点不习惯。直到他回来，又恢复了每天的“Mylo 早”、“小 Bean 早”。</p><p>他很 Geek，和他一起做的微信招聘页面、年会系统等，都充满了极客范。小米手环刚出来，他就搞了好多个，有的作为我们的奖励，剩下的就拿来给大家用积点拍卖了。他甚至还搞过四轴飞行器、树莓派 2，拿来进行拍卖。</p><p>他对我有点偏心。我毕业后回到公司的时候，刚好有个很大的显示器空着，他直接拿给我用了。我至今都用着这遭人羡慕的屏幕（也就是前面提到的会反光可以用来做镜子的屏幕），用 grace 的话，比架构师的屏幕都大。夏天那时候，他还会从冰箱拿可乐给我喝；台湾同事送他的水果，他也会拿些给我。我至今还记得那么大那么水灵的桃子，咬在嘴里的口感。他还会送我他刚淘的手机壳，虽然只要 1 块 9，还包邮，但是心里满满的感激……这或许就是离老大近的好处吧。</p><p>文笔不好，但我觉得我想写点东西。这是美罗工作了 9 年的地方。</p><p>虽然不知道他在最后一封邮件里说的“FightingX”是个啥玩意，但感觉很 Geek 的样子。</p><p>我会哭，因为他是个好老大。</p><p>再见，美罗。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[修复 position:fixed 在 ios 虚拟键盘弹出时错位的 bug]]></title>
        <id>/posts/2015/fix-fixed-bug-in-ios-when-call-virtual-keyboards</id>
        <link href="https://hadb.me/posts/2015/fix-fixed-bug-in-ios-when-call-virtual-keyboards"/>
        <updated>2015-04-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[问题描述：在使用 bootstrap 的 navbar-fixed-top 时，发现在 iPhone 上的微信里面，当点击 input 弹出输入法之后，顶部 fixed 的 navbar 消失，在输入法没有关闭的情况下，向上滚动，会发现 navbar 在半空中。]]></summary>
        <content type="html"><![CDATA[<p>问题描述：在使用 bootstrap 的 navbar-fixed-top 时，发现在 iPhone 上的微信里面，当点击 input 弹出输入法之后，顶部 fixed 的 navbar 消失，在输入法没有关闭的情况下，向上滚动，会发现 navbar 在半空中。</p><p>Google 了一下，发现这个问题在 iOS 中很常见，Bootstrap 也对此进行了说明（<a href="http://getbootstrap.com/getting-started/#support-fixed-position-keyboards" rel="nofollow">戳这里</a>）。</p><blockquote><h4 id="virtual-keyboards">Virtual keyboards</h4><p>Also, note that if you're using a fixed navbar or using inputs within a modal, iOS has a rendering bug that doesn't update the position of fixed elements when the virtual keyboard is triggered. A few workarounds for this include transforming your elements to position: absolute or invoking a timer on focus to try to correct the positioning manually. This is not handled by Bootstrap, so it is up to you to decide which solution is best for your application.</p></blockquote><p>不过我在最近刚更新的 iOS8.3 的 Safari 中，没有发现这个问题。在 8.3 的 Safari 中，点击 input 弹出输入法之后，fixed 元素会失效，navbar 回到最顶端，没有浮在半空中。猜测是在弹出虚拟键盘之后为了节省页面空间，而对 fixed 的元素进行了处理，但是在微信中的浏览器上处理出了点问题。</p><p>目前发现最好的解决方案是在点击 input 之后，直接把 fixed 的元素变成 absolute 的，不需要浏览器自己去做处理。</p><p>有人说在滚动时用 timer 实时调整元素位置，我觉得这个很低端。浏览器去处理 fixed 元素自然有它的道理，确实可以节省屏幕空间。我们其实也没有必要在这个情况下强制显示 navbar，这时用户的重点在于输入。当我们 input 失去焦点之后，输入法关闭，这时我们再显示出 navbar。</p><p>下面直接上代码：</p><p>添加这样一段 css：</p><pre><code><span class="line" line="1"><span class="stp6e">.</span><span class="sbgvK">fixfixed</span><span class="stp6e">.</span><span class="sbgvK">navbar-fixed-top</span><span class="sP7_E"> {
</span></span><span class="line" line="2"><span class="soE4H">  position</span><span class="sP7_E">:</span><span class="s_hVV"> absolute</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="sP7_E">}
</span></span></code></pre><p>添加这样一段 js：</p><pre><code><span class="line" line="1"><span class="sGLFI">$</span><span class="su5hD">(</span><span class="sP7_E">()</span><span class="sbsja"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="2"><span class="sVHd0">  if</span><span class="skxfh"> (</span><span class="su5hD">Modernizr</span><span class="sP7_E">.</span><span class="su5hD">touch</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="3"><span class="sGLFI">    $</span><span class="skxfh">(</span><span class="su5hD">document</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="sGLFI">on</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">focus</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">input</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sP7_E"> ()</span><span class="sbsja"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="4"><span class="sGLFI">      $</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">.navbar-fixed-top</span><span class="sjJ54">'</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="sGLFI">addClass</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">fixfixed</span><span class="sjJ54">'</span><span class="skxfh">)
</span></span><span class="line" line="5"><span class="sP7_E">    }</span><span class="skxfh">)
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sGLFI">    $</span><span class="skxfh">(</span><span class="su5hD">document</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="sGLFI">on</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">blur</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">input</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sP7_E"> ()</span><span class="sbsja"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="8"><span class="sGLFI">      $</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">.navbar-fixed-top</span><span class="sjJ54">'</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="sGLFI">removeClass</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">fixfixed</span><span class="sjJ54">'</span><span class="skxfh">)
</span></span><span class="line" line="9"><span class="sP7_E">    }</span><span class="skxfh">)
</span></span><span class="line" line="10"><span class="sP7_E">  }
</span></span><span class="line" line="11"><span class="sP7_E">}</span><span class="su5hD">)
</span></span></code></pre><p>使用了 Modernizr，仅在触屏上进行处理，对桌面浏览器不做处理，这样对于桌面浏览器上的体验更好。</p><p>完美解决问题！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Web Deploy 进阶]]></title>
        <id>/posts/2015/web-deploy</id>
        <link href="https://hadb.me/posts/2015/web-deploy"/>
        <updated>2015-05-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前想写个 Web Deploy 的教程，结果一直耽搁了。最近又遇到一个比较高级的用法，打算暂时就不写基础教程了，把几个不常用的但是很有用的用法列一下。]]></summary>
        <content type="html"><![CDATA[<p>之前想写个 Web Deploy 的教程，结果一直耽搁了。最近又遇到一个比较高级的用法，打算暂时就不写基础教程了，把几个不常用的但是很有用的用法列一下。</p><h4 id="_1部署的时候排除某些文件或文件夹">1、部署的时候排除某些文件或文件夹</h4><p>这个功能其实很有用，比如一些自定义的 config，里面包含了一些 key 或者很重要的信息，而你的代码是开源的，你希望分享源代码，但是这些服务器相关的 key 还是不能暴露的，这时候，你本地的 config 里可能只是一些测试的 key，或者压根就可以是空的。在服务器上的 config 里，你可以放心大胆地配置这些 key。每次部署的时候，我们就希望跳过这些 config 文件，而不至于用本地的 config 去替换掉线上的。</p><p>我们需要修改项目的配置文件，也就是.csproj 文件，注意修改 Release 的，如果你发布选项中配置的是 Release 的话。</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">PropertyGroup</span><span class="s9AJx"> Condition</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI"> '$(Configuration)|$(Platform)' == 'Release|AnyCPU' </span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">DebugType</span><span class="sP7_E">></span><span class="su5hD">pdbonly</span><span class="sP7_E"></</span><span class="sQzsp">DebugType</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">    <</span><span class="sQzsp">Optimize</span><span class="sP7_E">></span><span class="su5hD">true</span><span class="sP7_E"></</span><span class="sQzsp">Optimize</span><span class="sP7_E">>
</span></span><span class="line" line="4"><span class="sP7_E">    <</span><span class="sQzsp">OutputPath</span><span class="sP7_E">></span><span class="su5hD">bin\</span><span class="sP7_E"></</span><span class="sQzsp">OutputPath</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">    <</span><span class="sQzsp">DefineConstants</span><span class="sP7_E">></span><span class="su5hD">TRACE</span><span class="sP7_E"></</span><span class="sQzsp">DefineConstants</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E">    <</span><span class="sQzsp">ErrorReport</span><span class="sP7_E">></span><span class="su5hD">prompt</span><span class="sP7_E"></</span><span class="sQzsp">ErrorReport</span><span class="sP7_E">>
</span></span><span class="line" line="7"><span class="sP7_E">    <</span><span class="sQzsp">WarningLevel</span><span class="sP7_E">></span><span class="su5hD">4</span><span class="sP7_E"></</span><span class="sQzsp">WarningLevel</span><span class="sP7_E">>
</span></span><span class="line" line="8"><span class="sutJx">    <!-- 下面这一行用来排除指定文件夹，分号分割多个文件夹 -->
</span></span><span class="line" line="9"><span class="sP7_E">    <</span><span class="sQzsp">ExcludeFoldersFromDeployment</span><span class="sP7_E">></span><span class="su5hD">Configurations</span><span class="sP7_E"></</span><span class="sQzsp">ExcludeFoldersFromDeployment</span><span class="sP7_E">>
</span></span><span class="line" line="10"><span class="sutJx">    <!-- 下面这一行用来排除指定文件，分号来分隔多个文件 -->
</span></span><span class="line" line="11"><span class="sP7_E">    <</span><span class="sQzsp">ExcludeFilesFromDeployment</span><span class="sP7_E">></span><span class="su5hD">XXX.config;YYY.config</span><span class="sP7_E"></</span><span class="sQzsp">ExcludeFilesFromDeployment</span><span class="sP7_E">>
</span></span><span class="line" line="12"><span class="sP7_E"></</span><span class="sQzsp">PropertyGroup</span><span class="sP7_E">>
</span></span></code></pre><h4 id="_2通过文件校验而不是修改时间来决定某个文件是否需要发布">2、通过文件校验而不是修改时间来决定某个文件是否需要发布</h4><p>这个功能同样很有用，尤其当你通过源代码管理的时候，你在不同的电脑上，虽然代码相同，但其实每个文件的修改时间并不同。这会导致你在这台电脑上部署了之后，在另一台电脑上修改了部分内容，却还是需要部署所有文件。当网络不给力的时候，部署需要很长时间。</p><p>我们需要修改部署配置文件，一般是 Properties\PublishFiles\XXX.pubxml。</p><p>我们在 PropertyGroup 里添加这么一行就可以了</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">MSDeployUseChecksum</span><span class="sP7_E">></span><span class="su5hD">true</span><span class="sP7_E"></</span><span class="sQzsp">MSDeployUseChecksum</span><span class="sP7_E">>
</span></span></code></pre><p>先写这两个。之后有空再写个详细的教程。一开始配置 Web Deploy 还是有不少注意点的。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[再见，新蛋]]></title>
        <id>/posts/2015/good-bye-newegg</id>
        <link href="https://hadb.me/posts/2015/good-bye-newegg"/>
        <updated>2015-05-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近开始变得敏感起来。上班路上，我开始变得格外注意窗外的风景。因为我知道，以后我将不会每天再走在这条熟悉的路上了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2015/20150520.good-bye-newegg/cover.jpg" alt="封面" /><p>最近开始变得敏感起来。上班路上，我开始变得格外注意窗外的风景。因为我知道，以后我将不会每天再走在这条熟悉的路上了。</p><p>这几天发生了太多事，甚至梦里都还在继续。以至于感觉过去了很久。</p><p>原本的想法很单纯，只是为了看看外面的世界，而我并没有想到，我这一去，我就真的要离开新蛋了。</p><p>携程和百度还有一家做教育的创业公司先后联系了我。百度让我提交更详细的资料，携程约我过去聊聊。我抱着参观一下携程的想法去了。</p><p>携程很大，大的让我感觉我太渺小了，置身其中，我就像一个小蚂蚁。我想，这么多人，他们应该也不知道我不是这里的吧。于是我装作很镇定，仿佛我就是携程员工一样。大摇大摆在里面转悠，观察着这里的一切。所有人忙忙碌碌，耳朵里各种声音混在一起突然感觉仿佛很安静，安静得只剩下我一个人。离 HR 跟我约的时间还有半个小时，我在这里转了一圈，看到了他们人事部的位置，于是走了过去，问了一下。面试这就提前开始了。</p><p>面试过程很顺利，先后聊了四五个人，最后拿到了 Offer。携程的薪资多得我心动了，我在想，去还是不去呢，咋跟 Peter 交代呢。</p><p>我的自信心开始爆棚，突然感觉我好像真有两把刷子。跟家人汇报了一通，他们挺高兴的。</p><p>但这不是高潮。神通广大的 Mylo 那天下午联系我，问我是不是有异动的想法，让我去他那里，薪资不输携程，做的东西也比携程有意思。</p><p>于是果断从了 Mylo。</p><p>那天晚上，梦见 Mylo 给每人发了一台 iMac，非常高大上。但是没有工作场地，我们一群人就去砍木头，像搭帐篷一样搭了个棚子，坐在里面写代码，还有点漏雨。大风吹过，支撑的木头折了，大家又赶紧一起去修。创业很艰辛，但大家却忙得不亦乐乎。</p><p>昨天，去 Mylo 那里看了一下，虽然地段差了点，但里面环境还是可以的，一看就是创业公司的样子。比梦里搭的帐篷要好多了。</p><p>昨晚，偷偷在 Peter 桌上放了一瓶小花，留了个便签，今天早上他告诉我这是他收到最好的礼物的时候，我还是忍不住泪水地哗哗流。</p><p>我不是刻意在今天离职，但很巧合的是今天是 5.20，我真心爱着这里的一切，热情的小伙伴们，扫地的阿姨，爬来爬去的乌龟……我舍不得离开。可我内心又渴望着离开，有更大的舞台在向我招手。</p><p>新蛋是我离开学校之后的第一站，这里记录了我成长的一切。我会永远记得这个温暖的大家庭，记得我在这里所发生的一切。</p><p>Newegg 经历了太多的人来人往，每个人都终会有离开的一天。但，离开不代表结束，人虽离开，但情谊还在。</p><p>世界很大，我要出去闯了。无论在天涯海角，我的身上都会刻着 Newegg 的印记。无论我今后发展如何，我永远不会忘记 Newegg 对我的培养。</p><p>哭过，笑过。</p><p>擦干泪，开启新的篇章。</p><p>by Bean, the last time at Newegg.</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[VS2015 打包程序无法在 XP 下安装的问题]]></title>
        <id>/posts/2015/vs2015-installer-not-work-in-windows-xp</id>
        <link href="https://hadb.me/posts/2015/vs2015-installer-not-work-in-windows-xp"/>
        <updated>2015-07-30T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h4 id="问题描述">问题描述</h4><p>最近有个需求，需要做一个 WinForm 程序，目标机器基本都是比较旧的 XP 机器。需要安装.net Framework 环境以及添加快捷方式等，所以决定做一个安装程序。VS 默认的是推荐使用 InstallShield Limited Edition，经过尝试，发现实在不好用，而且 Limited 版还有不少限制。于是想用以前 VS 版本中的 Installer Project。寻找了一下，发现有 2015 版的插件（<a href="https://visualstudiogallery.msdn.microsoft.com/003f3135-bbca-4eb2-951d-88820065a124" rel="nofollow" title="Microsoft Visual Studio 2015 Installer Projects">Microsoft Visual Studio 2015 Installer Projects</a>）。其他都很顺利，在 Win7、Win8.1、Win10 中安装都没有问题。唯独当我不远万里来到目标机器的时候，发现在 XP 系统上安装失败！安装程序莫名退出。纠结了几天，最终在网上搜到了解决方案。原因是这样，在 VS2010 之后的 VS 中，dpca.dll 这个文件中最低的 Windows 版本已经不支持 XP 了，导致在用 2010 以上的 VS 版本打包的安装包在 XP 上总是失败的。</p><h4 id="解决方法">解决方法</h4><ol><li>关闭 Visual Studio</li><li>从 C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Tools\Deployment 中复制 dpca.dll 文件到 C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\VSI\bin 中替换。（需要找仍在使用 VS2010 的小伙伴）</li><li>打开项目</li><li>重新编译</li></ol><h4 id="相关文章">相关文章</h4><ul><li><a href="http://stackoverflow.com/questions/23978677/dirca-checkfx-return-value-3-vs-2013-deployment-project/26039835#26039835" rel="nofollow">http://stackoverflow.com/questions/23978677/dirca-checkfx-return-value-3-vs-2013-deployment-project/26039835#26039835</a></li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[微信公众号中更换域名]]></title>
        <id>/posts/2015/change-domain-in-weixin</id>
        <link href="https://hadb.me/posts/2015/change-domain-in-weixin"/>
        <updated>2015-08-20T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h4 id="更新">更新</h4><p>如果需要实现微信授权支持多个回调域名，可以参考我这个开源项目：<a href="https://github.com/HADB/GetWeixinCode" rel="nofollow">GetWeixinCode</a></p><hr></hr><h4 id="问题描述">问题描述</h4><p>项目刚做的时候，并没有找到好的域名，所以用了一个比较长的域名。后来公司花钱买了一个心仪的域名，理所当然，我们需要启用新域名了。</p><p>我们的 H5 站点是基于微信的，由于微信的各种坑，这里有很多值得注意的地方。</p><p>首先，需要在公众号设置中，将新域名加入到业务域名以及 JS 接口安全域名中，在微信支付的开发配置中，也要将新域名加入支付授权目录中。这几个比较容易，因为他们都支持配置多个域名。</p><p>我们的页面加载之后会立即通过静默授权跳转去拿用户的 code 以换取 openid，来实现自动登录，为了减少跳转，我们在微信公众号的自定义菜单中配置的链接就是微信的授权链接
<code>https://open.weixin.qq.com/connect/oauth2/authorize?XXXXX&redirect_uri=h.xxx-old.com</code>
这里比较坑的是，授权回调页面域名只能配置一个，而且自定义菜单的修改最慢要 24 小时才能生效，而且你没法确定是什么时候生效，有的用户会生效，有的用户仍是旧的链接。所以这里的链接不能冒然改成新链接。不过我们可以这么实现。</p><h4 id="解决方案">解决方案</h4><ol><li>修改公众号自定义菜单中配置的链接，直接改为原域名<code>http://h.xxx-old.com</code>，在页面代码中做判断，如果没有拿到 code 参数，就主动跳转到微信的授权页面去拿 code（这个原来就做了，为了让用户直接访问域名的时候也能拿到 code）。这个生效可能需要 24 小时，稳妥的做法就是等 24 小时之后再进行后面的操作。</li><li>将代码中跳转到微信授权页面的 redirect_uri 改为新域名：
<code>https://open.weixin.qq.com/connect/oauth2/authorize?XXXXX&redirect_uri=h.xxx-new.com</code>
同时将微信公众号中的授权回调页面域名改为新域名（这个是立即生效的）。这时，无论是从旧域名访问还是从新域名访问，授权回调的时候，都会成功跳转到新域名，并且带上 code 了。</li><li>修改公众号中的链接，改为微信授权链接，并且 redirect_uri 写成新域名：
<code>https://open.weixin.qq.com/connect/oauth2/authorize?XXXXX&redirect_uri=h.xxx-new.com</code>
这个时候，无论是更新后的链接还是尚未更新的链接，都能成功授权，只是直接用域名会多跳转一下而已。</li></ol><p>Done.</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[The End of 2015]]></title>
        <id>/posts/2015/the-end-of-2015</id>
        <link href="https://hadb.me/posts/2015/the-end-of-2015"/>
        <updated>2015-12-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[不知不觉，2015 年就快到尽头了。]]></summary>
        <content type="html"><![CDATA[<p>不知不觉，2015 年就快到尽头了。</p><p>这一年，经历了好多事。</p><p>自从 5 月 20 号离开新蛋之后，就开始了无休止的加班生活，仅有的一点周末，也被之前接的一些外包所占据，没有闲暇做自己的事，没有闲暇更新博客，总共只写了两篇文章，还都是在上班时间抽空写的。</p><p>这半年，论成长倒也成长了不少。技术上，在 H5 方面也积累了自己的一套框架，在和 Native App 甚至微信上的交互上实现了大一统，可以做到一个页面，同时给 iOS、Android 和微信 H5 使用，并可进行双向数据交互。职场上，也接触了更多的人和事，感触很多。</p><p>做了大半年的前端，等级也晋升为高级前端开发，但做着做着，也开始有点迷茫，不太看得清职业的发展方向。优秀的前端工程师很少，我感觉有这么几个原因：</p><ul><li>一是，做前端需要一定的设计基础，起码得有基础的美感，知道页面长什么样好看，知道主流的设计风格，甚至还得会 PS，切图如果都不会，都不好意思说自己是做前端的。目前市面上不少前端的内容是一些以做后端为主的人顺带做的，能实现主要功能，但是在设计上或者一些更复杂的效果和功能上，就略显不足了。</li><li>二是，做一个优秀的前端，代码基础也得扎实。之前面了一个 CSS 基础很好的哥们，人也很踏实很好学，就是没有 js 基础，只会做页面写样式。本想慢慢带他，教他 js，是个不错的做前端的料子。招进来做了一两个月，无奈到最后还是被老大给劝退了。他是一开始做设计的，后面一直只写页面，写样式，和后端交互的那些 js 都是由后端的人写的，也导致他一直以来都以为前端只要写页面和样式就可以了。这样的前端，在要求比较高的公司就比较难生存，个人的发展也比较有限。</li><li>三是，从职业发展角度上讲，做前端，发展的高度是限的。在很多公司，前端开发并不是一个受重视的角色。甚至很多公司前端就那么几个人，或者是把前端拆分到各个 Team，每个 Team 配备一两个前端。这样的情况下，前端的发展前景很受限制，很难有晋升的机会。而且和其他前端的交流也很少，成长也慢很多。试想，开发经理、研发总监、CTO 这些角色里，有多少是从前端做上去的？这也导致有不少优秀的前端转去做后端了。</li></ul><p>这半年，我一直被产品需求追着跑，一直忙于实现各种业务功能，少有时间静下心来认真思考。遇到的很多问题，虽然找到了方法去解决，并且也积累了所谓经验，但是并没有时间去研究他为何未产生，真正的原因是什么，很多地方都是一知半解。渐渐意识到，原来自己还有那么多不足的地方。不像以前，自信得爆棚。</p><p>心里一直盘算着试着做做 iOS，一直都没有闲暇真正去动手，年底谈了一个 APP 的活，明年终于可以开始干了！给自己一点压力，push 着自己前进。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Plan B]]></title>
        <id>/posts/2016/plan-b</id>
        <link href="https://hadb.me/posts/2016/plan-b"/>
        <updated>2016-03-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天从外面回来的路上，我在想，我的 Plan B 是什么。]]></summary>
        <content type="html"><![CDATA[<p>今天从外面回来的路上，我在想，我的 Plan B 是什么。</p><p>直到刚刚，看到阮一峰老师新文章《你的 B 计划在哪里？》，才发现，我之前所想的那些 Plan B 其实并不算 Plan B，而是基于 Plan A 的一些妥协与调整。真正的 Plan B 应该是那些实施起来非常艰苦，不到走投无路自己没办法下定决心去做的计划，但他往往是所有的 Plan A 都失败的情况下，能救命的计划。</p><p>前段时间很幸运接到了一个非常好的项目，APP 的外包开发，要求签完合同两个月内完成。这对我来说，是个挑战，也是个机遇。一直很想转型，做前端也挺久了，然而现在的我越来越发现，在公司的这些项目对我而言没有任何挑战性，纯粹是堆时间。而且业务变动及其不稳定，加上一直被需求赶着走，甚至没有时间停下来整顿代码、理清逻辑，导致目前代码中的业务逻辑错综复杂，特别多的坑，搞得我心力交瘁。</p><p>我之前是下定了决心，如果项目能谈下来，我可以从公司离职，全职去搞这个外包。一来，Money 肯定比工资多，二来也是个锻炼自己、强迫自己去学习与转型的机会，三来，自己可以找到更感兴趣的地方，或许可以找回曾经的那种感觉。项目完成之后，如果有新项目那就接着干，如果找不到新的项目，再找工作也不迟。这是我最初的 Plan A。</p><p>今天下午和益达见面，他希望我能够出来和他一起创业，有股份，但工资会比现在少很多。以他的项目以及他投资人的项目来看，其实并不会占用我太多的时间，在他们有需求的时候帮他们搞，在他们没需求的时候，我依旧可以做那个 APP 的外包。于我而言，虽然属于自己的时间少了，但是相比 Plan A，我多了一些保底的收入，计划的安全性上更高了一点，2 个月之后，即便没有新项目，没有去找新工作，也已然有保底工资，可以多一些缓冲的时间，多做一些选择和考虑。算是 Plan A 的一个比较中庸的变种。</p><p>然而，这些都是在那个 APP 项目能够谈拢的情况下的计划。如果那个 APP 的项目没能谈下来。那可就真的要考虑 Plan B 了。</p><p>留在原公司，是一个非常保守的方案，只是为了工资，没有自己的时间，没有乐趣，提升空间不大，前途渺茫。这是一个我很不情愿的 Plan B。</p><p>和益达一起创业，给他们提供一些技术输出的同时换来一个办公场所+保底工资，牺牲了一部分自己的时间，但是依旧有不少时间做自己喜欢的事，可以接一些外包在手上干。规避了接不到活，又没工资的风险。融到钱也能有不错的回报。这似乎是一个比较安全稳妥的 Plan B。</p><p>还有一个很激进的 Plan，我心里很想这么干，但是需要很大的勇气。那就是直接自己出来做，接外包干。完全属于自己的时间，挑自己感兴趣的项目做，没有保底工资。是对 Plan A 的颠覆。但是可以获得自由、兴趣、成就感，算是人生职业生涯的一个转型。从此不再需要被项目催着走，不再会觉得加班没有补贴很亏。完全自己支配时间，由兴趣拉动，自己主动前进。当然，外包项目在金钱上的回报也比工资要大得多。如果手头能够一直有项目做，那自然是最好的，人既自由了，快乐了，钱也多了。而风险也不言而喻，就是不够稳定，一旦没有项目做，在金钱上，就是损失。这是一个我最向往，然而却未必能够有勇气去做的 Plan B，但这会是我的目标。</p><p>之前对 react native 总是跃跃欲试，一直苦于没有时间和精力去真正静下心来开始学。这两天，花了很多时间去研究，总算真正开始了，也找到了久违的兴趣和好奇心。大爱 react native，有种相见恨晚的感觉。2016 年 react native 一定会成为焦点，越来越多的项目会尝试通过 react native 来实现。而我感觉很幸运，自己能够在这样的时间点，有这样的机会能够开始投身这个领域。</p><p>我想，是时候尝试一种新的生活了！</p><p>加油！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[个人小结]]></title>
        <id>/posts/2016/personal-summary</id>
        <link href="https://hadb.me/posts/2016/personal-summary"/>
        <updated>2016-06-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[岁月匆匆，看到阮老师的消息，看了下日期，才发现转眼间毕业已近两年。]]></summary>
        <content type="html"><![CDATA[<p>岁月匆匆，看到阮老师的消息，看了下日期，才发现转眼间毕业已近两年。</p><p>很小的时候起，创业的种子就在心底萌芽，自己都不敢相信，这么快，我就已经走上了创业的路。好久没有总结自己了，借这个机会总结总结。</p><p>对计算机的爱好，是我在小学的时候就开始了的。我曾经有个堂叔，他小时候有肾病，看了很多地方，用了很多激素，看了很多年，后来病情稳定了，整个人因为激素用得太多，变得非常非常胖。他因为一直要到外地去看病，所以上学大概上到初中还是高中，后来就不上了。但他人很聪明，家里给他买了台电脑，他就钻研电脑。我上小学一二年级时，就是在他的带领下，走进了计算机的世界。因为接触得早，在同龄人眼里，我的电脑水平一直是遥遥领先。小学三四年级的时候开始有微机课，但老师教的东西早已没法满足我。我依稀记得那时我打字比老师还快，老师经常让我去帮他打东西。用画图画画得了县里什么奖。后面老师还去县里考试，拿了个 Word 的什么证书，证书后来就一直放在小学的展览厨里展览，至今都没给我。后来大概小学六年级的时候，堂叔病情恶化成尿毒症了，一直等不到合适的肾源，后来就去世了。我还记得他说要教我编程呢，还没教我怎么就走了呢。</p><p>后来到了上了初中就开始参加信息学奥林匹克竞赛了，开始学编程，我想我一定要把堂叔没教我的东西都学会。一直到高三，参加了六年，一直也只是省三等奖，从来没有得过更好的名次。现在想想，那时我还是太依赖老师了，自从最厉害的奥赛老师被南京某学校挖走之后，我们学校的信息学奥赛感觉就不行了。很多题目感觉老师自己都不清楚，而且信息学竞赛相比数学竞赛、物理竞赛、化学竞赛、生物竞赛等，受重视程度远远不足。在我高中那个时代，计算机课都是可能会被数学课霸占的。毕竟是个副科。那时我已经有电脑了，初一的时候，在好几次考试得第一之后，家人终于给我买了个电脑。但是不让我上网，他们说怕我沉迷网络。那时家里人也都不懂，听到网络就色变，觉得那是一个坏东西。那是我的学习基本靠课本，靠老师，然而教程上也有不少错误，很多地方我自己都是一知半解，但也无能为力。那时我还订阅了《计算机爱好者》、《大众软件》等杂志，信息的获取基本是靠这些杂志。</p><p>高中的时候，我买了个打印机，开始做起了学习资料打印的生意。我当时沉迷于整理知识点，把一些要背的课文、知识点、公式啥的，都要用电脑打出来，看起来才爽，不喜欢手写。结果很多人都喜欢我打印的资料，让我帮他们打，给钱我。一开始只是在班级里面，后来整个年级都会有人问我买。我的知识点也扩展到各个学科。甚至有了自己的品牌：好易思特，自创了个词“HAOest”的谐音。期间自己摸索 Word 的排版、公式、PDF 缩印什么的，玩得飞起。帮老师修电脑也是家常便饭。这段时间，积累了大量的计算机方面的知识，各个方面都有涉猎。但我承认，我在这些“歪门邪道”上面花得时间和精力太多了，以至于耽误了成绩。从高一开始，成绩就开始下滑，到最后我已经发现，没时间了。我也是在高中这几年，开始厌倦了学习，从“好学生”变成了上课睡觉、抄作业的“坏学生”。高考并没有考出理想的成绩，语数外分数最后发挥得还不错，但是物理化学没有拿到 A，最终选择了上大。</p><p>到了大学，进了自强学院，发现，并不像高中老师说的那样，上了大学你就轻松了。从圈养一下子变成了散养，没人管了，我的精力大多数并没有放到课程的学习，参加各种社团、打游戏、谈恋爱……第一学期高数挂科，着实给了我不小的震惊。后面也渐渐发现，大学的学习压力并不小，而我也难以再像高中那样静下心来学习。上课睡觉、翘课、抄作业也已成了家常便饭。大学期间，我虽然花了很多时间在别的地方，但也找到了自己真正的兴趣所在，分流的时候我选择了自己最擅长的计算机。进了计算机学院之后，发现老师教的内容实在太过基础，很多选修课程里面所讲的东西在发展迅速的计算机领域，已经太过古老。于是我自学了许多东西，Android 开发、.net、html5、js、css3 等等，并且掌握了精湛的搜索技能。学会了借助网络去解决我遇到的问题。
大三暑假，新蛋校招，通过了面试，开始去新蛋实习。到了公司，发现学校所教的内容果然与公司所需相差甚远。在新蛋实习了近一年，毕业后又工作了一年，这两年之间学到了很多。在方向上也更加明确，走向了互联网开发的方向。在新蛋实习、工作的两年，参与了多个大项目的开发，工作认真、积极，得到了领导的器重，并在公司内部做了几次技术分享，最终成为了新蛋有史以来最年轻的年度优秀员工。</p><p>在新蛋呆了两年之后，我想看看外面的世界，于是开始在网上放简历。不久收到了几个公司的面试邀请，最终选择了携程，拿到了携程的 Offer，薪资也从 8K 涨到 13K。但是后来由原来在新蛋的一个主管介绍，没有去携程，去了一个创业公司，薪资直接在新蛋的基础上翻倍变成 16K。创业公司虽然钱多，但是加班非常频繁，几乎每天都要加班，加班到 12 点以后也是常有的事。渐渐的，我对这样的工作也开始厌倦了。</p><p>工作期间，利用休息时间接了不少外包项目，也积累了不少人脉和经验。今年年初自己注册了公司，上海猿奋网络科技有限公司，取名“猿奋”，是希望以后可以聚集一群有缘分的程序猿一起奋斗。在签下了一个五六万的 APP 开发项目之后，我觉得时机差不多成熟了，于是在今年 4 月辞职自己一个人出来创业了。专门为中小型企业提供技术外包服务，开发网站、微信商城、APP 等。收入远比打工多，工作起来也更有动力和激情。我相信之后还会有很多的挑战在等着我，希望我能开创一片属于自己的事业！</p><p>路漫漫其修远兮，吾将上下而求索！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Travis CI 自动部署 Hexo]]></title>
        <id>/posts/2016/hexo-with-travis-ci</id>
        <link href="https://hadb.me/posts/2016/hexo-with-travis-ci"/>
        <updated>2016-07-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[先占个坑，有空写教程。]]></summary>
        <content type="html"><![CDATA[<p>先占个坑，有空写教程。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[ASP.NET Core 初体验]]></title>
        <id>/posts/2016/asp-net-core-first-experience</id>
        <link href="https://hadb.me/posts/2016/asp-net-core-first-experience"/>
        <updated>2016-07-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[前两天试了下 ASP.NET Core MVC，很好用。微软整合了大量前端工具，npm、Bower 都可以很方便地使用了，甚至对 Grunt、Gulp 这类的工具都有集成一些任务管理器，这对前端来说，是一件鼓舞人心的事。]]></summary>
        <content type="html"><![CDATA[<p>前两天试了下 ASP.NET Core MVC，很好用。微软整合了大量前端工具，npm、Bower 都可以很方便地使用了，甚至对 Grunt、Gulp 这类的工具都有集成一些任务管理器，这对前端来说，是一件鼓舞人心的事。</p><p>ASP.NET Core MVC 的推荐目录结构也进行了调整，新增了<code>wwwroot</code>这样一个静态目录，js、css、图片都可以放这里面，而 Bower 管理的第三方前端库则会自动下载到<code>wwwroot</code>里面的 lib 目录下。作为强迫症的我，<code>wwwroot</code>这个目录必须全部是自动生成的。通过 Gulp，可以很轻松的实现这一点。继承原先的目录结构习惯，在解决方案下建立 Scripts、Styles、Images 文件夹，里面用来放原始的 js、less 和图片，然后通过 Gulp 进行合并、压缩、复制到<code>wwwroot</code>目录下，这样 wwwroot 这个目录就可以在 git 里面排除掉了。完美。而在 ASP.NET Core 的项目目录下默认的<code>.gitignore</code>文件里，微软其实是已经有这样的想法：</p><pre><code><span class="line" line="1"><span class="su5hD"># Uncomment if you have tasks that create the project's static files in wwwroot
</span></span><span class="line" line="2"><span class="su5hD">#wwwroot/
</span></span></code></pre><p>在代码层面，和之前差别不大，基本上 M、V、C 的代码都可以直接拿过来用。在 bundle 上有一些变化，然而我是直接删掉了默认的 bundle 配置，既然可以方便地使用 Gulp 了，为啥不用呢？</p><p>在 View 中，新增了<code>environment</code>的语法，可以通过<code>environment</code>标签来控制开发环境和生产环境的不同输出，主要是用来控制 css、js 这些文件的引用，在开发环境下使用未压缩的文件，在生产环境下使用压缩过的文件。还提供了 cdn 的方式，可以配置多个链接，优先使用 CDN 的链接，通过<code>asp-fallback-test</code>来检查 CDN 的链接是否可用，不可用的话再切换为本地的链接。和之前 Bundle 里面的方式差不多，只是使用起来更简单了。现在 css、js 这些文件的缓存控制也比以前更简单，只需要加上<code>asp-append-version="true"</code>即可在文件名后面自动加上版本号后缀。</p><p>项目部署上面，确实遇到了一个坑。我是通过 Web Deploy 来自动部署的，花了半天时间才终于搞定。</p><ol><li>IIS 里应用程序池中的<code>.NET CLR</code>版本要选择“无托管代码”</li><li>安装<code>.NET Core Windows Server Hosting</code>，这里有个大坑，安装完之后要执行一下<code>iisreset</code>，我没有执行这一步，导致出现了<code>HTTP Error 502.5 - Process Failure</code>的问题，从事件查看器里面看到的错误日志是：<code>Failed to start process with commandline '"dotnet" .\****.dll', ErrorCode = '0x80070002'.</code>。遇到同样问题的朋友可以试试<code>iisreset</code>或者重启机器。</li><li>安装<code>HttpPlatformHandler</code>。</li><li>还有个就是我当时用 Web Delpoy 往服务器部署的时候，文件总是推不上去，后来 Google 了一下，在 pubxml 里面加上了以下两行：</li></ol><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">AllowUntrustedCertificate</span><span class="sP7_E">></span><span class="su5hD">True</span><span class="sP7_E"></</span><span class="sQzsp">AllowUntrustedCertificate</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E"><</span><span class="sQzsp">UsePowerShell</span><span class="sP7_E">></span><span class="su5hD">False</span><span class="sP7_E"></</span><span class="sQzsp">UsePowerShell</span><span class="sP7_E">>
</span></span></code></pre><ol start="5"><li>下载<a href="https://github.com/aspnet/DataProtection/blob/dev/Provision-AutoGenKeys.ps1" rel="nofollow" title="Provisioning PowerShell script">Provisioning PowerShell script</a>，在服务器上使用<code>PowerShell</code>运行，输入应用程序池的名称即可。</li></ol>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[“Bad Request - Invalid Hostname” 的解决办法]]></title>
        <id>/posts/2016/bad-request-invalid-hostname</id>
        <link href="https://hadb.me/posts/2016/bad-request-invalid-hostname"/>
        <updated>2016-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近在做一个微信端的应用，除了在本地测试之外，有时候还需要在手机上进行测试。]]></summary>
        <content type="html"><![CDATA[<p>最近在做一个微信端的应用，除了在本地测试之外，有时候还需要在手机上进行测试。</p><p>假设我的手机和 PC 在同一内网内，PC 的 IP 是<code>192.168.1.2</code>，Website 的端口是<code>12345</code>。</p><p>我的第一反应是，我应该在手机上通过<code>http://192.168.1.2:12345</code>来访问我的站点。</p><p>然而，我得到了这样一个错误：</p><pre><code><span class="line" line="1"><span class="su5hD">Bad Request - Invalid Hostname
</span></span><span class="line" line="2"><span class="su5hD">------------------------------------------------
</span></span><span class="line" line="3"><span class="su5hD">HTTP </span><span class="s_sjI">Error</span><span class="s39Yj"> 400</span><span class="su5hD">. The request hostname is invalid.
</span></span></code></pre><p>方法很简单，Visual Studio 2015 的项目目录中会有一个<code>.vs</code>的文件夹，打开<code>.vs\config\applicationhost.config</code>，找到目标站点的配置节点，例如：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">site</span><span class="s9AJx"> name</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">Demo.Website</span><span class="sjJ54">"</span><span class="s9AJx"> id</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">2</span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">application</span><span class="s9AJx"> path</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">/</span><span class="sjJ54">"</span><span class="s9AJx"> applicationPool</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">Clr4IntegratedAppPool</span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">        <</span><span class="sQzsp">virtualDirectory</span><span class="s9AJx"> path</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">/</span><span class="sjJ54">"</span><span class="s9AJx"> physicalPath</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">D:\Projects\Demo\Demo.Website</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="4"><span class="sP7_E">    </</span><span class="sQzsp">application</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">    <</span><span class="sQzsp">bindings</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E">        <</span><span class="sQzsp">binding</span><span class="s9AJx"> protocol</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">http</span><span class="sjJ54">"</span><span class="s9AJx"> bindingInformation</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">*:52945:localhost</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="7"><span class="sP7_E">        <</span><span class="sQzsp">binding</span><span class="s9AJx"> protocol</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">http</span><span class="sjJ54">"</span><span class="s9AJx"> bindingInformation</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">*:52945:*</span><span class="sjJ54">"</span><span class="sP7_E"> /></span><span class="sutJx"> <!-- 加上这一行 -->
</span></span><span class="line" line="8"><span class="sP7_E">    </</span><span class="sQzsp">bindings</span><span class="sP7_E">>
</span></span><span class="line" line="9"><span class="sP7_E"></</span><span class="sQzsp">site</span><span class="sP7_E">>
</span></span></code></pre><p>网上大部分教程基本就说了这么多，然而我在这样配置了之后依然有问题。甚至有些教程是直接把<code>localhost</code>改成了<code>*</code>，也可以理解。然而，我每次这么做之后，重新启动站点的时候，总是会自动重新生成一个节点，那个里面配置的是<code>localhost</code>。纠结了一下午。</p><p>最后发现是权限的问题，如果想配置非<code>localhost</code>的绑定，VS 必须以<strong>管理员权限</strong>运行才行。这样如果只配置了非<code>localhost</code>的绑定，就不会新建了，或者配置多条绑定也可以生效了。</p><p>遇到这个问题一直解决不了的朋友可以参考下。</p><p>另外如果站点甚至无法访问的话，可以新建一条防火墙入站规则，把端口号配置进去。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Visual Studio Web Deploy 发布 ASP.NET Core 至 IIS]]></title>
        <id>/posts/2016/publishing-asp-net-core-to-iis-with-web-deploy-using-visual-studio</id>
        <link href="https://hadb.me/posts/2016/publishing-asp-net-core-to-iis-with-web-deploy-using-visual-studio"/>
        <updated>2016-10-17T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h4 id="操作系统要求">操作系统要求</h4><ul><li>Windows 7 及以上</li><li>Windows Server 2008 R2 及以上</li></ul><h4 id="iis-配置">IIS 配置</h4><p>在服务器管理器中，通过添加角色和功能的向导，在服务器角色中勾选 Web 服务器（IIS），并安装。</p><h4 id="安装net-core-windows-server-hosting-包">安装.NET Core Windows Server Hosting 包</h4><ol><li>安装<a href="https://go.microsoft.com/fwlink/?LinkID=827547" rel="nofollow">.NET Core Windows Server Hosting</a>，这个包会安装.NET Core 运行时、.NET Core 库和 ASP.NET Core 模块，这个模块会在 IIS 和 Kestrel 服务器之间创建反向代理。</li><li>重启服务器，或者从命令行执行<code>net stop was /y</code>，接着执行<code>net start w3svc</code>。</li></ol><p>更多关于 ASP.NET Core 模块以及针对该模块的配置、web.config 中系统变量的设置、app_ofline.htm 的使用、模块日志的激活等，请参阅<a href="https://docs.asp.net/en/latest/hosting/aspnet-core-module.html" rel="nofollow">ASP.NET Core Module Configuration Reference</a>。</p><h4 id="应用程序配置">应用程序配置</h4><p>添加对<code>Microsoft.AspNetCore.Server.IISIntegration</code>包的依赖，添加<code>.UseIISIntegration()</code>到<code>WebHostBuilder()</code>中以引入 IIS 集成中间件。</p><pre><code><span class="line" line="1"><span class="sG8yY">var</span><span class="sbgvK"> host</span><span class="smGrS"> =</span><span class="smGrS"> new</span><span class="sbgvK"> WebHostBuilder</span><span class="sP7_E">()
</span></span><span class="line" line="2"><span class="sP7_E">  .</span><span class="sGLFI">UseKestrel</span><span class="sP7_E">()
</span></span><span class="line" line="3"><span class="sP7_E">  .</span><span class="sGLFI">UseContentRoot</span><span class="sP7_E">(</span><span class="su5hD">Directory</span><span class="sP7_E">.</span><span class="sGLFI">GetCurrentDirectory</span><span class="sP7_E">())
</span></span><span class="line" line="4"><span class="sP7_E">  .</span><span class="sGLFI">UseIISIntegration</span><span class="sP7_E">()
</span></span><span class="line" line="5"><span class="sP7_E">  .</span><span class="sGLFI">UseStartup</span><span class="sP7_E"><</span><span class="sbgvK">Startup</span><span class="sP7_E">>()
</span></span><span class="line" line="6"><span class="sP7_E">  .</span><span class="sGLFI">Build</span><span class="sP7_E">();
</span></span></code></pre><p>需要指出的是，添加<code>.UseIISIntegration()</code>并不会影响代码的可移植性。</p><h4 id="为-iisintegration-服务设置-iisoptions">为 IISIntegration 服务设置 IISOptions</h4><p>为了配置 IISIntegration 服务，需要在 ConfigureServices 中为 IISOptions 添加服务器配置。</p><pre><code><span class="line" line="1"><span class="su5hD">services</span><span class="sP7_E">.</span><span class="sGLFI">Configure</span><span class="sP7_E"><</span><span class="sbgvK">IISOptions</span><span class="sP7_E">>(</span><span class="sbgvK">options</span><span class="smGrS"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="2"><span class="smGrS">  ..</span><span class="su5hD">.
</span></span><span class="line" line="3"><span class="sP7_E">});
</span></span></code></pre><table><thead><tr><th>Option</th><th>Setting</th></tr></thead><tbody><tr><td>AutomaticAuthentication</td><td>If true, the authentication middleware will alter the request user arriving and respond to generic challenges. If false, the authentication middleware will only provide identity and respond to challenges when explicitly indicated by the AuthenticationScheme.</td></tr><tr><td>ForwardClientCertificate</td><td>If true and the MS-ASPNETCORE-CLIENTCERT request header is present, the ITLSConnectionFeature will be populated.</td></tr><tr><td>ForwardWindowsAuthentication</td><td>If true, authentication middleware will attempt to authenticate using platform handler windows authentication. If false, authentication middleware won’t be added.</td></tr></tbody></table><h4 id="publish-iis-工具">publish-iis 工具</h4><p>The publish-iis tool can be added to any .NET Core application and will configure the ASP.NET Core Module by creating or modifying the web.config file. The tool runs after publishing with the dotnet publish command or publishing with Visual Studio and will configure the processPath and arguments for you. If you’re publishing a web.config file by including the file in your project and listing the file in the publishOptions section of project.json, the tool will not modify other IIS settings you have included in the file.</p><p>To include the publish-iis tool in your application, add entries to the tools and scripts sections of project.json.</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">tools</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">Microsoft.AspNetCore.Server.IISIntegration.Tools</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">1.0.0-preview2-final</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  },
</span></span><span class="line" line="5"><span class="s39Yj">  "</span><span class="sseR_">scripts</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="6"><span class="s39Yj">    "</span><span class="sZMiF">postpublish</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%</span><span class="sjJ54">"
</span></span><span class="line" line="7"><span class="sP7_E">  }
</span></span><span class="line" line="8"><span class="sP7_E">}
</span></span></code></pre><h4 id="web-deploy-配置">Web Deploy 配置</h4><p>第一步需要确保你的服务器支持 ASP.NET Core，必要的条件是：</p><ol><li>安装了 IIS 7.5+</li><li>安装了 HttpPlatformHandler</li><li>安装了 Web Deploy v3.6</li></ol><p>HttpPlatformHandler 是一个新的组件，用来连接 IIS 和 ASP.NET Core 应用程序，下载链接如下：</p><ul><li><a href="http://go.microsoft.com/fwlink/?LinkID=690721" rel="nofollow">64 位下载地址</a></li><li><a href="http://go.microsoft.com/fwlink/?LinkId=690722" rel="nofollow">32 位下载地址</a></li></ul><p>安装 HttpPlatformHandler 之前，需要先安装 Web Deploy v3.6，可以通过<a href="https://www.microsoft.com/web/downloads/platform.aspx" rel="nofollow">Web Platform Installer</a>（WebPI），或者直接从<a href="https://www.microsoft.com/en-us/download/details.aspx?id=43717" rel="nofollow">下载中心</a>下载，不过推荐通过 WebPI 的方式下载，它提供了独立的安装并且包含了一些必要的配置。</p><h4 id="在-iis-中配置站点">在 IIS 中配置站点</h4><ol><li>在 IIS 管理器中新建一个网站，输入网站名、物理地址以及域名绑定的配置。</li><li>设置应用程序池的.NET CLR 版本为无托管代码。</li><li>右键网站->部署->启用 Web Deploy 发布...，会在桌面生成一个.PublishSettings 后缀的配置文件，复制出来，后续操作中需要导入到 Visual Studio 中。</li></ol><h4 id="配置数据保护">配置数据保护</h4><p>为了持久化数据保护的密钥，你必须为每个应用程序池创建注册表存储单元来存储这些密钥。需要为每个 ASP.NET Core 应用程序池执行这个 PowerShell 脚本<a href="https://github.com/aspnet/DataProtection/blob/dev/Provision-AutoGenKeys.ps1" rel="nofollow">Provisioning PowerShell</a>。</p><p>For web farm scenarios developers can configure their applications to use a UNC path to store the data protection key ring. By default this does not encrypt the key ring. You can deploy an x509 certificate to each machine and use that to encrypt the keyring. See the <a href="https://docs.asp.net/en/latest/security/data-protection/configuration/overview.html#data-protection-configuring" rel="nofollow">configuration APIs</a> for more details.</p><p>Warning: Data Protection is used by various ASP.NET middlewares, including those used in authentication. Even if you do not specifically call any Data Protection APIs from your own code you should configure Data Protection with the deployment script or in your own code. If you do not configure data protection when using IIS by default the keys will be held in memory and discarded when your application closes or restarts. This will then, for example, invalidate any cookies written by the cookie authentication and users will have to login again.</p><p>关于配置 IIS，可以前往<a href="https://docs.asp.net/en/latest/publishing/iis.html" rel="nofollow">Publishing to IIS</a>查看更多详情。下面我们来看看 Visual Studio 中的步骤。</p><h4 id="通过-visual-studio-发布">通过 Visual Studio 发布</h4><p>在配置好服务器之后，下一步就是在 Visual Studio 中创建一个发布配置文件。将 ASP.NET Core 应用程序发布到标准的 IIS 服务器上最简单的方法就是使用发布配置文件。如果你的服务器支持创建发布配置文件，可以下载过来，然后在 Visual Studio 发布对话框中导入进来。</p><p>如果发现使用 Web Deploy 无法部署，可能是由于数据保护没有配置好，也可以不配置，在 pubxml 中添加如下两行：</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">AllowUntrustedCertificate</span><span class="sP7_E">></span><span class="su5hD">True</span><span class="sP7_E"></</span><span class="sQzsp">AllowUntrustedCertificate</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E"><</span><span class="sQzsp">UsePowerShell</span><span class="sP7_E">></span><span class="su5hD">False</span><span class="sP7_E"></</span><span class="sQzsp">UsePowerShell</span><span class="sP7_E">>
</span></span></code></pre><h4 id="参考链接">参考链接</h4><ul><li><a href="https://docs.asp.net/en/latest/publishing/iis.html" rel="nofollow">https://docs.asp.net/en/latest/publishing/iis.html</a></li><li><a href="https://docs.asp.net/en/latest/publishing/iis-with-msdeploy.html" rel="nofollow">https://docs.asp.net/en/latest/publishing/iis-with-msdeploy.html</a></li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Windoows 下 Redis Sentinel 的部署]]></title>
        <id>/posts/2016/redis-windows-sentinel</id>
        <link href="https://hadb.me/posts/2016/redis-windows-sentinel"/>
        <updated>2016-10-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[虽然很久之前就了解了 Redis 的哨兵机制，今天第一次尝试在多个服务器上部署多个 Redis 实例，并且设置了哨兵用来进行自动的主从切换。]]></summary>
        <content type="html"><![CDATA[<p>虽然很久之前就了解了 Redis 的哨兵机制，今天第一次尝试在多个服务器上部署多个 Redis 实例，并且设置了哨兵用来进行自动的主从切换。</p><h4 id="一部署-redis">一、部署 Redis</h4><p>在 3 台服务器上分别安装了 Redis，Redis on Windows 下载地址：<a href="https://github.com/MSOpenTech/redis/releases" rel="nofollow">https://github.com/MSOpenTech/redis/releases</a>。</p><p>配置文件添加密码：</p><pre><code>requirepass <密码>
masterauth <密码>
</code></pre><p>除了设置本实例的密码外，还需要输入 master 的密码（需要和本实例密码相同），所有实例需要设置相同的密码，以便进行主从切换。</p><p>需要注意的是，Redis 从某个版本起，加入了一个<code>protected-mode</code>的保护模式。启动保护模式的条件是<code>protected-mode</code>开启，且没有设置<code>bind</code>，且没有设置密码。我的 Redis 的实例部署在多个公网服务器下，所以加密码是必须的，另外需要注释掉默认的<code>bind 127.0.0.1</code>，以使用公网 IP。因为设置了密码，所以<code>protected-mode</code>就无需进行改动，直接使用默认的就可以了。但是在后面哨兵的配置中的保护模式会有一个坑。</p><p>在 3 个 Redis 实例中挑选一个作为初始的 master。在另外两个实例的配置文件中，加入<code>slaveof</code>的配置。</p><h4 id="二配置-sentinel">二、配置 Sentinel</h4><p>创建哨兵的配置文件，内容如下：</p><pre><code>port <端口号>

sentinel monitor redis-master <Master IP> <Master端口号> 2

sentinel down-after-milliseconds redis-master 5000

sentinel failover-timeout redis-master 900000

sentinel parallel-syncs redis-master 2

sentinel auth-pass redis-master <密码>

logfile "LogFiles/monkeyrun-sentinel.log"

protected-mode no
</code></pre><p>具体参数的含义不再赘述，可以 Google。</p><p>这里需要加上最后一行<code>protected-mode no</code>，把哨兵的保护模式关掉。因为哨兵目前不支持设置密码，如果不加这行就会启动保护模式了，外网无法访问，会出现哨兵与哨兵之间互相认为对方不可用的情况。</p><h4 id="三一些命令">三、一些命令</h4><p>因为是在 Windows 下，有些命令可以通过批处理文件方便处理。将如下代码分别保存为.bat 文件，可以直接双击执行。默认安装完会新建一个名叫 Redis 的服务，我不喜欢这个名字，可以先卸载这个默认的 Redis 服务，然后重新安装自己命名的服务。这样的好处是以后可以在一台服务器上安装多个不同用途的 Redis 实例，以便区分。</p><p>1、安装 Redis 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-install</span><span class="s_sjI"> redis-service-monkey-run.conf</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-service-monkey-run
</span></span></code></pre><p>2、启动 Redis 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-start</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-service-monkey-run
</span></span></code></pre><p>3、停止 Redis 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-stop</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-service-monkey-run
</span></span></code></pre><p>4、卸载 Redis 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-stop</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-service-monkey-run
</span></span><span class="line" line="2"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-uninstall</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-service-monkey-run
</span></span></code></pre><p>5、安装 Sentinel 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-install</span><span class="s_sjI"> sentinel-monkey-run.conf</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-sentinel-service-monkey-run</span><span class="stzsN"> --sentinel
</span></span></code></pre><p>6、启动 Sentinel 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-start</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-sentinel-service-monkey-run
</span></span></code></pre><p>7、停止 Sentinel 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-stop</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-sentinel-service-monkey-run
</span></span></code></pre><p>8、卸载 Sentinel 服务</p><pre><code><span class="line" line="1"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-stop</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-sentinel-service-monkey-run
</span></span><span class="line" line="2"><span class="sbgvK">redis-server</span><span class="stzsN"> --service-uninstall</span><span class="stzsN"> --service-name</span><span class="s_sjI"> redis-sentinel-service-monkey-run
</span></span></code></pre><h4 id="四其他">四、其他</h4><p>验证了一下哨兵的主从切换，很爽！</p><p>睡觉！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[更新 Microsoft.NETCore.App 到 1.0.1 出现 502.5 错误的问题]]></title>
        <id>/posts/2016/upgrade-to-net-core-app-1-0-1-problem</id>
        <link href="https://hadb.me/posts/2016/upgrade-to-net-core-app-1-0-1-problem"/>
        <updated>2016-10-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天白天遇到一个问题，花了很长时间才解决。记录下。]]></summary>
        <content type="html"><![CDATA[<p>今天白天遇到一个问题，花了很长时间才解决。记录下。</p><p>问题是这样的，我是个强迫症，如果发现有可以更新的包，我肯定会去更新。</p><p>新建了一个 ASP.NET Core 的 Api 项目，发现有包可以更新，于是通过 Nuget 自动更新。更新完之后，出现第一个坑。</p><p>原先的</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">Microsoft.NETCore.App</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">version</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">1.0.0</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="4"><span class="s39Yj">    "</span><span class="sZMiF">type</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">platform</span><span class="sjJ54">"
</span></span><span class="line" line="5"><span class="sP7_E">  }
</span></span><span class="line" line="6"><span class="sP7_E">}
</span></span></code></pre><p>更新之后会丢失 <code>"type": "platform"</code>，变成</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">Microsoft.NETCore.App</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">1.0.1</span><span class="sjJ54">"
</span></span><span class="line" line="3"><span class="sP7_E">}
</span></span></code></pre><p>直接编译都会报错。这个好解决，自己手动改下。把 <code>version</code> 和 <code>type</code> 加上。</p><p>改好之后，编译不报错了，但是在 iis express 上运行的时候，会出现 502.5 的错误，百思不得其解。Google 了很长时间也没找到解决方案。后来猛然在 <a href="https://www.microsoft.com/net/core#windows" rel="nofollow">.NET Core 首页</a> 的 Install .NET Core SDK 中看到一个 <a href="https://go.microsoft.com/fwlink/?LinkID=827546" rel="nofollow">.NET Core 1.0.1 - VS 2015 Tooling Preview 2</a>，突然感觉是不是还得安装下这个更新才能用.NET Core 1.0.1，于是下载更新，问题解决。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[解决阿里云 CDN 回源 https 返回 503 错误的问题]]></title>
        <id>/posts/2016/aliyun-cdn-not-support-sni</id>
        <link href="https://hadb.me/posts/2016/aliyun-cdn-not-support-sni"/>
        <updated>2016-11-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近打算把www.monkeyrun.net改成全站 https，使用的Let’s Encrypt的证书。然而在设置阿里云 CDN 的时候，阿里云 CDN 回源一直返回 503 错误，发工单，来来回回经过整整两天，终于把问题解决。容我娓娓道来。]]></summary>
        <content type="html"><![CDATA[<p>最近打算把<a href="https://www.monkeyrun.net" rel="nofollow">www.monkeyrun.net</a>改成全站 https，使用的<a href="https://letsencrypt.org/" rel="nofollow">Let’s Encrypt</a>的证书。然而在设置阿里云 CDN 的时候，阿里云 CDN 回源一直返回 503 错误，发工单，来来回回经过整整两天，终于把问题解决。容我娓娓道来。</p><p>最一开始，我先开启了阿里云的 CDN，源站设置为<a href="https://www.monkeyrun.net" rel="nofollow">www.monkeyrun.net</a>，通过 80 端口回源，没有任何问题。</p><p>后来当时配置好证书，站点也开启了 https 之后，将回源端口改为 443，开始出问题了，CDN 资源全部返回 503。而直接通过浏览器访问 https 的源站内容，都是没有问题的。</p><p>发工单，经过漫长的等待和提供链接等更详细的信息之后，阿里云的工作人员首先认为这个问题可能是由于我开启了防火墙或者一些安全软件导致，拦截或阻止了 CDN 节点的回源请求。我关闭了防火墙，问题依旧存在。</p><p>又经过漫长的等待以及转交专项处理人员处理之后，给我发了个抓的包，说是 CDN 回源请求被源站给 RST 了，让我检查我的服务器在网络层面是不是做了什么限制。看了半天抓包的数据，也不大看得懂，各种谷歌，最后感觉可能是协议不同，握手的时候有一个是 TLS 1.0，有一个是 TLS 1.2，谷歌了一通，被带入了另一个未知领域，尝试了各种 cipher suites，随后还是无果。</p><p>后来找到一个网站，测试 SSL 兼容性的，<a href="https://www.ssllabs.com/ssltest/" rel="nofollow">https://www.ssllabs.com/ssltest/</a>，测试了一下网站 SSL 兼容性，发现不支持 SNI 的请求会直接 close connection。于是又问阿里工作人员，得知他们 CDN 回源时，SSL 握手不支持发送 SNI。</p><p>定位到问题了，在 IIS 站点里面，编辑网站绑定，取消勾选“需要服务器名称指示”，问题解决！</p><p>可以愉快的开启全站 https 了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[自建 NAS 及 DDNS]]></title>
        <id>/posts/2016/nas</id>
        <link href="https://hadb.me/posts/2016/nas"/>
        <updated>2016-11-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[众所周知的原因，前段时间 360 云盘也倒下了，之前大部分照片、电影资源都放在 360 云盘上。由于国内的环境，感觉第三方云盘的可靠程度还不如自己建个 NAS。前端时间研究了硬件方案，今天研究了下外网访问的方案。]]></summary>
        <content type="html"><![CDATA[<p>众所周知的原因，前段时间 360 云盘也倒下了，之前大部分照片、电影资源都放在 360 云盘上。由于国内的环境，感觉第三方云盘的可靠程度还不如自己建个 NAS。前端时间研究了硬件方案，今天研究了下外网访问的方案。</p><p>固定 IP 肯定是拉不起，太贵了，国内运营商太黑心。只能通过 DDNS，但花生壳这种我也不想用，以前试用过，速度太慢。既然是程序猿，还是自己来吧。具体方案如下：</p><ol><li>阿里云的云解析 DNS，升级付费版，将最低 TTL 值拉到 1 秒，其余都拉成最低配置，一年 40.8 块钱，完全可以接受。</li><li>在自己的阿里云服务器上搭建一个小站点，用于返回来访请求的公网 IP 地址。没有外网服务器的，可以利用 ip138 的服务来做，<a href="http://city.ip138.com/ip2city.asp" rel="nofollow">http://city.ip138.com/ip2city.asp</a>。</li><li>做一个小应用，跑在 NAS 上，每秒向步骤 2 中的站点请求获取 NAS 的外网 IP，并通过阿里云云解析 DNS 的 api 接口，更新域名的 IP 地址，并记录，如果下次请求 IP 不变则跳过，IP 变化了则更新。做好日志，运行一段时间之后看下电信的动态 ip 更换有没有规律，可以适当调整获取外网 IP 的频率。</li></ol>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Entity Framework Core SQLite provider 向已存在的表中添加外键]]></title>
        <id>/posts/2016/asp-net-core-ef-sqlite-add-foreign-key-to-exist-table</id>
        <link href="https://hadb.me/posts/2016/asp-net-core-ef-sqlite-add-foreign-key-to-exist-table"/>
        <updated>2016-12-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[SQLite 本身不支持向已创建的表中添加外键，类似的限制还有很多，比较蛋疼，具体可以参见SQLite Limitations。]]></summary>
        <content type="html"><![CDATA[<p>SQLite 本身不支持向已创建的表中添加外键，类似的限制还有很多，比较蛋疼，具体可以参见<a href="https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations" rel="nofollow">SQLite Limitations</a>。</p><p>项目中，如果是测试的时候，数据不是很重要的话，最方便的方法就是把已经创建的 Migrations 包括 ModelSnapshot 都删掉，重新 Add-Migration 重建数据库。</p><p>对于已经发布的应用，数据库不能删了创建的话，可以“曲线救国”。</p><p>假设需要给 TableA 添加一个需要建立外键的字段 ColumnA，为了增加难度，假设 TableB 中的 Column</p><p>B 是 TableA 的外键。具体操作方法如下：</p><ol><li>先在代码中 TableA 里添加 ColumnA（不设置外键），Add-Migration，更新到线上数据库</li><li>将本地的数据库改名为 database-backup，删除项目中所有 Migrations 和 ModelSnapshot，创建一个 RebuildDatabase 的 Migration，创建全新的数据库，从新数据库中复制 TableA 的 Create Statement SQL 语句并将改 SQL 语句中的表名改为 TableA-New</li><li>在线上数据库中执行步骤 2 中的 SQL 语句，将创建 TableA-New（已经含有外键约束了）</li><li>导出线上数据库中 TableA 中的数据到 SQL 文件中，并将该 SQL 文件中的表名改为 TableA-New</li><li>将步骤 4 中的 SQL 文件的数据导入到线上数据库中</li><li>将线上数据库的 TableA 改名为 TableA-Old，将 TableA-New 改名为 TableA</li><li>因为重命名的关系，这时候 TableB 中的 ColumnB 是 TableA-Old 的外键，通过如下方法，将 TableB 中的外键约束改到 TableA 中：复制 TableB 的 Create Statement，创建一个 TableB-New，其中的 ColumnB 是 TableA 的外键，然后将 TableB 改名为 TableB-Old，并将 TableB-New 改名为 TableB（如果还有 TableC 中有 TableB-Old 的外键，通过同样的方法操作，以此类推），删除 TableB-Old。（如果没有这样的 TableB，则此步骤省略）</li><li>删除 TableAOld</li><li>清空线上数据库__EFMigrationHistory 表中的数据，并手动添加一条数据，以 RebuildDatabase 的 Migration 的文件名作为 MigrationId，并输入当前的 ProductVersion</li></ol><p>收工！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[小黑啊小黑 T.T]]></title>
        <id>/posts/2016/small-black-dog</id>
        <link href="https://hadb.me/posts/2016/small-black-dog"/>
        <updated>2016-12-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天爸爸从成都开车回老家了，连续开了 22 小时，1900 公里到老家了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2016/20161213.small-black-dog/cover.jpg" alt="封面" /><p>昨天爸爸从成都开车回老家了，连续开了 22 小时，1900 公里到老家了。</p><p>早上告诉我到家的消息，以及小黑被隔壁二爹爹送掉的消息。</p><p>T.T，可怜的小黑，被蒙着头扔到到邓庄的某个十字路口了，又要变成流浪狗了，想想就可怜。老婆早上听到这个消息都哭了出来。</p><p>小黑是我目前见到的占有欲最强也是最热情的狗了，可谓性情中狗。它的故事以后有空再详说。只能说生而为狗，而且出生就被抛弃成为流浪狗，是一件很不幸的事。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2016 年终总结]]></title>
        <id>/posts/2016/annual-review-2016</id>
        <link href="https://hadb.me/posts/2016/annual-review-2016"/>
        <updated>2016-12-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[2016 年过去了，丰富多彩的一年，这一年，有很多第一次。]]></summary>
        <content type="html"><![CDATA[<p>2016 年过去了，丰富多彩的一年，这一年，有很多第一次。</p><ul><li>3 月：第一次拿到营业执照，成为法人。</li><li>4 月：和老婆去毛里求斯度蜜月，第一次去海岛，第一次下海游泳，第一次尝到海水，第一次海底漫步，第一次……毛里求斯有太多的第一次，仿佛一切还在眼前。度完蜜月，从公司离职，开始了自己的第一次创业。</li><li>5 月：第一次洗牙，并发现了一个龋齿。第一次买 Nike 的帽子。</li><li>6 月：第一次补牙。第一次做铜锣烧。第一次将一个项目的 APP 发布到 AppStore。同时，因为久久没有拍到沪牌，而车子太久没保养，第一次坐 4S 店的免费拖车去保养……</li><li>7 月：第一次在老家见到小黑。</li><li>9 月：第一次接澳门的外包。</li><li>10 月：第一次安装车位锁。第一次买狗粮。</li><li>11 月：第一次与一个 30 万的大项目失之交臂。</li><li>12 月：第一次印名片。第一次拥有属于自己的 MacBook。第一次拥有一台 NAS。</li></ul><p>还有很多不值一提的第一次，就不提了。</p><p>2017 年，除了项目外，给自己定的目标：</p><ul className="contains-task-list"><li className="task-list-item"><input disabled type="checkbox"></input> 做一个自己的产品</li><li className="task-list-item"><input disabled type="checkbox"></input> 写 50 篇以上的博客</li><li className="task-list-item"><input disabled type="checkbox"></input> 每周跑步 10 公里</li><li className="task-list-item"><input disabled type="checkbox"></input> 整理以前 wordpress 站点里的内容，并关闭所有 wordpress 站点</li><li className="task-list-item"><input disabled type="checkbox"></input> 整理云盘里的所有文档，并归类，保存到 NAS</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 1 of 2017]]></title>
        <id>/posts/2017/week-1-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-1-of-2017"/>
        <updated>2017-01-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今年要定一个小目标，每周一篇周记。]]></summary>
        <content type="html"><![CDATA[<p>今年要定一个小目标，每周一篇周记。</p><p>刚开始用 Bear，感觉很爽，终于有了一种可以自由书写的感觉。😀</p><p>外面淅沥的雨声，老婆熟睡的呼吸声，键盘敲打的滴滴声，交错在一起，像兴奋剂一般刺激着我的大脑。让我有种想多写点东西的欲望。</p><p>想起那句“我思故我在”。高中毕业以来，就没怎么好好写过东西了。感觉时光飞逝，甚至只能从照片里找到过往的我，也仅仅是我正在做的事以及从神情中回忆我当时的心情。但，通过照片和视频，只能回忆起表面的东西，却没有我的思考，没有精神层面的东西留下来。以至于若干年后，随着岁月的流逝，记忆的减淡，当我再次翻出那些照片的时候，或许我再也没法真正体会到我当时的心情。还是有必要写点文字，即便没有任何营养，至少多年之后，通过这些文字，可以更清晰地勾勒出彼时的我。</p><p>2017 的第一周，写写去新公司的事。2017，离开了安乐窝，在 Mike 的忽悠之下，重新回到了工作中。这一周，说句实话，让我很有压力。进入了自己的未知领域。一直在用着.net，也一直跟随者微软的脚步，.net core 也玩得很溜。一下子要去搞 node、vue、vue-ssr、webpack 这些从未接触的内容，脑子真有点来不及适应。代码看得云里雾里。也很难静下心来去好好研究。有时候一天就那么过去了，而感觉自己并没有进步多少。回想这一两年的我，有点过于专注自己熟悉的技术，而很少去探索未知领域，很少去尝试使用新东西。例如 Angular、Vue、React 等，有时候看不进去，就退缩了。自己给自己找一些借口，不愿意去认真思考，静下心来去学。今年去新公司，也算是给自己的一个挑战，希望能够通过这次机会，逼迫自己去学点新东西，不至于被时代淘汰。</p><p>2017 年，有了自己的 Macbook，要好好利用起来。写代码只是一个方面，更多地我觉得可以利用一些零散时间，记录下自己的思考。</p><p>这周，将蔡益达那里的一万块钱拿回来了，代持协议也还给了他。至少这些日记暂时一段时间不会公开，所以我写写自己真实的想法。2017 年，我应该不会有太多的时间为他搞弯播吧的事，至少不愿意免费或低价搞了。16 年吃了太多亏。17 年宁愿少赚钱，也不想把自己的精力浪费在别的地方。拿自己的精力换钱这个事情，其实本身是很低级的行为。以后还是要多考虑自己的成长，以及时间成本。</p><p>2017 年，希望自己能够做出一个自己的产品，有点人气的产品，符合大众需求的东西，能盈利最好。</p><p>2017 年，要把身体搞好。和每周一篇日记一样，还要做到每周跑步 5 公里。这其实是一个很容易完成的任务，从坚持做起。</p><p>好了，就写这么多吧。快 4 点了，眼皮有点累了 😴。睡了。🌙</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 2 of 2017]]></title>
        <id>/posts/2017/week-2-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-2-of-2017"/>
        <updated>2017-01-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是周一，今天是 1 月 9 日，今天微信正式发布小程序。决定追一波小程序 + 春节的浪潮，搞一个小产品。]]></summary>
        <content type="html"><![CDATA[<p>今天是周一，今天是 1 月 9 日，今天微信正式发布小程序。决定追一波小程序 + 春节的浪潮，搞一个小产品。</p><p>今天是周四，这会儿在飞机 ✈️ 上。第一次公费坐飞机旅游，第一次在飞机上写东西，有点爽。</p><p>昆明旅游完就回老家了，今年的项目任务基本算告一段落，接下来这段时间里，要开始搞搞自己的东西，这周催下 Bon，看他们设计稿啥时候能搞出来。考虑到小程序推广的问题，感觉还得先搞个 H5 版的，有时间再搞小程序版的。回去研究一下小程序的开发问题，如果比较简单，也可以双管齐下。这次的产品要尽可能简单，使用方便，用完即走。</p><ol><li>微信登录，不搞任何用户资料啥的，就记录下每个用户的购买情况，以及所送祝福的浏览情况。</li><li>每套祝福模板保存一套，通过参数来显示，参数中带有发送者 ID、名字、收件人名字、祝福语。每位用户打开，记录数据，比如免费版只能送给 10 人祝福，10 人浏览后不能再使用。</li></ol><hr></hr><p>周四晚上，参加了公司年会，不愧是牛逼公司，1 等奖 5 个 iPhone7P，特等奖 1 个新版 MBPR。然后最后甘总还加了个特别超级大奖，个人出 50 万。😳 只能说牛逼！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 3 of 2017]]></title>
        <id>/posts/2017/week-3-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-3-of-2017"/>
        <updated>2017-01-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是周六，这会儿在回上海的大巴上，公司明天有云南的领导过来，开始实行考勤制度。所以明天开始接着上班，上到大年三十再回去。]]></summary>
        <content type="html"><![CDATA[<p>今天是周六，这会儿在回上海的大巴上，公司明天有云南的领导过来，开始实行考勤制度。所以明天开始接着上班，上到大年三十再回去。</p><p>这周过得有点快，主要都在老家了，说实话在老家真是不适合写代码。不过在家也不应该写代码。烧烧锅，吃吃饭，睡睡觉，看看电视，一天也就过去了，全家人一起，其乐融融，也是件很爽的事。</p><p>这次回上海，一个人，可以有时间做些自己的事。把拜年小程序搞出来。25 号小程序就暂停审核了，所以，最迟 23 号晚上要提交审核。</p><p>小程序我打算这么搞：</p><ol><li>21 号把后端做好，先搭个简单框架，返回指定 json 格式数据，包括模板的 id、名字、价格、封面 url、大图 url、是否购买、剩余发送次数等</li><li>22 号完成小程序免费模板发送功能，并提交审核</li><li>23 号有时间就完成收费版功能，没时间就继续完善免费版</li></ol>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 4 of 2017]]></title>
        <id>/posts/2017/week-4-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-4-of-2017"/>
        <updated>2017-01-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[春节假期]]></summary>
        <content type="html"><![CDATA[<p>春节假期</p><p>这周一直工作到腊月</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 5 of 2017]]></title>
        <id>/posts/2017/week-5-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-5-of-2017"/>
        <updated>2017-02-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是 2 月 4 日周六，天气阴。]]></summary>
        <content type="html"><![CDATA[<p>今天是 2 月 4 日周六，天气阴。</p><p>这周大部分在家以及走亲戚，时间过得飞快。</p><p>初五回的上海，昨天第一天上班，已经可以比较熟练地使用 Vuex 了，昨天写了一个轮播控件，感觉还是很爽的。</p><p>昨晚去买了个 Apple Watch。用起来很爽。</p><p>下周一申飞入职，之后有人帮忙了。😀</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 6 of 2017]]></title>
        <id>/posts/2017/week-6-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-6-of-2017"/>
        <updated>2017-02-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是 2 月 10 日，周五，天气晴。]]></summary>
        <content type="html"><![CDATA[<p>今天是 2 月 10 日，周五，天气晴。</p><p>这周做了不少管理上的工作，代码写得不多，做了一下 navbar 和 menu 的组件，vue 上面父子组件的通信，仍有些迷茫，方式比较多。另外 Atom 上 eslint 对 vue 的支持很不好，有点蛋疼。</p><p>这周尝试使用 GitBook 创建了一个前端文档，GitBook 用起来还是很爽的。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 7 of 2017]]></title>
        <id>/posts/2017/week-7-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-7-of-2017"/>
        <updated>2017-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是 2 月 17 日，周五，天气阴。]]></summary>
        <content type="html"><![CDATA[<p>今天是 2 月 17 日，周五，天气阴。</p><p>这周完成了用 Vue 封装 swiper，解决了支持 SSR 的问题。另外旅游官网做了一些管理的工作。</p><hr></hr><p>今天是 2 月 18 日，周六，天气晴。</p><p>北京时间 20 点 51 分。这会儿在医院陪老婆，老婆前天产检因为宫缩住进医院，在挂硫酸镁溶液抑制宫缩，流速很慢，一瓶水需要 10 个小时。</p><p>下周二去昆明，讨论 B2B 项目的事情，周六回来。再过一两个星期就足月，希望老婆这几天能安然度过。</p><p>医院空调有点热，我躺在小床上已然睁不开眼，不如先月亮。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[陪产日记]]></title>
        <id>/posts/2017/diary-of-waiting-for-baby-born</id>
        <link href="https://hadb.me/posts/2017/diary-of-waiting-for-baby-born"/>
        <updated>2017-03-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[03:18，电梯里，老婆口渴了，我出去买点喝的。宫缩还不是很规律，五六分钟一次，偶尔十几分钟。]]></summary>
        <content type="html"><![CDATA[<p>03:18，电梯里，老婆口渴了，我出去买点喝的。宫缩还不是很规律，五六分钟一次，偶尔十几分钟。</p><p>03:21，独自走在马路上，有点冷，身边仍然不断有汽车呼啸而过。红绿灯路口，仍有汽车电动车交错，凌晨 3 点的上海，依旧热闹。前段时间，又忙得没有坚持写日记了，从今天开始，恢复写。到全家了，附近也只有全家还开着。</p><p>03:34，买了一堆吃的喝的，全家对面的马路上停了好几辆出租车，司机在组团睡觉。买完东西突然肚子好饿，困倒不困，心里面的感觉，还说不上很激动。毕竟这种小打小闹的阵痛已经很久了，从昨天晚上七八点到现在，那会儿我就以为快了，看来我还是太天真了。</p><p>06:48，微波炉热粥。医生刚刚查房了，宫口开了两指不到，医生说今天应该会生。</p><p>11:02，吃了午饭，阵痛还在继续，伴随着干呕，老婆好辛苦。</p><p>11:40，痛的更厉害了，进产房了。</p><p>11:48，外面开始下雨了，雨滴打在窗户上滴滴答答的。</p><p>13:32，自从进了产房后就没消息了，估计已经疼得不想玩手机了。家属也不能进去。丈母娘也到了。我们在病房里焦急地等待着。之前女孩的名字已经想好了，就叫邓佳一，源自于《守护丽人》里女主的名字林佳一。男孩的名字刚刚在微信群里研究决定，就叫邓家熠。</p><p>13:46，让送了两片婴儿的尿不湿进去，看来快生了。</p><p>15:00，依旧在焦急地等待。坐在产房外面。前面出来了两个，都是生不出来要破腹产的。</p><p>16:14，等待中。</p><p>18:18，想问问吃不吃东西，问护工，护工让打电话问，打电话，没接。再麻烦护工帮忙问下。一会儿后老婆打电话过来，已经疼得说不出话，接通后好久，老婆颤抖着喊了声喂～，听完我眼泪就快掉下来。呻吟着说以后不想再生了。😭老婆好辛苦</p><p>18:28，护工推了个移动病床进去，莫非是生了？</p><p>18:36，听到婴儿的啼哭声，是我儿吗？</p><p>18:49，从中午到现在，擦了无数遍手机镜头，想等出来时拍个清楚点的第一照。</p><p>18:59，外面一起等的另一家人，生了，18 点 18 分就生了，才刚通知……老婆你生了吗？</p><p>20:20，6 点多生的那个孕妇和宝宝一起出来了。观察了 2 个小时。问了下护工，老婆应该还没生。</p><p>20:30，电梯里遇到产房的护工，等她送完产车，拜托她帮忙进去看下情况，说头已经出来了。心里开始激动了，有股肾上腺素的感觉。</p><p>21:06，老婆还没回我微信。</p><p>21:07，老婆发微信说生了，男孩，开心。老婆辛苦了！💖放心了！</p><p>21:46，刚刚送了一碗全家的枸杞粥，老婆说是甜的。不能喝。这会儿去全家买个不甜的豆浆。听老婆声音感觉稍微好些了。</p><p>22:00，签了字，交了 140，今晚打乙肝疫苗，明天打卡介苗。说是 11 点左右出产房。</p><p>22:34，老婆快出来吧！心里的石头落了地，就等你们出来了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 18 of 2017]]></title>
        <id>/posts/2017/week-18-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-18-of-2017"/>
        <updated>2017-05-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[感觉已经很久没写周记了，今天又把 60% 的键盘捣鼓出来了，打算尝试适应适应。]]></summary>
        <content type="html"><![CDATA[<p>感觉已经很久没写周记了，今天又把 60% 的键盘捣鼓出来了，打算尝试适应适应。</p><p>前段时间项目很忙，加上后面儿子出生，真的几乎没有时间去写周记。这段时间终于稍微闲了下来，赶紧抽点时间出来写点东西。</p><p>前几天利用地铁上的时间，看完了李开复的《世界因你而不同》，深有感触。通过对开复职业生涯的了解，学到了很多做人处事的道理，当然，最重要的是增加了很多对美国的向往。好想去美帝工作啊！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 20 of 2017]]></title>
        <id>/posts/2017/week-20-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-20-of-2017"/>
        <updated>2017-05-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是 5 月 18 日，周四，天气晴。]]></summary>
        <content type="html"><![CDATA[<p>今天是 5 月 18 日，周四，天气晴。</p><p>这周有几件大事。</p><ul><li>第一件，牛拜单车杜总再次约我聊天。</li><li>第二件，国资拟对上海地区人员降低社保标准，并要求核心成员去昆明长期出差。</li><li>第三件，周六，儿子双满月，家里要请客。</li></ul><p>杜总第一次约我聊天，是今年 1 月份的事，当时谈了一晚上，也给出了初步方案。当时他们的产品还在杭州外包团队手里做，还没有成型。后来杜总也约过我几次，由于当时刚到国资、以及儿子出生等事情，一直没有和他见面。上周六，杜总又约我，我们约好周一见面，带上申飞。</p><p>周日的时候，Mike 在陨石坑群跟我们说了云南总部对上海团队即将采取降低社保标准的动作，我并没有太多感想，反而增加了我去和杜总合作的动力。</p><p>周一，Mike 又找我们聊天，说了去云南长期出差的情况，说老实话，我心里反而很淡定，至少将来决定选择离开的话，也比较好说。晚上和杜总又聊了下天，说了薪资要求，基本没啥问题。</p><p>周二的时候，把人员工资、Macbook 等硬件成本、房租成本等报给了杜总，也没有太多问题。</p><p>这几天，已经开始有点身在曹营心在汉的感觉，没有太多动力写代码了。开始思考起人生，思考起自己这些时间积累的东西。</p><p>人总是需要去拼才能成长，我想，这或许是我人生的又一个转折点。我想盗用闯朋友圈的一段话：</p><p>那些看似平常的选择，其实都是命运的巨变。只是当时站在三岔路口，眼见风云千樯，你做出选择的那一日，在日记上，相当沉闷和平凡，当时还以为是生命中普通的一天。</p><p>刚刚搜了下，这段话其实是来自《杀鹌鹑的少女》。</p><p>还没有到吃午饭的时间，我想再写点东西。</p><p>说一下未来的规划。</p><p>短期规划</p><ul><li>周末回去，忙完儿子的双满月酒。</li><li>周一先和闯、老蒋、邢路他们见一面，吃吃串子，聊聊人生。</li><li>周二带闯去见下杜总。</li><li>周三我先开始请假去选办公室。</li><li>然后和杜总商量劳动合同的事。</li><li>没什么问题，月底前离职，调休。</li><li>六月把他们陆续弄过来。</li><li>七月、八月，把目前系统全部接手，修复一些现有 bug，基本保持稳定。</li><li>九月开始进行架构改造、系统升级。APP 出新版、微信推出小程序版。</li><li>年底争取整合进 ReactNative，实现一些功能的热部署。实现 CI 自动打包部署。</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Week 21 of 2017]]></title>
        <id>/posts/2017/week-21-of-2017</id>
        <link href="https://hadb.me/posts/2017/week-21-of-2017"/>
        <updated>2017-05-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[人在面临重大决策的时候，总会有点退缩，我也不例外。当我骑着ofo，驰骋在我一点也不熟悉的地段，望着街两边陌生的建筑，忽然也有一种想退缩的感觉，这是我想要的新工作吗？前途光明吗？而他们呢？也会有这样的想法吗？怎么去让他们义无反顾前进呢？]]></summary>
        <content type="html"><![CDATA[<p>人在面临重大决策的时候，总会有点退缩，我也不例外。当我骑着ofo，驰骋在我一点也不熟悉的地段，望着街两边陌生的建筑，忽然也有一种想退缩的感觉，这是我想要的新工作吗？前途光明吗？而他们呢？也会有这样的想法吗？怎么去让他们义无反顾前进呢？</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如梦令·宴席终散]]></title>
        <id>/posts/2017/rumengling</id>
        <link href="https://hadb.me/posts/2017/rumengling"/>
        <updated>2017-06-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昔日激昂沙场，]]></summary>
        <content type="html"><![CDATA[<p>昔日激昂沙场，</p><p>滚打摸爬跌撞。</p><p>不散宴席难，</p><p>我欲乘风破浪。</p><p>难忘，难忘，</p><p>人在天涯共闯。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170607]]></title>
        <id>/posts/2017/diary-20170607</id>
        <link href="https://hadb.me/posts/2017/diary-20170607"/>
        <updated>2017-06-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这是这个月第二次去杭州了。上次是 6 月 1 日，本以为上次去杭州就能完成项目交接的，哪晓得那个外包公司的老板有点喜欢装逼，上午装了半天逼，喝茶，最后才去聊正事，闯爷都没来得及参加就回上海了。聊好之后我私下去找他们开发把代码都拷好了，他知道了，让我又都删了……估计他们和杜总还有些东西没谈好。]]></summary>
        <content type="html"><![CDATA[<p>这是这个月第二次去杭州了。上次是 6 月 1 日，本以为上次去杭州就能完成项目交接的，哪晓得那个外包公司的老板有点喜欢装逼，上午装了半天逼，喝茶，最后才去聊正事，闯爷都没来得及参加就回上海了。聊好之后我私下去找他们开发把代码都拷好了，他知道了，让我又都删了……估计他们和杜总还有些东西没谈好。</p><p>国资那边现在已经是在提不起兴趣了，各种坑。去意已决了。今天给几个项目组 Leader、Jean 和 Mike 发了邮件，说明了在目前 Vue 项目中进行 Turbolinks 改造的方案不可行，如果要彻底改造，成本太大，还不如直接用 ReactNative 重写。Weex 的成熟度还没能达到要求，并且未来也不好说。张锐、建辉、建阳也同意用 ReactNative 重写的方案。</p><p>现在心里也是毫无波澜。昨天 Peter 还问我，说 Mike 这里怎么样了。他说他早就给 Mike 预判了撑不了多久。Mike 终究还是打工的，还是受制于各种来自云南的压力。</p><p>新公司，我打算在这里呆 10 年以上，如果薪资、股票份额等还算令人满意的话。</p><p>此刻坐在前往杭州的高铁上，沐浴着车窗外微弱的阳光，一点也不晒人，心情很舒畅。好久没有这么放松了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170613]]></title>
        <id>/posts/2017/diary-20170613</id>
        <link href="https://hadb.me/posts/2017/diary-20170613"/>
        <updated>2017-06-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天把闯，老蒋，邢路都聚到一起了，一起去杭州交接，老板的目标是今天一次把问题问完。]]></summary>
        <content type="html"><![CDATA[<p>今天把闯，老蒋，邢路都聚到一起了，一起去杭州交接，老板的目标是今天一次把问题问完。</p><p>上周末，我，闯，老蒋都来加班了，杜总周日也来了，并且下单买了两台 macbook pro，还给他自己买了一台 macbook 土豪金款的。预计这周五到货。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170614]]></title>
        <id>/posts/2017/diary-20170614</id>
        <link href="https://hadb.me/posts/2017/diary-20170614"/>
        <updated>2017-06-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在去昆山的路上，今天昆山智能锁产品线停了，说是锁扫码无法打开，怀疑是后端的问题。]]></summary>
        <content type="html"><![CDATA[<p>在去昆山的路上，今天昆山智能锁产品线停了，说是锁扫码无法打开，怀疑是后端的问题。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170617]]></title>
        <id>/posts/2017/diary-20170617</id>
        <link href="https://hadb.me/posts/2017/diary-20170617"/>
        <updated>2017-06-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天把闯，老蒋，申飞都带去昆山了，和邹小意，邓伟也见了面。经过排查，确认了那批板子的问题是 sim 卡没法上网。]]></summary>
        <content type="html"><![CDATA[<p>今天把闯，老蒋，申飞都带去昆山了，和邹小意，邓伟也见了面。经过排查，确认了那批板子的问题是 sim 卡没法上网。</p><p>晚上和章老师一行一同吃了饭，他们都喝了不少酒，我和闯，沈熠没喝。</p><p>吃完饭去章老师研发基地，带他们大开了眼界，章老师饶有兴趣地给我们展示了那个会各种动作的机器人。上次我看了跳舞，这次演示了俯卧撑，有点意思。机器人是其他家的产品，章老师拿来研究，打算在生产线上搞一个机械臂代替一部分人工。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170618]]></title>
        <id>/posts/2017/diary-20170618</id>
        <link href="https://hadb.me/posts/2017/diary-20170618"/>
        <updated>2017-06-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[过的第一个父亲节，在加班挣钱。]]></summary>
        <content type="html"><![CDATA[<p>过的第一个父亲节，在加班挣钱。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170703]]></title>
        <id>/posts/2017/diary-20170703</id>
        <link href="https://hadb.me/posts/2017/diary-20170703"/>
        <updated>2017-07-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天搬到独立办公室了，感觉终于开始有点像样子了。]]></summary>
        <content type="html"><![CDATA[<p>今天搬到独立办公室了，感觉终于开始有点像样子了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170822]]></title>
        <id>/posts/2017/diary-20170822</id>
        <link href="https://hadb.me/posts/2017/diary-20170822"/>
        <updated>2017-08-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今年已经过去一大半，回想起来，好像也没给自己定啥目标。自从到了牛拜之后，生活节奏也发生了很大的变化，早起习惯也没了，每天 9 点多才起得来。也没有时间做自己的事情了。]]></summary>
        <content type="html"><![CDATA[<p>今年已经过去一大半，回想起来，好像也没给自己定啥目标。自从到了牛拜之后，生活节奏也发生了很大的变化，早起习惯也没了，每天 9 点多才起得来。也没有时间做自己的事情了。</p><p>这周开始，我们有了固定的休息时间，每周二和周三休息，其余时间上班。</p><p>今天是休息的第一天。</p><p>修改了下短信拔风堂 api 的问题，过几天准备抽个时间把尾款要一下。</p><p>看了两部电影，一个讲一个伊拉克狙击手的蹂躏美国大兵的故事，罕见的结局方式。另一个是阿汤哥的新木乃伊。</p><p>在家吃了顿火锅。</p><p>陪了会儿子。</p><p>晚上，躺在床上，有了点时间思考人生 🤔。</p><p>感觉需要坚持做一点有意义的事情。比如每周写个博客，每天写日记之类的。每天抽出一小时的时间，做些有意义的事情。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20170916]]></title>
        <id>/posts/2017/diary-20170916</id>
        <link href="https://hadb.me/posts/2017/diary-20170916"/>
        <updated>2017-09-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[肖勖今天开始因为各种原因不来了，想到这样一句话：]]></summary>
        <content type="html"><![CDATA[<p>肖勖今天开始因为各种原因不来了，想到这样一句话：</p><p>古今成大事者，不惟有超世之才，亦必有坚韧不拔之志。</p><p>他太容易放弃了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[开始使用 Ghost 啦！]]></title>
        <id>/posts/2017/using-ghost</id>
        <link href="https://hadb.me/posts/2017/using-ghost"/>
        <updated>2017-10-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[关注 Ghost 很久很久了，14 年就尝试过 0.5.3 版，由于一直没有出正式版，所以一直处于关注和等待中。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2017/20171010.using-ghost/cover.jpg" alt="封面" /><p>关注 Ghost 很久很久了，14 年就尝试过 0.5.3 版，由于一直没有出正式版，所以一直处于关注和等待中。</p><p>终于在差不多一年前 Ghost 在 GitHub 上发布了<a href="https://github.com/TryGhost/Ghost/releases/tag/1.0.0-alpha.1" rel="nofollow">1.0.0-alpha.1</a>，本以为很快就能出正式版了，开始了焦急的等待。一开始基本上每天都会去 GitHub 上检查下他们的动态。后来变成了大约每周会去看一次，等了一个又一个版本，甚至等了大半年，终于在第 21 个 alpha 版后，推出了 beta 版，随后不久发布了 1.0.0 正式版。尝试去安装，结果发现挺多坑的，试了很久都没成功，正式版的 Docker 镜像也一直没出。再后面，Ghost 团队的更新也非常勤奋，基本每周都会发布新版本，那段期间我正好也比较忙，一直在默默关注，没有再去尝试安装，任务列表里的“博客迁移到 Ghost 上”也被一再推迟。</p><p>直到今天，突然想再去试下。看到 Docker 镜像也同步了最新的版本，于是直接上 Docker。几分钟就装完了，如丝般顺滑，很爽！（20171012 更新：通过默认命令安装，有些地址会显示成 localhost 开头的地址，通过<code>--env url=https://hadb.me/</code>可以修改这个地址，但是由于 Nginx 设置有问题，导致设置成 https 的地址后就陷入重定向循环，这个问题后来困扰了很久，解决方案下面会讲。）</p><p>作为一个技术博主，我觉得还是有必要把安装步骤写一下，方便后人。</p><p>我的博客是搭建在自己家里的 NAS 服务器上，具体细节以后再讲。</p><h3 id="宿主机安装证书">宿主机安装证书</h3><pre><code><span class="line" line="1"><span class="sbgvK">service</span><span class="s_sjI"> nginx</span><span class="s_sjI"> stop</span><span class="sP7_E"> &&</span><span class="s_hVV"> \
</span></span><span class="line" line="2"><span class="sbgvK">certbot</span><span class="s_sjI"> certonly</span><span class="stzsN"> --standalone</span><span class="stzsN"> --preferred-challenges</span><span class="s_sjI"> tls-sni</span><span class="stzsN"> -d</span><span class="s_sjI"> hadb.me</span><span class="sP7_E"> &&</span><span class="s_hVV"> \
</span></span><span class="line" line="3"><span class="sbgvK">service</span><span class="s_sjI"> nginx</span><span class="s_sjI"> start
</span></span></code></pre><h3 id="宿主机-nginx-设置">宿主机 Nginx 设置</h3><pre><code><span class="line" line="1"><span class="sbsja">server</span><span class="su5hD"> {
</span></span><span class="line" line="2"><span class="smGrS">    listen </span><span class="srdBf">443</span><span class="su5hD"> ssl</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="smGrS">    server_name </span><span class="su5hD">hadb.me</span><span class="sP7_E">;
</span></span><span class="line" line="4"><span class="smGrS">    client_max_body_size </span><span class="srdBf">1024m</span><span class="sP7_E">;
</span></span><span class="line" line="5"><span class="smGrS">    ssl </span><span class="s39Yj">on</span><span class="sP7_E">;
</span></span><span class="line" line="6"><span class="smGrS">    ssl_certificate </span><span class="su5hD">/etc/letsencrypt/live/hadb.me/fullchain.pem</span><span class="sP7_E">;
</span></span><span class="line" line="7"><span class="smGrS">    ssl_certificate_key </span><span class="su5hD">/etc/letsencrypt/live/hadb.me/privkey.pem</span><span class="sP7_E">;
</span></span><span class="line" line="8"><span class="sbsja">    location</span><span class="sbgvK"> / </span><span class="su5hD">{
</span></span><span class="line" line="9"><span class="smGrS">        proxy_redirect </span><span class="s39Yj">off</span><span class="sP7_E">;
</span></span><span class="line" line="10"><span class="smGrS">        proxy_read_timeout </span><span class="srdBf">300</span><span class="sP7_E">;
</span></span><span class="line" line="11"><span class="smGrS">        proxy_set_header </span><span class="su5hD">host </span><span class="sP7_E">$</span><span class="su5hD">host</span><span class="sP7_E">;
</span></span><span class="line" line="12"><span class="smGrS">        proxy_set_header </span><span class="su5hD">x-real-ip </span><span class="sP7_E">$</span><span class="su5hD">remote_addr</span><span class="sP7_E">;
</span></span><span class="line" line="13"><span class="smGrS">        proxy_set_header </span><span class="su5hD">x-forwarded-for </span><span class="sP7_E">$</span><span class="su5hD">proxy_add_x_forwarded_for</span><span class="sP7_E">;
</span></span><span class="line" line="14"><span class="smGrS">        proxy_set_header </span><span class="su5hD">x-forwarded-proto https</span><span class="sP7_E">;
</span></span><span class="line" line="15"><span class="smGrS">        proxy_pass </span><span class="su5hD">http://localhost:12368</span><span class="sP7_E">;
</span></span><span class="line" line="16"><span class="su5hD">    }
</span></span><span class="line" line="17"><span class="su5hD">}
</span></span></code></pre><p>其中 <code>proxy_set_header x-forwarded-proto https;</code>很重要，如果不加的话，当启动 docker 设置 url 为 https 开头的地址时，就会陷入重定向循环，这个问题困扰了我很久，最终在<a href="https://github.com/TryGhost/Ghost/issues/2796" rel="nofollow">这个 issue</a>里找到了答案。</p><h3 id="创建数据镜像">创建数据镜像</h3><pre><code><span class="line" line="1"><span class="sbgvK">docker</span><span class="s_sjI"> create</span><span class="stzsN"> -v</span><span class="s_sjI"> /srv/ghost/hadb.me:/var/lib/ghost/content</span><span class="stzsN"> --name</span><span class="s_sjI"> ghost-hadb-me-data</span><span class="s_sjI"> ubuntu:16.04
</span></span></code></pre><h3 id="启动-docker">启动 docker</h3><pre><code><span class="line" line="1"><span class="sbgvK">sudo</span><span class="s_sjI"> docker</span><span class="s_sjI"> run</span><span class="stzsN"> --detach</span><span class="s_hVV"> \
</span></span><span class="line" line="2"><span class="stzsN">    --name</span><span class="s_sjI"> ghost-hadb-me</span><span class="s_hVV"> \
</span></span><span class="line" line="3"><span class="stzsN">    --env</span><span class="s_sjI"> url=https://hadb.me/</span><span class="s_hVV"> \
</span></span><span class="line" line="4"><span class="stzsN">    --publish</span><span class="s_sjI"> 12368:2368</span><span class="s_hVV"> \
</span></span><span class="line" line="5"><span class="stzsN">    --volumes-from</span><span class="s_sjI"> ghost-hadb-me-data</span><span class="s_hVV"> \
</span></span><span class="line" line="6"><span class="stzsN">    --tty</span><span class="s_hVV"> \
</span></span><span class="line" line="7"><span class="s_sjI">    ghost:latest
</span></span></code></pre><p>几行命令，Done！就可以开始愉快的写博客啦！哈哈哈哈哈！😃</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[家里进小偷了]]></title>
        <id>/posts/2017/thief-came-into-my-house</id>
        <link href="https://hadb.me/posts/2017/thief-came-into-my-house"/>
        <updated>2017-10-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[早上出门前老婆发现厨房靠外的窗纱被刀划开一个大口子，我去看了下，发现被划开一个 L 型的口子，人刚好可以穿过，再看窗台，发现有脚印，突然意识到应该是小偷光顾过了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2017/20171013.thief-came-into-my-house/cover.jpg" alt="封面" /><p>早上出门前老婆发现厨房靠外的窗纱被刀划开一个大口子，我去看了下，发现被划开一个 L 型的口子，人刚好可以穿过，再看窗台，发现有脚印，突然意识到应该是小偷光顾过了。</p><p>目前我们租在一楼，厨房是靠在外面的，平时窗户都会关，有人的时候有时候会开着通风。窗户外面做了一个大的纱窗，防蚊用的。</p><p>发现被小偷光顾后，赶紧检查家里值钱的东西，我看了下我的包，发现 macbook、钱包以及钱包里的钱都在，老婆看了看戒指啥的也都在。仔细想了想，家里也没啥值钱的东西，最值钱的估计也就 macbook 跟戒指了。手机啥的都在床上，估计小偷没进卧室。</p><p>经过事后回忆，小偷很有可能是在早上 8 点多光顾的，因为我们上班比较晚，一般 9 点多才出门，小偷估计以为家里没人了，进来后发现床上有人，就跑了。</p><p>这件事情有这样几个想法：</p><ol><li>尽量不要买或租 1 楼的房子，安全性差，如果住在 1 楼，防盗窗啥的不可少</li><li>家里少放现金</li><li>我家除了电脑和戒指之外并没有什么值钱的东西😭</li></ol>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[小黑的故事]]></title>
        <id>/posts/2017/story-of-xiaohei</id>
        <link href="https://hadb.me/posts/2017/story-of-xiaohei"/>
        <updated>2017-10-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[小黑是一只流浪狗。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2017/20171014.story-of-xiaohei/cover.jpg" alt="封面" /><p>小黑是一只流浪狗。</p><p>生下来没几天就被遗弃到到垃圾房旁边，由它自生自灭。据我奶奶讲，她第一次在垃圾房旁边见到小黑的时候，它还是个小奶狗，饿得嗷嗷叫。我奶奶见它可怜，给了些剩菜它吃。过了几点再去扔垃圾见到它的时候，它已经会自己从垃圾里面找东西吃了。由于我奶奶经常喂东西它吃，它就跟着我奶奶回家了，赶都赶不走。</p><p>小时候它很粘人，也很讨人喜欢。我奶奶有时候早晨去田里干活，小黑不声不响地就跟着她了，一直陪着她，直到她干完活回来，奶奶那时候挺喜欢它的。我回过老家几次，第一次见到它，它一点都不认生，没有冲着我叫，见到我就摇尾巴。每次吃完饭，第一件事就是把骨头什么的给它吃，吃得可香了。没有骨头的时候，我会把火腿肠省给它吃。可能从小饿惯了的原因，它吃东西特别快，狼吞虎咽。火腿肠到它嘴里，估计还没嚼就吞下去了。</p><p>第二次回去已经是两三个月后了，它的个头比之前大了很多。我家还有隔壁二爹爹家的剩饭剩菜基本都被它包了，长得很壮很肥。但它也比以前凶了很多。附近的狗都打不过它，它吃东西的时候，别的狗只要靠近，它都会恶狠狠地去把别的狗赶走。甚至也经常去抢别的狗的东西吃。</p><p>大了之后，它还是很粘人，但就没有那么讨人喜欢了。可能因为它小时候经常跳起来撒娇，就有东西吃，导致它形成了条件反射，看到熟人就跳起来撒娇。因为在农村，它的爪子经常很脏，经常把我奶奶的裤子弄脏，奶奶总是骂它。我回去之后，它很快认出了我，在我面前跳来跳去，好不热情。我对它特别好，这次回来还专门买了狗粮和磨牙棒给它吃。让它感受下城里狗狗们的食物。它特别爱吃狗粮，但是它的成长速度出乎了我的意料，那点狗粮压根填不了它的肚子。磨牙棒在它嘴里，也是三下五除二就被吃掉了。由于只带了一袋狗粮，每次我都只给它吃一点。想教它听话，每次它做对了我就给它奖励。但它太调皮了，不给它吃他会一直跳来跳去，我后面也拿它没辙。</p><p>由于在农村，狗咬人的事情还是挺多的，咬了人要赔钱。所以一般他们都不愿意养狗。养狗还要去给它打疫苗什么的，也要花钱。所以虽然它经常来我家，但我奶奶对外从来不会说它是我家的狗。它经常在我们这附近转，附近的狗跟它都很熟，经常有很多狗跟着她转。后来我才发现，到了狗狗交配的季节了。小黑是只母狗，附近的公狗很多，母狗少。公狗之间经常会打架，小黑跟它们之间也常常会追逐打闹。家门口最多的时候有十几条狗，好不热闹。</p><p>隔壁二爹爹不太喜欢小黑，它确实也很惹人烦，而且它对外人越来越凶，见到外人会恶狠狠地叫个不停。二爹爹的羊圈里没有养羊，堆了些草，有草保暖，并且遮风挡雨，所以小黑以二爹爹的羊圈为家，天天睡在那里。二爹爹担心如果它咬了人，别人会来找他麻烦。所以后来，二爹爹就把他送到很远之外的一个十字路口，让它又重新过上了流浪的生活。</p><p>很想念你，热情的小黑，希望你能找到吃的。也不知，在你趴在地上思考的时候，在你饿肚子的时候，你的脑海里是不是还会经常想起，那个曾经给你喂火腿肠，给你喂狗粮，给你买磨牙棒的我？</p><p>生而为狗，并且是流浪狗，是你的不幸。我想如果你从小不被遗弃，不那么缺少爱，不经常饿肚子，是不是就不会那么热情，不会那么不讨人厌，不会那么凶，是不是就可以长期留在我们这里，帮我们守卫家园，做一只有家的中华田园犬了？</p><p>小黑，祝好！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[喜迎十九大]]></title>
        <id>/posts/2017/celebrate-19th-national-congress</id>
        <link href="https://hadb.me/posts/2017/celebrate-19th-national-congress"/>
        <updated>2017-10-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近十九大召开，我的博客、GitLab 等服务都挂了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2017/20171021.celebrate-19th-national-congress/cover.jpg" alt="封面" /><p>最近十九大召开，我的博客、GitLab 等服务都挂了。</p><p>以往，上海电信虽然封了 80 端口，但 443 端口还能用，我自己的站点基本都在家里自建的一台服务器上，通过猫的 NAT 设置，把 443 的流量转发到服务器上来，里面再通过 Nginx，可以实现 https 访问自己的博客以及 GitLab 等服务。</p><p>不过现在 443 端口也禁用了的话，就只能用自定义端口了，看起来就很别扭了。GitLab 目前临时换了别的端口在用，因为 GitLab 这货实在太耗性能了，云服务器上自己搭个的话，太费钱了。所幸的是 Ghost 的性能消耗应该不高，昨晚已经开始走域名备案的流程，妥协了，为了博客能稳定，打算把博客放到阿里云上。</p><p>作为一个程序猿，科学上网是必备技能。然而国内的网络环境，极其恶劣，在这样的情况下，也催生了很多产业，SS 账号便是其中一个灰色产业。很早之前我也是通过购买各种第三方的账号来科学上网，但后面由于网络不稳定，加上项目上的关系，自己在 DigitalOcean 上租了服务器，自己搭建了 SS 服务。最近 GFW 的技术有了突破，可以检测到 Shadowsocks 的流量了，很多服务都挂了。萌生了加入这个产业的想法，然而搜了下，被这个新闻吓到了：<a href="http://www.williamlong.info/archives/5084.html" rel="nofollow">出售翻墙软件被抓获利一万判刑九个月</a>。于是打消了这个念头。</p><p>我本身是党员，也热爱党，热爱国家。对于海外各种反动势力也是深恶痛绝。但，作为一名 IT 从业者，很多技术资料都需要借助 Google 才能搜索得到，很多东西在国内是没有的，中文文档也很匮乏，这种情况，我们就不得不通过翻墙来实现。国内对于网络一刀切的做法，真的让人很难过。Google 这样的公司，这样的服务，在中国，官方竟然是不让用的，在 21 世纪，这简直是无法理解的一件事情，网络管制到这样的地步，究竟害怕的是什么？社会主义强国的自信在哪里？这不是清朝时的闭关锁国么？</p><p>这样一篇文章，也许并不是政治正确的，但我还是想写，我想，等未来我们能自由地在互联网上遨游的时候，我再回过头来看自己的博客，能想起，当时那个年代，我们的网络环境是多么的恶劣。我们这一代，见证了很多历史时刻，享受了改革开放的红利、见证了中国崛起、见证了房价的疯长、也正经历着网络封锁的时代。还记得刚上大学那会儿，谷歌还是能用的，还记得那段时间 GitHub 被国内巨大流量搞挂了，时刻到 Status 里面看 DDoS 攻击的进展。</p><p>时代在发展，技术在发展，这些年，GFW 的那些科学家们也没闲着。GFW 的初衷是好的，然而发展到现在，它所阻止的不仅仅是那些反动的网站、言论，对搞技术的人而言，它阻挡的更多的是国外先进的技术，现在的 GFW 就是中国网络界的一个魔。无能者只能受其欺凌，能者绕其道而行。有句话叫：“魔高一尺，道高一丈”，降妖除魔的道士们在哪里？什么时候我们不再需要绕其道而行，能够自由地触及到互联网的任何角落？到那个时候，我们才真的能扬眉吐气，自信地站在世界的舞台上，不再受美帝攻击、受国人诟病我们的网络自由了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20171026]]></title>
        <id>/posts/2017/diary-20171026</id>
        <link href="https://hadb.me/posts/2017/diary-20171026"/>
        <updated>2017-10-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天去炫丽建材进行了报价，下午没遇到华兴磁材的杨总]]></summary>
        <content type="html"><![CDATA[<p>昨天去炫丽建材进行了报价，下午没遇到华兴磁材的杨总</p><p>今天上午再次来到华兴磁材</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20171031]]></title>
        <id>/posts/2017/diary-20171031</id>
        <link href="https://hadb.me/posts/2017/diary-20171031"/>
        <updated>2017-10-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天这是第三次去炫丽建材了。]]></summary>
        <content type="html"><![CDATA[<p>今天这是第三次去炫丽建材了。</p><p>早上 8 点多就出门，到了那里，沈许说今天刘总不在，昨天准备跟我们说的，搞忘了。我们说先把需求确定下来，刘总那边之后再给他看也没事。</p><p>需求谈了差不多了的时候，刘总又来厂里了，天意。</p><p>我们把需求确定好了，也给了一个很合理的价格，她们拿给刘总看。刘总让她们带我们去食堂吃了工作餐。前面来了两次都没吃上工作餐，这次还吃上饭了，感觉稳了。后面还要等戴总签字，才能签合同开工搞。</p><p>吃完饭，申总送我回去。路上遇到一个刚被撞倒在地的狗，还在蠕动着。这一幕一直在我脑海里，很久挥之不去，所以写下来。</p><p>到了家，简单介绍了下项目的情况，爸爸让我早点回上海，陪老婆一起带孩子。于是又上了去海安的公交。奶奶说回来一趟还没在家吃顿饭，她心里都觉得很难过，我安慰她说没事，以后还会经常回来的。</p><p>老婆下个月开始不上班了，专职在家带小孩。昨晚把妈妈一起带回老家了，姨帮她介绍了个工作，和姨一起上班，今天一大早就去了。</p><p>这会儿在前往上海的大巴上，一点半的虹桥班，新车，很平稳，里面味道也很好。以后中午走还坐这班。不想坐 1 点的那班了，到处带人，味道也不好。</p><p>老家收稻开始倒计时了，就等收割机了。金灿灿的一大片，很好看。今年没能在家帮忙，还好我爸在家，可以帮帮爷爷奶奶。南边有块田被种田大户要过去了，现在还剩一亩多田，比以前应该也轻松很多了。</p><p>刘奶奶家的狗还在哺乳期，中午给他喂了个大骨头，它啃了半天都没啃完。昨天喂它吃火腿肠，感觉他嚼都没嚼就一口吞下去了，跟以前小黑很像。不过它的牙齿没有小黑厉害，小黑啃骨头都是咔咔就碎了。</p><p>昨晚开爸的车回来，很久没开车了，开了两百多公里，过了把瘾。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ghost 博客迁移至阿里云 Docker]]></title>
        <id>/posts/2017/transfer-blog-to-aliyun-docker</id>
        <link href="https://hadb.me/posts/2017/transfer-blog-to-aliyun-docker"/>
        <updated>2017-11-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[刚刚，将 Ghost 博客迁移到了阿里云 Docker 上。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2017/20171102.transfer-blog-to-aliyun-docker/cover.png" alt="封面" /><p>刚刚，将 Ghost 博客迁移到了阿里云 Docker 上。</p><p>由于近期网络问题，导致家里的 NAS 已经无法提供 443 端口的服务了，之前的临时解决方案是将 hadb.me 的域名解析到 DigitalOcean 的一台机器上，然后用 nginx 转发到 NAS 的 20443 端口，通过海外的服务器做了中转，访问速度可想而知。并且近期海外网络极不稳定，最终决定还是老老实实备案，迁移到阿里云上来。</p><p>近年来，Docker 容器化越来越火，我最近的几个项目也都是通过 Docker 来部署的，非常方便。</p><p>域名备案经历了几波周折，提交备案后，阿里云初检未通过，有如下问题：</p><ol><li>根据要求已经取得备案号的网站最下方必须显示您的备案号，并能链接到工信部网站<a href="http://www.miitbeian.gov.cn/" rel="nofollow">www.miitbeian.gov.cn</a>，目前您网站“monkeyrun.net”最下方备案号无法链接工信部网站，请您修改</li><li>根据要求网站名称必须与主办单位名称有一定的关联性。您备案的网站名称“HADB 的博客”与主办单位名称“上海猿奋网络科技有限公司”没有关联性，请修改</li><li>根据要求域名持有者必须与主办单位名称一致，经查询您的域名“hadb.me”持有者与您备案信息中“邓斌 ”单位名称/法人姓名不一致，请您先办理域名过户</li><li>根据管局要求域名有效期需要大于 6 个月，您的域名“hadb.me”有效期不足 6 个月，请您修改</li><li>“邓斌”证件号码在多个单位/个人备案中重复出现多次，根据要求，一个证件号码只能出现在一个单位/个人备案下，请您更换其它证件</li></ol><p>问题 1、2 改起来都还好，很快改完了。</p><p>问题 3 操作的过程中遇到了一个很蛋疼的问题。域名原先在 Godaddy 上购买的，在过户前，手贱把域名里的持有者信息修改了下，从英文名改成了中文品拼音，然后就尴尬了，Godaddy 禁止域名转出了，锁定期貌似 60 天，后来给 Godaddy 打中文客服，一个妹子客服跟我说可以给<a href="mailto:review60@goaddy.com">review60@goaddy.com</a>发邮件申请解锁，发了邮件一天没回复。又打电话过去，这次是个男客服接的，他跟我说，这个锁定期是没办法解锁的，巴拉巴拉，口径竟然不一样。后来 Godaddy 的 review60 团队回复我邮件了，说已经解锁了 60 天的锁定期。如果有遇到同样问题的朋友，可以尝试给 review60 团队发邮件就可以解锁了。但是在万网进行域名过户的时候，一直提示“该域名产品暂时不允许转入，无法进行转入操作”，查了下，万网目前不支持.me 域名的新注册和转入。后来就尝试了下直接提交，没有做过户操作，也通过了初审。阿里云这里要求的过户其实是非必要的，只需要把持有者信息修改就可以了。</p><p>问题 4 续费了下就可以了。</p><p>问题 5，也费了些功夫。几年前上大学时，一个外包项目中用的我自己的身份证作为网站负责人备案的。之前一直没有要求说一个证件号码只能出现在一个备案下，不过既然现在提示了这个问题，那就去处理下。由于这个外包项目已经停止了，并且甲方的网站也已经不做了。所以处理起来很简单，直接登录原备案账号，把备案号注销掉就可以了。</p><p>几经周折，备案号终于下来了，接下来开始处理部署的问题。</p><p>在阿里云上部署与在自己的机器上部署 Docker 有些区别。</p><p>具体流程如下：</p><ol><li>购买阿里云文件存储 NAS 服务，用来存放 Docker 数据卷</li><li>在 ECS 上挂在 NAS，将以前的博客数据复制到 NAS 中的<code>/ghost-hadb-data</code>目录下</li><li>容器服务中创建 NAS 类型的数据卷<code>ghost-hadb-data</code>，指向<code>/ghost-hadb-data</code>目录</li><li>创建应用，简单路由配置，将<code>hadb.me</code>指向容器端口<code>2368</code>，选择刚刚创建的数据卷，容器路径为<code>/var/lib/ghost/content</code>，在环境变量中配置 url 为<code>https://hadb.me/</code></li><li>配置负载均衡，添加 https 协议 443 端口监听，导入证书</li><li>将域名解析切换到负载均衡 ip 地址</li></ol><p>Done！以后可以愉快的写博客啦！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[微信小程序在安卓上 SSL 报错的问题]]></title>
        <id>/posts/2017/wxapp-ssl-error</id>
        <link href="https://hadb.me/posts/2017/wxapp-ssl-error"/>
        <updated>2017-11-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[开发工具上和 iOS 真机上访问 api 都是正常的，在安卓上提示如下错误：]]></summary>
        <content type="html"><![CDATA[<p>开发工具上和 iOS 真机上访问 api 都是正常的，在安卓上提示如下错误：</p><pre><code><span class="line" line="1"><span class="su5hD">request:fail ssl hand shake error
</span></span></code></pre><p>尝试在安卓的浏览器中访问 api 地址，提示“<code>该证书并非来自可信的授权中心</code>”，于是感觉应该是 SSL 证书的问题。</p><p>SSL 证书是通过 Let's Encrypt 申请的，部署在阿里云 SLB 上。</p><p>通过<a href="https://www.ssllabs.com/ssltest/index.html" rel="nofollow">https://www.ssllabs.com/ssltest/index.html</a> 测试，TLS1.0、TLS1.1、TLS1.2 都是支持的，但有如下提示</p><pre><code><span class="line" line="1"><span class="su5hD">This server's certificate chain is incomplete. Grade capped to B.
</span></span></code></pre><p>于是重新查看了下 Let's Encrypt 生成的证书文件，想起来在阿里云 SLB 的证书填写的是<code>cert.pem</code>的内容，没有包含中间证书。于是重新填写<code>fullchain.pem</code>里的内容，问题解决。</p><p>下面是 Let's Encrypt 生成的证书文件及其内容：</p><table><thead><tr><th>文件名</th><th>内容</th></tr></thead><tbody><tr><td>cert.pem</td><td>服务端证书</td></tr><tr><td>chain.pem</td><td>浏览器需要的所有证书但不包括服务端证书，比如根证书和中间证书</td></tr><tr><td>fullchain.pem</td><td>包括了 cert.pem 和 chain.pem 的内容</td></tr><tr><td>privkey.pem</td><td>证书的私钥</td></tr></tbody></table>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2018 年伊始]]></title>
        <id>/posts/2018/start-of-2018</id>
        <link href="https://hadb.me/posts/2018/start-of-2018"/>
        <updated>2018-01-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[2018 年的第一天，下午，地铁 4 号线。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2018/20180101.start-of-2018/cover.jpg" alt="封面" /><p>2018 年的第一天，下午，地铁 4 号线。</p><p>2017 年，经历了很多事，有喜悦，有愤怒，有信心满满，有无可奈何。</p><p>理应在 2017 年末写点总结的内容，却一直没有心情。2018 的第一天，跟手头项目的甲方老板聊了会天，豁然开朗，心情好了很多，对未来又多了些信心。</p><p>2017 年，最大的成就应该就是牛拜单车的项目了。从问题百出的外包手上接过项目，从零开始搭建技术团队，最终稳定运营，累计几百万单，也是有史以来管理过的最大的项目了。技术层面，Docker、kotlin、VUE，这些新东西也都玩得很溜了。</p><p>2017 年，最无奈的应该也是牛拜单车。这个项目是以全职的方式做的，还带去了几个兄弟，然而最终因为老板的问题，兄弟们的赔偿没拿到不谈，还都搭进去一个月工资。很无奈。2018 年，还要继续陪兄弟们走劳动仲裁。</p><p>2017 年，有了儿子，买了房子，拍中了沪牌，完成了人生中几件大事。2018 年，可以更集中精力向自己的目标前进。</p><p>2018 年，争取坚持在创业的道路上能走得更远一些，把智能路灯、智能电表、无人货架这些物联网项目搞好，利用外包的资源把这些项目搞起来，做成云平台，可复制，可以以此去谈更多的同类项目，实现平台运作，才是可持续发展的方向，2018 年要向可持续发展的外包方向努力，可以适当接一些短平快的项目，但不应以这些项目为主。希望 2018 年能够实现可持续发展，扩大团队规模，去找一家孵化基地，搞到几个免费的工位，把公司像模像样地搞起来。</p><p>最近看到一些说法，比如“最后一批 90 后已成年”、“90 后已步入中年”之类的。作为很早的一批 90 后，我倒还没有感受到中年的危机。压力还很小，可以说轻松过完一辈子是很容易实现的。虽然家里人很希望我能找一个稳定的工作，他们对我的期望也还停留在老老实实上班、养家糊口、稳定安逸地生活，然而我还是有一颗不平庸的心，那股出人头地的志气还在，燕雀安知鸿鹄之志哉！</p><p>还记得阮一峰老师的《<a href="http://www.ruanyifeng.com/blog/2016/03/plan-b.html" rel="nofollow">你的 B 计划在哪里？</a>》，当时我也写过一篇文章《<a href="../2016/plan-b/">Plan B</a>》，现在回想起来，2016 年和 2017 年，有一半的时间在执行当时制定的 Plan B，中间也妥协过，找过两份工资不错的工作，现在又回到了之前的 Plan B 上，走上了不打工的外包之路。目前看来，其实当时制定的 Plan B 也不能算是严格意义上的 Plan B，我还是在围绕软件开发这个行业在制定计划。真正的 Plan B 应该是完全逃离 Plan A 的计划。2017 年，目睹了很多创业项目的失败。如果将来的某一天，我在自己的创业的道路上彻底失败了，我该何去何从？希望 2018 年，我能为自己找到答案。</p><p>2018 年，争取能够有稳定的项目、稳定的资金来源，证明自己，不再需要去打工，可以在自己的 Plan B 上越走越远，变成 Plan A。</p><p>加油！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于孤独]]></title>
        <id>/posts/2018/about-lonely</id>
        <link href="https://hadb.me/posts/2018/about-lonely"/>
        <updated>2018-01-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[人是一种害怕孤独的动物。]]></summary>
        <content type="html"><![CDATA[<p>人是一种害怕孤独的动物。</p><p>这种害怕是天生的，从婴儿时期醒来看不到父母的哭泣，到年老了子女不在身边的忧伤。</p><p>最近，老婆孩子都回老家了，丈人丈母娘天天在工地加班。只有我一个人在家，倍感孤独。</p><p>平时，他们在的时候，我是很排斥出门买东西的。刚刚，突然想到今天一天好像只吃了一包方便面，突然有点想吃点水果，于是出门买了些水果回来。</p><p>社交软件在很大程度上缓解了独处的孤独感，玩游戏大家普遍也都会去玩网络游戏，可以跟朋友一起开黑，也是一张社交方式。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180129]]></title>
        <id>/posts/2018/diary-20180129</id>
        <link href="https://hadb.me/posts/2018/diary-20180129"/>
        <updated>2018-01-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[凌晨两点，耳边呼呼的空调声，我坐在儿子的围栏里写代码。]]></summary>
        <content type="html"><![CDATA[<p>凌晨两点，耳边呼呼的空调声，我坐在儿子的围栏里写代码。</p><p>刚刚用 Vue 完成了一个很酷的动效，动了点脑筋，感觉很爽。前段时间接了个很坑爹的外包，各种奇葩需求，界面逻辑很乱，写得很不爽。而且做的东西都没咋要动脑筋，没有一点成就感。</p><p>前几天一直在下雪，之前本想趁着雪意，写首诗助助兴，也一直没能抽出时间。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180211]]></title>
        <id>/posts/2018/diary-20180211</id>
        <link href="https://hadb.me/posts/2018/diary-20180211"/>
        <updated>2018-02-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天傍晚去老家洗车店帮忙保养，机油是自己从京东上买的。]]></summary>
        <content type="html"><![CDATA[<p>昨天傍晚去老家洗车店帮忙保养，机油是自己从京东上买的。</p><p>老板先是把我的车吊起来，在下面找了半天，没找到机油箱底壳放油的螺丝。然后就把车放下来从引擎盖上面的机油管抽油。他的细抽油管找不到了，拿了根稍微粗一些的管子，用力塞了进去。油抽好后，管子拔不出来了，又不敢用力拔。搞到天黑，只得作罢。老板没拆过奥迪，不敢乱搞，打算今天去海安找他朋友帮忙拆下来弄。</p><p>今天早上一起去海安他朋友的汽修厂，汽修厂的小弟用力拔了出来，断了一部分在里面。他们老板说没事，不影响开，他们这儿都是这么弄的。保养两三次之后，差不多都会掉下来，从油底壳下面可以找到。早知道你也是拔断在里面，还要去海安干嘛。😓</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180301]]></title>
        <id>/posts/2018/diary-20180301</id>
        <link href="https://hadb.me/posts/2018/diary-20180301"/>
        <updated>2018-03-01T00:00:00.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>2018，Fighting！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180407]]></title>
        <id>/posts/2018/diary-20180407</id>
        <link href="https://hadb.me/posts/2018/diary-20180407"/>
        <updated>2018-04-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[凌晨 2 点 18 分，我爸来电。]]></summary>
        <content type="html"><![CDATA[<p>凌晨 2 点 18 分，我爸来电。</p><p>我赶紧坐起来，我爸一般不轻易给我打电话。用惯了微信的他，通常都是一大堆长语音飞过来让我慢慢听。</p><p>电话里说我妈最近身体不舒服，刚刚胃痉挛了，浑身难受，说吃不消了要去人民医院看看，没人带孩子，让我们回去几天。</p><p>原计划清明节回去的，那样的话我们在家估计已经送去医院了。</p><p>不多说，凌晨 3 点，我们已经开上回家的路。</p><p>一路开启车载 KTV 模式，我发现这是有效缓解瞌睡的好方法。当然，在服务区喝下一瓶咖啡之后，我才发现，之前其实感觉清醒却也是迷糊的。喝下咖啡后，那才叫真清醒。</p><p>回到家，6 点左右，天已经亮了，去庄上吃了个早饭，买了点包子带回去。有一小段朝东的路，朝阳照在挡风玻璃上亮的我几乎看不见。赶紧打开空调把上面的雾水吹掉才恢复视野。</p><p>到家后发现并没有去医院，用热水袋缓解了之后就不想去了。家里人都有个坏毛病，就是讳疾忌医。有点病总喜欢扛着，实在撑不住了才愿意去医院。虽然我有时候也这样。</p><p>躺在楼下小孩的床上眯了一会儿，他们都吃了早饭，又说医院检查都要空腹，今天就不去了，明天再看看。</p><p>来到外面，阳光正好，空气里弥漫着的都是油菜花的味道，爱极了农村，这是我生活了 20 年的地方。</p><p>下午带儿子去镇上洗了个澡。</p><p>晚上迷迷糊糊我就睡着了。直到刚才，才醒过来。明天一定要把我妈带到医院查一下。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Maven 项目 Docker 一键发布配置]]></title>
        <id>/posts/2018/dockerfile-maven</id>
        <link href="https://hadb.me/posts/2018/dockerfile-maven"/>
        <updated>2018-04-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Docker 用了很久了，之前 Maven 项目一直用的docker-maven-plugin，但是作者目前已经不推荐使用这种方式了，该项目已经不再更新功能，只提供 bugfix。他们的新项目叫做dockerfile-maven，配置上有些不同，之前一直没时间去更新，最近的一个项目中，采用了最新的插件，中间也踩过不少坑，刚刚终于都搞定了，记录一下。]]></summary>
        <content type="html"><![CDATA[<p>Docker 用了很久了，之前 Maven 项目一直用的<a href="https://github.com/spotify/docker-maven-plugin" rel="nofollow">docker-maven-plugin</a>，但是作者目前已经不推荐使用这种方式了，该项目已经不再更新功能，只提供 bugfix。他们的新项目叫做<a href="https://github.com/spotify/dockerfile-maven" rel="nofollow">dockerfile-maven</a>，配置上有些不同，之前一直没时间去更新，最近的一个项目中，采用了最新的插件，中间也踩过不少坑，刚刚终于都搞定了，记录一下。</p><p>Dockerfile 无需多说，整理了一个通用的，可以用在任意 Spring Boot 项目中，如下：</p><pre><code><span class="line" line="1"><span class="sw1J6">FROM</span><span class="su5hD"> frolvlad/alpine-oraclejdk8:slim
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sw1J6">RUN</span><span class="su5hD"> ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sw1J6">VOLUME</span><span class="su5hD"> /tmp
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sw1J6">ARG</span><span class="su5hD"> JAR_FILE
</span></span><span class="line" line="8"><span class="sw1J6">ADD</span><span class="su5hD"> ${JAR_FILE} app.jar
</span></span><span class="line" line="9"><span class="sw1J6">RUN</span><span class="su5hD"> sh -c </span><span class="s_sjI">'touch /app.jar'
</span></span><span class="line" line="10"><span class="sw1J6">ENV</span><span class="su5hD"> JAVA_OPTS=</span><span class="s_sjI">""
</span></span><span class="line" line="11"><span class="sw1J6">ENV</span><span class="su5hD"> ENV=</span><span class="s_sjI">""
</span></span><span class="line" line="12"><span class="sw1J6">ENTRYPOINT</span><span class="su5hD"> [ </span><span class="s_sjI">"sh"</span><span class="su5hD">, </span><span class="s_sjI">"-c"</span><span class="su5hD">, </span><span class="s_sjI">"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar --spring.profiles.active=$ENV"</span><span class="su5hD"> ]
</span></span></code></pre><p>在用阿里云容器服务的时候，这里的<code>ENV</code>可以直接显示到配置项中进行配置，根据不同的配置选择不同的 profiles 文件。</p><p><code>pom.xml</code>文件中，加入<code>plugin</code>，会自动添加版本号和<code>latest</code>两个<code>tag</code>，并推送。</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">plugin</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">groupId</span><span class="sP7_E">></span><span class="su5hD">com.spotify</span><span class="sP7_E"></</span><span class="sQzsp">groupId</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">    <</span><span class="sQzsp">artifactId</span><span class="sP7_E">></span><span class="su5hD">dockerfile-maven-plugin</span><span class="sP7_E"></</span><span class="sQzsp">artifactId</span><span class="sP7_E">>
</span></span><span class="line" line="4"><span class="sP7_E">    <</span><span class="sQzsp">version</span><span class="sP7_E">></span><span class="su5hD">1.4.0</span><span class="sP7_E"></</span><span class="sQzsp">version</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">    <</span><span class="sQzsp">executions</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E">        <</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="7"><span class="sP7_E">            <</span><span class="sQzsp">id</span><span class="sP7_E">></span><span class="su5hD">build-image</span><span class="sP7_E"></</span><span class="sQzsp">id</span><span class="sP7_E">>
</span></span><span class="line" line="8"><span class="sP7_E">            <</span><span class="sQzsp">phase</span><span class="sP7_E">></span><span class="su5hD">package</span><span class="sP7_E"></</span><span class="sQzsp">phase</span><span class="sP7_E">>
</span></span><span class="line" line="9"><span class="sP7_E">            <</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="10"><span class="sP7_E">                <</span><span class="sQzsp">goal</span><span class="sP7_E">></span><span class="su5hD">build</span><span class="sP7_E"></</span><span class="sQzsp">goal</span><span class="sP7_E">>
</span></span><span class="line" line="11"><span class="sP7_E">            </</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="12"><span class="sP7_E">        </</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="13"><span class="sP7_E">        <</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="14"><span class="sP7_E">            <</span><span class="sQzsp">id</span><span class="sP7_E">></span><span class="su5hD">tag-image-version</span><span class="sP7_E"></</span><span class="sQzsp">id</span><span class="sP7_E">>
</span></span><span class="line" line="15"><span class="sP7_E">            <</span><span class="sQzsp">phase</span><span class="sP7_E">></span><span class="su5hD">deploy</span><span class="sP7_E"></</span><span class="sQzsp">phase</span><span class="sP7_E">>
</span></span><span class="line" line="16"><span class="sP7_E">            <</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="17"><span class="sP7_E">                <</span><span class="sQzsp">goal</span><span class="sP7_E">></span><span class="su5hD">tag</span><span class="sP7_E"></</span><span class="sQzsp">goal</span><span class="sP7_E">>
</span></span><span class="line" line="18"><span class="sP7_E">                <</span><span class="sQzsp">goal</span><span class="sP7_E">></span><span class="su5hD">push</span><span class="sP7_E"></</span><span class="sQzsp">goal</span><span class="sP7_E">>
</span></span><span class="line" line="19"><span class="sP7_E">            </</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="20"><span class="sP7_E">            <</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="21"><span class="sP7_E">                <</span><span class="sQzsp">tag</span><span class="sP7_E">></span><span class="su5hD">${project.version}</span><span class="sP7_E"></</span><span class="sQzsp">tag</span><span class="sP7_E">>
</span></span><span class="line" line="22"><span class="sP7_E">            </</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="23"><span class="sP7_E">        </</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="24"><span class="sP7_E">        <</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="25"><span class="sP7_E">            <</span><span class="sQzsp">id</span><span class="sP7_E">></span><span class="su5hD">tag-image-latest</span><span class="sP7_E"></</span><span class="sQzsp">id</span><span class="sP7_E">>
</span></span><span class="line" line="26"><span class="sP7_E">            <</span><span class="sQzsp">phase</span><span class="sP7_E">></span><span class="su5hD">deploy</span><span class="sP7_E"></</span><span class="sQzsp">phase</span><span class="sP7_E">>
</span></span><span class="line" line="27"><span class="sP7_E">            <</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="28"><span class="sP7_E">                <</span><span class="sQzsp">goal</span><span class="sP7_E">></span><span class="su5hD">tag</span><span class="sP7_E"></</span><span class="sQzsp">goal</span><span class="sP7_E">>
</span></span><span class="line" line="29"><span class="sP7_E">                <</span><span class="sQzsp">goal</span><span class="sP7_E">></span><span class="su5hD">push</span><span class="sP7_E"></</span><span class="sQzsp">goal</span><span class="sP7_E">>
</span></span><span class="line" line="30"><span class="sP7_E">            </</span><span class="sQzsp">goals</span><span class="sP7_E">>
</span></span><span class="line" line="31"><span class="sP7_E">            <</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="32"><span class="sP7_E">                <</span><span class="sQzsp">tag</span><span class="sP7_E">></span><span class="su5hD">latest</span><span class="sP7_E"></</span><span class="sQzsp">tag</span><span class="sP7_E">>
</span></span><span class="line" line="33"><span class="sP7_E">            </</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="34"><span class="sP7_E">        </</span><span class="sQzsp">execution</span><span class="sP7_E">>
</span></span><span class="line" line="35"><span class="sP7_E">    </</span><span class="sQzsp">executions</span><span class="sP7_E">>
</span></span><span class="line" line="36"><span class="sP7_E">    <</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="37"><span class="sP7_E">        <</span><span class="sQzsp">repository</span><span class="sP7_E">></span><span class="su5hD">registry.cn-hangzhou.aliyuncs.com/xxx/${project.artifactId}</span><span class="sP7_E"></</span><span class="sQzsp">repository</span><span class="sP7_E">>
</span></span><span class="line" line="38"><span class="sP7_E">        <</span><span class="sQzsp">tag</span><span class="sP7_E">></span><span class="su5hD">${project.version}</span><span class="sP7_E"></</span><span class="sQzsp">tag</span><span class="sP7_E">>
</span></span><span class="line" line="39"><span class="sP7_E">        <</span><span class="sQzsp">useMavenSettingsForAuth</span><span class="sP7_E">></span><span class="su5hD">true</span><span class="sP7_E"></</span><span class="sQzsp">useMavenSettingsForAuth</span><span class="sP7_E">>
</span></span><span class="line" line="40"><span class="sP7_E">        <</span><span class="sQzsp">buildArgs</span><span class="sP7_E">>
</span></span><span class="line" line="41"><span class="sP7_E">            <</span><span class="sQzsp">JAR_FILE</span><span class="sP7_E">></span><span class="su5hD">target/${project.build.finalName}.jar</span><span class="sP7_E"></</span><span class="sQzsp">JAR_FILE</span><span class="sP7_E">>
</span></span><span class="line" line="42"><span class="sP7_E">        </</span><span class="sQzsp">buildArgs</span><span class="sP7_E">>
</span></span><span class="line" line="43"><span class="sP7_E">    </</span><span class="sQzsp">configuration</span><span class="sP7_E">>
</span></span><span class="line" line="44"><span class="sP7_E"></</span><span class="sQzsp">plugin</span><span class="sP7_E">>
</span></span></code></pre><p>想要直接用<code>mvn deploy</code>完成整个部署的话，还需要加一下 Nexus 的发布配置</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">distributionManagement</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">repository</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">        <</span><span class="sQzsp">id</span><span class="sP7_E">></span><span class="su5hD">monkey-run-maven-release</span><span class="sP7_E"></</span><span class="sQzsp">id</span><span class="sP7_E">>
</span></span><span class="line" line="4"><span class="sP7_E">        <</span><span class="sQzsp">name</span><span class="sP7_E">></span><span class="su5hD">MonkeyRun Maven Release Repository</span><span class="sP7_E"></</span><span class="sQzsp">name</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">        <</span><span class="sQzsp">url</span><span class="sP7_E">></span><span class="su5hD">你的nexus仓库地址</span><span class="sP7_E"></</span><span class="sQzsp">url</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E">    </</span><span class="sQzsp">repository</span><span class="sP7_E">>
</span></span><span class="line" line="7"><span class="sP7_E"></</span><span class="sQzsp">distributionManagement</span><span class="sP7_E">>
</span></span></code></pre><p>阿里云容器上配置好镜像更新重新部署的触发器，之后就是直接<code>mvn deploy</code>等编译好上传完就发布完成啦！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[前端跨项目组件化及基于 Docker 的快速部署方案]]></title>
        <id>/posts/2018/frontend-components-and-docker-deploy</id>
        <link href="https://hadb.me/posts/2018/frontend-components-and-docker-deploy"/>
        <updated>2018-04-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近静下心来写了几个项目，花了些时间重新整理了整套组件化方案和部署方案，记录一下。]]></summary>
        <content type="html"><![CDATA[<p>最近静下心来写了几个项目，花了些时间重新整理了整套组件化方案和部署方案，记录一下。</p><h3 id="跨项目组件化">跨项目组件化</h3><p>前端的组件化不用多说了，发展到现在，无论是 React 的还是 Vue，都提供了相当方便的组件化实现。在日常项目中，有些组件其实是可以跨多个项目使用的，将这些组件抽离出来作为单独项目，并复用到其他项目中去，一来可以避免重复造轮子，加快开发速度，二来维护效率也高，一些 bugfix 或者新特性直接在组件中更新，项目中只需要更新引用版本号即可，方便快捷。</p><p>跨项目的组件化方式也很多，开发阶段可以用 <code>npm link</code>，相当于在主项目的 <code>node_modules</code> 目录中创建了一个链向组件项目的软链，方便是挺方便，但是有几个问题。一是 Eslint 的目录递归检查是基于最终实际目录的，也就是说虽然 Eslint 默认排除 <code>node_modules</code> 目录，但它依然会对该目录中的软链项目进行检查，一旦组件项目的 Eslint 规则和主项目的 Eslint 不一致的话，主项目 Eslint 就没法通过，这个比较蛋疼，就得临时禁用 Eslint 或者修改组件项目的规则。作为组件项目应该保证少依赖，而且要服务多个项目，没办法保证匹配各个项目的 Eslint 规则。第二个问题是，通过 <code>npm link</code> 实现的依赖，不会体现在 <code>package.json</code> 中，如果通过 Docker 去部署，在 Docker 上是不知道你这个软链的，即便能够把软链写进去，在 Docker 中构建的时候，由于目录问题，也不能保证可以把组件项目文件拷过去。因此，在生产中，<code>npm link</code> 这个方案是没办法用的。</p><p>之前在国资的时候，采用的是通过组件项目的 git 地址来定位包，使用起来也很方便。如下：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">dependencies</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">example-component</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">git+ssh://git@xxx.com/example-component.git#v1.0.0</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  }
</span></span><span class="line" line="5"><span class="sP7_E">}
</span></span></code></pre><p>npm 支持通过 tag 来定位版本，组件项目发布的时候打一个 tag，对应的在项目中更新最后的版本号重新安装就可以完成升级了。由于之前没有用 Docker 部署，所以也没发现有什么问题。用了 Docker 的话，就会有些小问题。一般为了精简，都会采用基于 <code>Alpine</code> 的基础镜像，我目前的前端项目都是基于 <code>node:8-alpine</code> 来构建的。要知道，<code>Alpine</code> 镜像本身只有 4.8M，<code>node:8-apline</code> 也只有 20 几兆，非常精简。但是通过上面的这种方式，需要依赖 git，而 <code>Alpine</code> 显然是没有安装 <code>git</code> 的，也没有必要为了部署专门去安装一个 git。</p><p>于是便有了第三种方案，基于 Nexus 的 npm repository 方案。把包发布到 npm 上不太现实，大部分公司项目还是希望私有，和 maven 一样，Nexus 也支持 npm。创建一个 <code>hosted</code> 类型的 npm 仓库，例如 <code>npm-hosted</code>，具体教程自行谷歌。但是我们又不希望给该仓库增加过多的压力，不想把所有 npm 或者 yarn 默认的 registry 改为 Nexus，没必要，因为即便改为 Nexus，在国内的网络环境，还是 proxy 到 <code>https://registry.npm.taobao.org</code> 上去了，而且会在 Nexus 上留下大量缓存，也经过了两层的下载，我尝试过，很蛋疼，在 Nexus 没有缓存第一次去下载的时候，还会有很多失败。后来发现 npm 也支持直接通过 <code>tgz</code> 文件的方式来引用，这样就好办了。</p><p>首先执行 <code>npm adduser --registry=https://myregistry.example.com</code>，输入 Nexus 上具有上传 npm 包权限的用户名和密码，会在本地记录该用户的登录认证。然后在组件项目的 <code>package.json</code> 中加入：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">publishConfig</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">registry</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">https://myregistry.example.com/repository/npm-hosted/</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  }
</span></span><span class="line" line="5"><span class="sP7_E">}
</span></span></code></pre><p>然后执行 <code>npm publish</code>，组件项目就会被上传到 Nexus 了。</p><p>在具体项目中，需要用到改组件的时候，在 <code>package.json</code> 中这样引用：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">dependencies</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">vue-footer</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">https://myregistry.example.com/repository/npm-hosted/example-component/-/example-component-1.0.0.tgz</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  }
</span></span><span class="line" line="5"><span class="sP7_E">}
</span></span></code></pre><p>或者直接 <code>npm install https://myregistry.example.com/repository/npm-hosted/example-component/-/example-component-1.0.0.tgz --save</code>。</p><p>组件更新通过修改 <code>package.json</code> 里的版本号，然后 <code>npm publish</code>，然后具体项目中修改最后的版本号，重新安装即可。</p><p>另外由于这次都是用的最新的组件，也遇到了一个很大的坑。</p><p>我的项目都是基于 Nuxt.js 来搞的，Nuxt.js 的框架用起来更爽，ssr 性能也比 vue 原生的要高。有一个问题，组件项目在 webpack 打包的时候，默认是不支持 Nuxt 的 SSR 的，用了 <code>vue-style-loader</code> 之后，里面会有多个地方用到了 <code>document</code>。如果需要同时支持 Browser 和 SSR，需要再建一个 SSR 的 webpack config，将 <code>target</code> 设为 <code>node</code>，并且 <code>vue-loader</code> 的 <code>options</code> 中需要加入 <code>optimizeSSR: false</code>，这个是因为尤大在最近的某个版本中针对 SSR 做了一些优化，但是在 Nuxt 的 SSR 中会有些问题，具体可以参见 <a href="https://github.com/nuxt/nuxt.js/issues/2565" rel="nofollow">https://github.com/nuxt/nuxt.js/issues/2565</a> ，找了很久找到了这个 issue，追踪了下，尤大貌似在最近的 <a href="https://github.com/vuejs/vue/commit/9b22d86ab315a3c6061a6a4776eab1964304f92e" rel="nofollow">v2.5.17-beta.0</a> 中已经修复了这个问题，具体等 release 版发布之后再试下。在 Nuxt 中，创建一个 plugin，直接引用生成的 SSR 版本的文件即可。</p><pre><code><span class="line" line="1"><span class="sVHd0">import</span><span class="su5hD"> ExampleComponent </span><span class="sVHd0">from</span><span class="sjJ54"> '</span><span class="s_sjI">example-component/dist/ssr.js</span><span class="sjJ54">'
</span></span><span class="line" line="2"><span class="sVHd0">import</span><span class="su5hD"> Vue </span><span class="sVHd0">from</span><span class="sjJ54"> '</span><span class="s_sjI">vue</span><span class="sjJ54">'
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="su5hD">Vue</span><span class="sP7_E">.</span><span class="sGLFI">use</span><span class="su5hD">(ExampleComponent)
</span></span></code></pre><p>在 <code>nuxt.config.js</code> 的 <code>plugins</code> 中直接加入 <code>'~plugins/components-plugin.js'</code> 即可。网上大部分解决方案是引用的时候将组件项目设置为 <code>ssr: false</code>，其实是治标不治本，放弃了该组件在服务端的渲染，不可取。</p><h3 id="基于-docker-的快速部署">基于 Docker 的快速部署</h3><p>使用 Docker 也快 1 年了，基本上从开始用上 Docker 之后，就爱不释手了，大大缩短了发布时间，减少了运维成本。</p><p>目前我的项目都是部署在阿里云上，基于阿里云的容器集群方案，前面通过 SLB，后面横向部署多台机器。不多说，贴下 Dockerfile：</p><pre><code><span class="line" line="1"><span class="sw1J6">FROM</span><span class="su5hD"> node:8-alpine
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sw1J6">WORKDIR</span><span class="su5hD"> /app
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sw1J6">COPY</span><span class="su5hD"> package.json /app
</span></span><span class="line" line="6"><span class="sw1J6">COPY</span><span class="su5hD"> yarn.lock /app
</span></span><span class="line" line="7"><span class="sw1J6">RUN</span><span class="su5hD"> npm config set registry https://registry.npm.taobao.org && yarn config set registry https://registry.npm.taobao.org && yarn install
</span></span><span class="line" line="8"><span class="sw1J6">COPY</span><span class="su5hD"> . /app
</span></span><span class="line" line="9"><span class="sw1J6">RUN</span><span class="su5hD"> npm run build
</span></span><span class="line" line="10"><span emptyLinePlaceholder>
</span></span><span class="line" line="11"><span class="sw1J6">EXPOSE</span><span class="su5hD"> 6002
</span></span><span class="line" line="12"><span class="sw1J6">ENV</span><span class="su5hD"> SERVER_ENV $SERVER_ENV
</span></span><span class="line" line="13"><span class="sw1J6">CMD</span><span class="su5hD"> [</span><span class="s_sjI">"npm"</span><span class="su5hD">, </span><span class="s_sjI">"run"</span><span class="su5hD">, </span><span class="s_sjI">"start"</span><span class="su5hD">]
</span></span></code></pre><p>记得在 <code>.dockerignore</code> 文件中把 <code>node_modules</code> 目录加上，在 COPY 的时候不进行复制，而是在 docker 环境中重新获取。</p><p>在项目的 <code>package.json</code> 中配置好 <code>deploy</code> 命令：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">scripts</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">deploy</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">docker build -t registry.cn-hangzhou.aliyuncs.com/your-name/example-project:$npm_package_version -t registry.cn-hangzhou.aliyuncs.com/your-name/example-project:latest . && docker push registry.cn-hangzhou.aliyuncs.com/your-name/example-project:$npm_package_version && docker push registry.cn-hangzhou.aliyuncs.com/your-name/example-project:latest</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  }
</span></span><span class="line" line="5"><span class="sP7_E">}
</span></span></code></pre><p>在阿里云的容器镜像服务中建好镜像，便可以部署上去了。之前我是直接通过绑定 gitlab，在代码更新后，触发阿里云镜像服务在线构建，但是我们采用了私有的 npm 仓库的话，就比较麻烦了，还得配置 Nexus 账号，索性直接在本地构建，上传的网速也不是问题。在阿里云的容器服务中创建好应用，设置好触发器，在镜像更新后触发重新部署，就大功告成了。如果有多台机器，在调度配置中配置好“平滑升级”，会在同一服务的多个容器升级的时候，保证当前一批或者一个容器升级或者更新成功（健康检查成功）之后，再来更新或者升级下一批容器，也就是“滚动发布”。之后只需要 <code>npm run deploy</code>，喝杯咖啡 ☕️，就完成了所有的部署工作了。方便、快捷、不易出错。</p><p>最近看了篇文章，里面有句话：“这世界上肯定存在让人上瘾的代码”，提出了“技术多巴胺”这个说法。而此刻的我，就仿佛打了几针“技术多巴胺”一样，很兴奋，一点睡意都没有。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180419]]></title>
        <id>/posts/2018/diary-20180419</id>
        <link href="https://hadb.me/posts/2018/diary-20180419"/>
        <updated>2018-04-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我发现，洗澡的那段时间是我的贤者时间。]]></summary>
        <content type="html"><![CDATA[<p>我发现，洗澡的那段时间是我的贤者时间。</p><p>洗澡的时候，头脑很庆幸，即便深夜本身困意很浓，一旦克服了懒惰，开始了洗澡，大脑就开始飞快的转起来。洗澡这个事情本身并不会消耗太多的脑力，这个时候多余的脑力就开始追忆过去，思考人生了。</p><p>今天洗澡的时候，思考的就是关于洗澡的时候喜欢思考这件事情本身。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180420]]></title>
        <id>/posts/2018/diary-20180420</id>
        <link href="https://hadb.me/posts/2018/diary-20180420"/>
        <updated>2018-04-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天在一个非常低级的问题上耗了一天。]]></summary>
        <content type="html"><![CDATA[<p>今天在一个非常低级的问题上耗了一天。</p><p>起因是我想把 <code>www.monkeyrun.net</code> 设置为 http 自动 301 到 https 上，由于部署在阿里云的容器服务上，上面用的负载均衡，设置起来会稍微麻烦一点。</p><p>需要起一个 nginx 的容器，然后将 http 请求绑定到 nginx 的端口上，然后 nginx 再反向代理到 https 站点上</p><p>操作起来不复杂，但是发现一个很蛋疼的问题，设置好之后，80 端口和 443 端口的站点都访问不了了。</p><p>后来调试的时候，又发现，把 80 端口禁掉，443 端口竟然访问不了。</p><p>开始以为是负载均衡或者容器服务的配置问题，发工单给阿里的人，耗了几个小时，没给出啥有效的答案。</p><p>再后来发现，首页访问不了是因为服务端做了 api 请求，如果没有 api 请求的页面是可以访问的，所有的 api 请求返回 504 Gateway Timeout。</p><p>api 请求是通过 <code>http-proxy-middleware</code>，代理到 <code>api.www.monkeyrun.net</code> 上去了，一直以为是因为这个中间件的问题。甚至把 nuxt 项目的架构都调整了，直接换成了 <code>@nuxtjs/axios</code> 和 <code>@nuxtjs/proxy</code> 两个组件，结果发现问题依然存在。</p><p>后来恼怒了，猜测会不会是 https 的请求代理到 http 的 api 上的问题，打算给 api 也搞个 https 证书，干脆全部上 https 试试。然后发现了真正的问题所在！</p><p>那就是，api 和前端站点因为都在一个 Docker 集群上，也是公用的一个负载均衡 😂，停了 80 端口，api 就挂了，因为 api 还没启用 https，当然访问不了了！傻逼了！</p><p>最终配置成功！</p><p>前端域名解析到负载均衡 A（外网 IP）上，https: 443 端口监听容器集群的 9080 端口，http: 80 端口监听容器集群的 80 端口</p><p>容器集群的 80 端口是 nginx，nginx 的配置为：</p><pre><code><span class="line" line="1"><span class="smGrS">user </span><span class="su5hD"> nginx</span><span class="sP7_E">;
</span></span><span class="line" line="2"><span class="smGrS">error_log </span><span class="su5hD"> /var/log/nginx/error.log </span><span class="s39Yj">warn</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="smGrS">pid </span><span class="su5hD">       /var/run/nginx.pid</span><span class="sP7_E">;
</span></span><span class="line" line="4"><span class="sbsja">events</span><span class="su5hD"> {
</span></span><span class="line" line="5"><span class="smGrS">    worker_connections </span><span class="srdBf"> 65535</span><span class="sP7_E">;
</span></span><span class="line" line="6"><span class="su5hD">}
</span></span><span class="line" line="7"><span class="sbsja">http</span><span class="su5hD"> {
</span></span><span class="line" line="8"><span class="smGrS">    include </span><span class="su5hD">      /etc/nginx/mime.types</span><span class="sP7_E">;
</span></span><span class="line" line="9"><span class="smGrS">    default_type </span><span class="su5hD"> application/octet-stream</span><span class="sP7_E">;
</span></span><span class="line" line="10"><span class="smGrS">    log_format </span><span class="s39Yj"> main</span><span class="s_sjI">  '</span><span class="sjJ54">$</span><span class="su5hD">remote_addr</span><span class="s_sjI"> - </span><span class="sjJ54">$</span><span class="su5hD">remote_user</span><span class="s_sjI"> [</span><span class="sjJ54">$</span><span class="su5hD">time_local</span><span class="s_sjI">] "</span><span class="sjJ54">$</span><span class="su5hD">request</span><span class="s_sjI">" '
</span></span><span class="line" line="11"><span class="s_sjI">                      '</span><span class="sjJ54">$</span><span class="su5hD">status</span><span class="sjJ54"> $</span><span class="su5hD">body_bytes_sent</span><span class="s_sjI"> "</span><span class="sjJ54">$</span><span class="su5hD">http_referer</span><span class="s_sjI">" '
</span></span><span class="line" line="12"><span class="s_sjI">                      '"</span><span class="sjJ54">$</span><span class="su5hD">http_user_agent</span><span class="s_sjI">" "</span><span class="sjJ54">$</span><span class="su5hD">http_x_forwarded_for</span><span class="s_sjI">"'</span><span class="sP7_E">;
</span></span><span class="line" line="13"><span class="smGrS">    access_log </span><span class="su5hD"> /var/log/nginx/access.log  </span><span class="s39Yj">main</span><span class="sP7_E">;
</span></span><span class="line" line="14"><span class="smGrS">    keepalive_timeout </span><span class="srdBf"> 65</span><span class="sP7_E">;
</span></span><span class="line" line="15"><span class="smGrS">    gzip </span><span class="s39Yj"> on</span><span class="sP7_E">;
</span></span><span class="line" line="16"><span class="sbsja">    server</span><span class="su5hD"> {
</span></span><span class="line" line="17"><span class="smGrS">        listen </span><span class="srdBf">      80</span><span class="sP7_E">;
</span></span><span class="line" line="18"><span class="smGrS">        server_name </span><span class="su5hD"> www.monkeyrun.net</span><span class="sP7_E">;
</span></span><span class="line" line="19"><span class="sVHd0">        return</span><span class="srdBf"> 301</span><span class="su5hD"> https://</span><span class="sP7_E">$</span><span class="su5hD">host</span><span class="sP7_E">$</span><span class="su5hD">request_uri;
</span></span><span class="line" line="20"><span class="su5hD">    }
</span></span><span class="line" line="21"><span class="su5hD">}
</span></span></code></pre><p>后端域名解析到负载均衡 B（内网 IP）上，https: 443 端口监听容器集群的 9080 端口</p><p>node 将前端/api 的所有请求代理到后端域名上，由于都在阿里云容器集群内，所以内网可通。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Windows 存储池 RAID1 虚拟磁盘降级的问题]]></title>
        <id>/posts/2018/windows-storage-spaces-raid1-degraded</id>
        <link href="https://hadb.me/posts/2018/windows-storage-spaces-raid1-degraded"/>
        <updated>2018-04-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天早上看到监控邮件，git 和 nexus 服务都挂了，赶紧登录服务器看了下，果然，又是磁盘出问题了。存储池的一个 Mirror 的虚拟磁盘出现警告，提示已降级，其中一块物理磁盘显示“不正常：介质故障，IO 错误”，如下图：]]></summary>
        <content type="html"><![CDATA[<p>今天早上看到监控邮件，git 和 nexus 服务都挂了，赶紧登录服务器看了下，果然，又是磁盘出问题了。存储池的一个 Mirror 的虚拟磁盘出现警告，提示已降级，其中一块物理磁盘显示“不正常：介质故障，IO 错误”，如下图：</p><figure><img src="https://hadb.me/static/posts/2018/20180427.windows-storage-spaces-raid1-degraded/01.png"></img></figure><figure><img src="https://hadb.me/static/posts/2018/20180427.windows-storage-spaces-raid1-degraded/02.png"></img></figure><p>3 月份的时候，也出现过同样的问题，当时刚开始误以为是物理硬盘出了问题，但是检测下来磁盘 <code>S.M.A.R.T.</code> 数据是正常的，检查了坏道，也全部正常。后来把失败的物理磁盘拔下来准备寄回检修的时候，发现存储池的虚拟磁盘提示数据不完整，虽然是 RAID1，但是单靠剩下那一个硬盘还不足以恢复数据，尴尬。当时的处理办法是把两块磁盘都插上，虽然提示降级，但是数据是完整的，数据可以拷贝出来。拷贝出来后把整个存储池删了重建，再把数据拷进去，就正常了。当时也没多想。</p><p>今天又出现了同样的问题，出错的磁盘是另一块物理磁盘，这更加确定了该问题不是物理磁盘的锅，可能是存储池的问题。但是不想再通过上次的方法暴力解决。不过还是先把数据都拷贝出去了，以防万一。</p><p>尝试修复虚拟磁盘，没用，通过 <code>Get-StorageJob</code> 查看一直是 <code>Suspended</code> 的状态，直接用 <code>PowerShell</code> 执行 <code>Repair-VirtualDisk</code>，提示 <code>Repair-VirtualDisk : Not enough available capacity</code>（没有足够的可用容量）。</p><figure><img src="https://hadb.me/static/posts/2018/20180427.windows-storage-spaces-raid1-degraded/03.png"></img></figure><figure><img src="https://hadb.me/static/posts/2018/20180427.windows-storage-spaces-raid1-degraded/04.png"></img></figure><p>删除和重置物理磁盘都无法成功，提示需要先修复虚拟磁盘才行。</p><figure><img src="https://hadb.me/static/posts/2018/20180427.windows-storage-spaces-raid1-degraded/05.png"></img></figure><p>后来发现一个系统更新，<a href="https://support.microsoft.com/en-us/help/4093120/windows-10-update-kb4093120" rel="nofollow">KB4093120</a>，挺大的，去找了下更新内容，发现如下一条：</p><blockquote><p>Addresses an issue that may generate a capacity reserve fault warning during cluster validation or while running the Debug-StorageSubSystem cmdlet even though enough capacity is actually reserved. The warning is "The storage pool does not have the minimum recommended reserve capacity. This may limit your ability to restore data resiliency in the event of drive failure(s)."</p><p>修复了在群集验证期间或运行 Debug-StorageSubSystem cmdlet 时可能生成容量保留错误警告（即使实际上保留了足够的容量）的问题。 警告内容为“存储池没有建议的最低保留容量。 这可能会限制在驱动器发生故障时恢复数据弹性的能力。”</p></blockquote><p>可能是引起这个问题的原因，等待更新之后再尝试下。</p><p>更新之后发现问题依旧存在。沮丧。</p><p>突然想到没有足够的空间可能并不是指存储池的剩余空间，而是可能需要添加一个硬盘，作为临时备份的磁盘。于是找来一个移动硬盘，插上去，添加到存储池中，设置为 <code>AutoSelect</code>。果然，虚拟磁盘的修复开始了。等待修复结束，把提示“介质故障，IO 错误”的硬盘删除掉重新添加进来，然后通过 <code>Get-PhysicalDisk -SerialNumber 42H1YCTHF | Set-PhysicalDisk -Usage Retired</code> 把移动硬盘手动设置为 <code>Retired</code>，然后在物理磁盘的页面把移动硬盘从存储池删除掉，等待同步完成，就恢复成原样了。</p><p>以上便是该问题的解决办法，至于原因，暂时还不得而知。</p><p>出现这个错误之前，在系统事件里面，会有不少来源于 <code>Disk</code> 的警告和错误：</p><ol><li>事件 ID 为 <code>154</code> 的错误：<code>由于硬件错误，磁盘 0 (PDO 名称: \Device\00000055)的逻辑块地址 0xb5ae5d0 处的 IO 操作失败</code></li><li>事件 ID 为 <code>153</code> 的警告：<code>已在磁盘 0 (PDO 名称: \Device\000000a5)的逻辑块地址 0x0 处重试 IO 操作。</code></li><li>事件 ID 为 <code>51</code> 的警告：<code>传呼期间在设备 \Device\Harddisk4\DR4 上检测到一个错误。</code></li></ol><p>从注册表中 <code>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Disk\Enum</code> 这个目录里面可以看到磁盘 ID 对应的磁盘。</p><p>问题是硬盘检测下来是好的，没有坏道，并且上次出问题的硬盘，这次一次错误都没有。</p><p>有可能是 SATA 接口的问题，接触不良之类的。上次出问题之后两个硬盘都拆下来重新安装过，可能 SATA 接口换过了。这个具体后面再研究下。</p><h3 id="_2018-05-03-更新">2018-05-03 更新：</h3><p>买了两根绿联的 SATA 线换了下，目前为止没有再次发现这个错误。感觉果然是 SATA 线的问题。</p><h2 id="_1-存储池开机自动连接虚拟磁盘">1. 存储池开机自动连接虚拟磁盘</h2><p>最近修复存储池的问题，不知道为啥，有个虚拟磁盘可能是之前分离过，现在每次重启之后都需要手动连接，很麻烦，可通过 <code>Get-VirtualDisk</code> 命令查看虚拟磁盘状态，结果如下：</p><pre><code><span class="line" line="1"><span class="su5hD">FriendlyName ResiliencySettingName OperationalStatus HealthStatus IsManualAttach Size
</span></span><span class="line" line="2"><span class="smGrS">------------</span><span class="smGrS"> ---------------------</span><span class="smGrS"> -----------------</span><span class="smGrS"> ------------</span><span class="smGrS"> --------------</span><span class="smGrS"> ----
</span></span><span class="line" line="3"><span class="su5hD">Simple       Simple                OK                Healthy      False          </span><span class="srdBf">1</span><span class="su5hD"> TB
</span></span><span class="line" line="4"><span class="su5hD">Mirror       Mirror                OK                Healthy      True           </span><span class="srdBf">5</span><span class="su5hD"> TB
</span></span></code></pre><p>其中的 <code>IsManualAttach</code> 便是手动连接的属性，我们的这块名为 <code>Mirror</code> 的虚拟磁盘的 <code>IsManualAttach</code> 属性值为 <code>True</code>，所以不会自动连接了。</p><p>通过如下命令可将其设为自动挂载：</p><pre><code><span class="line" line="1"><span class="sptTA">Set-VirtualDisk</span><span class="smGrS"> -</span><span class="su5hD">FriendlyName Mirror </span><span class="smGrS">-</span><span class="su5hD">IsManualAttach </span><span class="s39Yj">$False
</span></span></code></pre><p>或者通过如下命令将所有手动挂载的磁盘设为自动挂载：</p><pre><code><span class="line" line="1"><span class="sptTA">Get-VirtualDisk</span><span class="smGrS"> |</span><span class="sptTA"> Where-Object</span><span class="sP7_E"> {</span><span class="s39Yj">$</span><span class="s_hVV">_</span><span class="su5hD">.IsManualAttach –eq </span><span class="s39Yj">$True</span><span class="sP7_E">}</span><span class="smGrS"> |</span><span class="sptTA"> Set-VirtualDisk</span><span class="su5hD"> –IsManualAttach </span><span class="s39Yj">$False
</span></span></code></pre><h2 id="_2-发现了服务器的一个大问题">2. 发现了服务器的一个大问题</h2><p>今天拆机发现硬盘很烫，想想不应该啊，摸了下 CPU 风扇，发现往里吹热气，往外的是冷风。😳！我的 CPU 风扇是两边都是风扇的，安装的时候没注意，竟然装反了！导致一直外往里吸气。难怪上面全是灰！！！尴尬了。赶紧淘宝买了一支 7783 的导热硅脂，重新安装一下，顺便清个灰。</p><p>安装前后的数据对比：</p><p><strong>风扇装反的情况下</strong></p><ol><li>正常开机 6 小时，CPU 温度 38℃，硬盘温度 40℃。</li><li>挖矿软件设置 60%的 CPU 使用率，挖矿 10 分钟左右，CPU 温度 62℃ 左右，硬盘温度 42℃。</li><li>挖矿软件设置 60%的 CPU 使用率，挖矿 60 分钟左右，CPU 温度 64℃ 左右，硬盘温度 45℃。</li><li>挖矿软件设置 60%的 CPU 使用率，挖矿 03 小时左右，CPU 温度 65℃ 左右，硬盘温度 48℃。</li></ol><p>重新换过导热硅脂，风扇装正的情况下：</p><ol><li>正常开机 10 分钟，CPU 温度 34℃，硬盘温度 31℃。</li><li>挖矿软件设置 60%的 CPU 使用率，挖矿 10 分钟左右，CPU 温度 55℃ 左右，硬盘温度 36℃。</li><li>挖矿软件设置 60%的 CPU 使用率，挖矿 60 分钟左右，CPU 温度 58℃ 左右，硬盘温度 42℃。</li></ol><h3 id="_2018-05-04-更新">2018-05-04 更新：</h3><p>解决完事件查看器里面其他几个错误，然后重启之后，发现再次出现 Disk 51 和 Disk 154 的错误。不过频率很低，可能还是由于接触的问题。</p><h3 id="_2018-05-23-更新">2018-05-23 更新：</h3><p>今天发现服务器上的服务挂了很久，登上去看，发现 D 盘不见了，尝试重启，等待了很长时间重启完成，可能在更新系统。重启进入后发现 D 盘出现了，但是硬盘一直出现不断重启的声音，事件里面每隔 5-6 秒钟就会出现一个 <code>已在磁盘 4 (PDO 名称: \Device\Space2)的逻辑块地址 0x1f52d368 处重试 IO 操作。</code> 的警告，并且伴随着错误 <code>由于硬件错误，磁盘 2 (PDO 名称: \Device\0000003e)的逻辑块地址 0xaa1dde8 处的 IO 操作失败。</code>，磁盘 1、2、4 都有警告，磁盘 1、2 有错误。之后手动拔了磁盘 ID 为 2 的物理磁盘（W5033A6K）的 SATA 线，然后重新插上，发现没有重启的声音了，错误警告也停止了，但虚拟磁盘出现“不正常，未知”的状态。感觉不像是接触不好的问题，因为我动了线没效果，拔了重插才有效果。看了下之前的日志，磁盘 0 是固态硬盘，莫非是系统盘的锅？固态硬盘检测也没有坏道。</p><h3 id="_2018-06-08-更新">2018-06-08 更新：</h3><p>前几天重装了系统，仍然出现了问题。6 号 18:03:06、6 号 20:01:13、7 号 6:16:47、7 号 6:17:11、7 号 6:20:22 都出现了 Disk 154 的错误。根据之前多次出错的事件来看，凌晨 6 点多是报这个错误的高发期，得先搞明白为啥，这个时间段在做什么。</p><p>网站报警显示 8 号 2 点 06 分挂了，Hyper-V 虚拟机已经关机了，虚拟机返回的错误如下：</p><pre><code>“ubuntu-01”: 虚拟硬盘“D:\虚拟机\Virtual Hard Disks\ubuntu-nas_E27174DE-7800-4F8C-918C-097516949C09.avhdx”检测到可恢复的错误。当前状态: 磁盘已满。(虚拟机 ID 3695BB9F-3BAA-4D94-938F-0C197B9AA34D)
</code></pre><p>应该是存储空间无法写入导致的。</p><h3 id="_2018-06-26-更新">2018-06-26 更新：</h3><p>前几天重新买了个主板，安装时发现硬盘的电源线插口有点鼓起来变形了，感觉是温度高热胀冷缩的原因。当时手动压扁了安装了。稳定了几天没问题，今天又出问题了。现在有点怀疑是这个电源线的问题。绿联重新买了几根线，等到了再试试。</p><blockquote><p>参考文章：</p><ul><li><a href="https://answers.microsoft.com/zh-hans/windows/forum/windows8_1-hardware/%E4%BA%8B%E4%BB%B6id129%E5%92%8C153/3d4be0c1-d67a-4c81-8367-acea9873f46d" rel="nofollow">https://answers.microsoft.com/zh-hans/windows/forum/windows8_1-hardware/%E4%BA%8B%E4%BB%B6id129%E5%92%8C153/3d4be0c1-d67a-4c81-8367-acea9873f46d</a></li><li><a href="http://www.pwrusr.com/system-administration/solved-warning-the-io-operation-at-logical-block-address-for-disk-was-retried" rel="nofollow">http://www.pwrusr.com/system-administration/solved-warning-the-io-operation-at-logical-block-address-for-disk-was-retried</a></li><li><a href="https://blogs.msdn.microsoft.com/ntdebugging/2013/04/30/interpreting-event-153-errors/" rel="nofollow">https://blogs.msdn.microsoft.com/ntdebugging/2013/04/30/interpreting-event-153-errors/</a></li><li><a href="https://www.wintips.org/how-to-fix-disk-event-51-an-error-detected-on-device-during-paging-operation/" rel="nofollow">https://www.wintips.org/how-to-fix-disk-event-51-an-error-detected-on-device-during-paging-operation/</a></li><li><a href="https://support.microsoft.com/zh-cn/help/2806730/disk-event-id-154-the-io-operation-failed-due-to-a-hardware-error" rel="nofollow">https://support.microsoft.com/zh-cn/help/2806730/disk-event-id-154-the-io-operation-failed-due-to-a-hardware-error</a></li></ul></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20180607]]></title>
        <id>/posts/2018/diary-20180607</id>
        <link href="https://hadb.me/posts/2018/diary-20180607"/>
        <updated>2018-06-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前因为新房子在装修，加上我手头项目时间紧，儿子放在老家给我爸妈带。前几天老家农忙，把儿子从老家带回了上海。]]></summary>
        <content type="html"><![CDATA[<p>之前因为新房子在装修，加上我手头项目时间紧，儿子放在老家给我爸妈带。前几天老家农忙，把儿子从老家带回了上海。</p><p>来了上海之后，儿子在家待不住，老婆天天带去游乐场玩，开心得一塌糊涂。在游乐场，比较安全，敢放开手让他自己去走，去跑，这几天给我最大的感受就是，儿子走路的能力强了很多，而且很喜欢走。</p><p>在家已经能够从另一个房间一个人摇摇晃晃走到我的房间，然后大喊一声“爸爸”，然后跑到我的椅子旁边，要拍打我的键盘。</p><p>最近手上有做不完的活，真想再多花点时间陪陪儿子。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20181009]]></title>
        <id>/posts/2018/diary-20181009</id>
        <link href="https://hadb.me/posts/2018/diary-20181009"/>
        <updated>2018-10-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[踏出高铁门的一刹那，感受到了来自北方的寒意。]]></summary>
        <content type="html"><![CDATA[<p>踏出高铁门的一刹那，感受到了来自北方的寒意。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20181018]]></title>
        <id>/posts/2018/diary-20181018</id>
        <link href="https://hadb.me/posts/2018/diary-20181018"/>
        <updated>2018-10-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[困，累，下班到家，就躺床上睡着了。刚刚爬起来，继续干活。真是累。]]></summary>
        <content type="html"><![CDATA[<p>困，累，下班到家，就躺床上睡着了。刚刚爬起来，继续干活。真是累。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[老友聚餐]]></title>
        <id>/posts/2018/dinner-with-old-friends-20181023</id>
        <link href="https://hadb.me/posts/2018/dinner-with-old-friends-20181023"/>
        <updated>2018-10-23T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<blockquote><p>今日一聚，看似无足轻重，在日记上，相当沉闷和平凡，或许就是命运巨变的开始。</p></blockquote><p>约了 N 久的饭局，终于吃上了。</p><p>上次和车钥匙的这帮兄弟一起聚餐，还是去年了吧。依稀记得那是去牛拜之前，想拉他们一起。</p><p>聊了很多，互相吐槽，谈论事业发展瓶颈与出路，谈论育儿经验，谈论买房。如今大家已经散布在各个公司，方向和岗位也都发生了一些变化，不变的还是曾经那份情谊。没想到，当初的小屋子里，竟诞生出如此的情谊，超越了团队，超越了公司，超越了时间，超越了距离。不禁有些感动，有这样一群兄弟，纵然不在一起工作，偶尔群里聊聊天南海北，偶尔聚聚餐，岂不快哉！</p><p>喝了些酒，我几乎从不喝酒的人，今晚喝了一杯多扎啤。发现，也没那么难喝。回来发现，丝毫不影响工作，甚至感觉代码写起来 bug 都没了，也突然有了想写点东西的冲动，感觉这种冲动是酒精的作用。回想了下，好像离上次写东西，已经距离好几个月了。</p><p>“一壶浊酒喜相逢。古今多少事，都付笑谈中。”这句用来形容今晚，再合适不过。</p><p>回来的的士上，微信跟现在公司的两位同事吐槽了工作上的压力，加之今日聚餐之喜悦以及刚刚很快就搞完了外包今日的任务，开着音乐，顿时感觉压力全无，好好享受会这酒后的快乐时光，莫非这就是飞哥所说的喝酒后的“贤者时间”。</p><p>本已决定再也不打工的我，几个月前被 Mylo 又“逼娼为良”，开始了打工。经过几个月的工作，我发现，我还是不适合打工。受不了这种委屈，自己当老板当惯了，突然被使唤来使唤去，还没好脸色，干着最累的活，拿着无法满足的薪资和职位，要求做着 TL 和 PO 的活，却把我排除在 TL 队伍之外，连虚的 PO 都没有。辛苦见不到回报，封闭开发两三个星期的工单项目，最终整个团队给了 500 块，智慧商场项目一期最晚加班到 4 点，最后一人发了 200 块，这点钱真不如不发，以“下次加薪狠狠加”来作为大饼，就好比挂在驴子面前的萝卜。每个项目都是赶鸭子上架，为了满足各种政治任务而完成，根本没有时间打磨，做的东西都是东拼西凑，写的代码我自己都不想再维护。</p><p>相比较而言，还是做 freelancer 爽，更多的钱，更高的地位，更自由的生活。</p><p>今年，已经完成了年初的目标，证明了自己 freelancer 这条路是可行的，至少目前为止，兼职的情况下，每个月外包的收入都远超工资收入。家里人希望一边拿着固定工资，一边再做着私活，两份的钱，殊不知，如今的我，已经快要到极限了，连续几个月每晚只睡 4~5 个小时，最近几个周末偶尔放松下，基本上都是烂睡如泥，睡得天昏地暗。睡到半夜还得爬起来继续做外包。</p><p>我是个有脾气的人。已决定最多到年底，拿完年终奖跑路。干到年底，我想这些赶鸭子上架的项目应该也差不多了，帮 Mylo 把这些项目都干完，也算有始有终了，后面享福也好，升职加薪也好，都与我无缘。倘若后面还是各种赶鸭子上架的项目，更不再值得我留下。一起度过最艰难的日子，对我来说，已经够意思了。落子无悔，我想我迟早会下这步棋，何不早做了结。现在这个职位，顶了天，能给我带来什么？除非真能有机会孵化内部项目，让我独立管一个子公司，盈利部分与集团分成，那我觉得还有点动力。不然，以我能带来的价值，我没有理由不单干。Freelancer 也好，招人继续做项目也好，自己能把控全局，那种下棋的成就感不是当颗棋子可以获得的。纵使是一颗重要的棋子，也只是一颗棋子。</p><p>我的世界，我的未来，我还是想自己掌握。不愿平平凡凡走完一生，注定要走不同的路。纵使路途艰难，但我前些年已打拼出一条路，没有理由放弃，几个月的尝试，我发现，离我的目标渐行渐远，是时候放弃安稳的 Plan A 了。</p><p>最近外包项目的进度有些落下了，明日开始重新调整工作重心，完成本职工作为主，必要的时候与 Mylo 沟通。要把外包进度赶上。</p><p>今日一聚，看似无足轻重，在日记上，相当沉闷和平凡，或许就是命运巨变的开始。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于梦想]]></title>
        <id>/posts/2018/about-dream</id>
        <link href="https://hadb.me/posts/2018/about-dream"/>
        <updated>2018-10-25T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<blockquote><p>所谓梦想，不是你在睡觉时梦到的东西，而是可以令你不睡觉去努力的东西。</p></blockquote><p>每个人都可以有梦想。食不果腹的流浪汉，可以梦想未来能够天天嘬着可乐，啃着鸡腿，过上衣食无忧的日子。进城打工的青年，可以梦想未来有一天能够买得起属于自己的一套房。</p><p>梦想，对于每个人的高度是不一样的。有了一定的物质基础，才会去追求更远大的梦想。而这个世界，大多数的人，尽管有着各种各样的梦想，但是被物质生活所迫，并没有能力去追求内心的这些梦想，费尽一生，最终也只是被生活所迫，过着平凡的生活，然后离开这个世界。</p><p>不得不说，关于梦想，很小的时候，我就树立了一个很遥远的目标——比尔·盖茨。</p><p>第一次听说比尔·盖茨，是我小学的时候。那时候对比尔的认知是，世界首富，创办了微软，而打开电脑的那面旗子就是微软。学校里一个星期大概也就一堂微机课，那时候君叔叔家有电脑，一有时间就去找君叔叔学电脑。</p><p>上初中后，拥有了属于自己的一台笔记本。那时家里人谈网色变，我只能去学校蹭网下载各种东西回来离线看。记得在一个夜晚，昏黄的灯光下，我看完了一本电子版的比尔·盖茨传记。看完后，更加崇拜他了。也是从那时起，我的电脑水平开始远超同龄人。那时候网上看到好的网站都会下载下来，回来慢慢看。一次偶然的机会，不小心通过记事本打开了，看到了一堆堆字符，其中有网页中的文字。研究了一个晚上，就已经摸索出怎么修改网页里面的东西了。那时迷恋于用 VB.NET 做各种好玩的程序，最得意的，莫过于在数学课上，学了函数之后，用 VB.NET 画出了几种函数图，并被老师拿去当演示教材了。2006 年 06 月 28 日，我设计了未来想创建的公司的名字和 Logo，“Midsoft”，希望比“Microsoft”大一点。</p><p>高中时，在学校创办了“好易思特 HAOest”，专门打印、贩卖各种学习资料，那时我爸给我买了一台兄弟牌打印机。“好易思特”，基本上传遍了同年级的所有班级。那时，我会在打印课程表的时候，免费给我们班的所有同学打印一份，发放给他们。因为我掌握了 PDF 的缩放打印功能，并且以我小学就拿到 Word 证书的排版能力，我打印出的课程表，总是最受欢迎的，甚至其他班级的老师都会来请我帮他们班级打印课程表，贴在每个学生的桌子角落上。那时，我的每份产品上，都会印上“好易思特 HAOest”的商标。所有东西都像模像样，甚至有产品编号、条形码、正版认证等，甚至还举办了一次新闻发布会。</p><p>上大学后，毫无限制的上网，让我真正开始走上了编程之路。并且在老师的引荐下，开始接外包做。</p><p>2016 年 03 月 04 日，注册了“猿奋网络”的公司，正式开始了创业之旅。自从外包收入比工资收入高之后，我发现，不管哪个公司多好的愿景把我“忽悠过去”，呆半年之后，我都会厌倦，都会有各种不爽，要么就是加班太多，导致性价比降低；要么就是职位上不去，能力再强也只是个小兵，没有存在感；要么就是做的东西，没有成就感。由于不需要考虑物质上的问题，所以我在辞职这件事情上，没有太多顾虑。当然，我一般都会选择在闲下来的时候提出辞职，虽然最想辞职的时候往往是最忙的时候，但如果在这个时候提出离职，未免太不够意思，我一般都会在结束后再提出。而最近，我也处在这样的状态中。</p><p>人总会为自己的行为寻找理由。我给自己找的理由是，梦想。</p><p>“打工是不可能打工的，这辈子都不可能打工的。”虽然这句话是出于盗贼之口，但他却是很多创业者的心声。所谓工作，大部分人都是在打工，也就是受雇于人，替别人工作，作为社会的一颗螺丝钉，发挥着自己有限的作用，而收取薪酬。这样的工作，与公司的成果相比，所获得的成就感很低，这也是我一直无法接受打工的原因。作为一个二十多岁的年轻人，纵使有能力，也很难获得很高的职位。对于我这种从小就想当老板的人来说，这种打工的工作，总归是无法留得住我。</p><p>所谓梦想，不是你在睡觉时梦到的东西，而是可以令你不睡觉去努力的东西。</p><p>最近的这几个月，几乎每天晚上都是两三点、三四点、甚至五点多才睡觉，而令我这么晚睡的原因，就是我的梦想。尽管，完成一个外包项目并不是我的梦想。但，完成一个个外包项目，我会离我的梦想越来越近。</p><p>我有时候会问自己，我的梦想到底是什么？</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20181105]]></title>
        <id>/posts/2018/diary-20181105</id>
        <link href="https://hadb.me/posts/2018/diary-20181105"/>
        <updated>2018-11-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[第一时间升级了 Mojave，发现往 NAS 上的 Time Machine 备份时总是导致重启。]]></summary>
        <content type="html"><![CDATA[<p>第一时间升级了 Mojave，发现往 NAS 上的 Time Machine 备份时总是导致重启。</p><p>今天找到了一些解决方案，尝试一下。相关链接如下：</p><p><a href="https://discussions.apple.com/thread/8561149" rel="nofollow">https://discussions.apple.com/thread/8561149</a></p><p><a href="https://forums.macrumors.com/threads/resolved-time-machine-not-working-on-mojave.2145515/" rel="nofollow">https://forums.macrumors.com/threads/resolved-time-machine-not-working-on-mojave.2145515/</a></p><p><a href="https://linustechtips.com/main/topic/977820-time-machine-failing-since-mojave/" rel="nofollow">https://linustechtips.com/main/topic/977820-time-machine-failing-since-mojave/</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20181109]]></title>
        <id>/posts/2018/diary-20181109</id>
        <link href="https://hadb.me/posts/2018/diary-20181109"/>
        <updated>2018-11-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[11:09 到达]]></summary>
        <content type="html"><![CDATA[<p>11:09 到达</p><ul><li>紫色带子 工作人员</li><li>黄色带子 参展商</li><li>绿色带子 交易团 专业观众</li><li>蓝色带子 工作人员，安保</li><li>蓝橙彩色 参展商</li></ul><p>5 号馆进</p><p>11:16 过安检</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20181210]]></title>
        <id>/posts/2018/diary-20181210</id>
        <link href="https://hadb.me/posts/2018/diary-20181210"/>
        <updated>2018-12-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近公司项目需要写一个 Windows 客户端，需要调用 SR300 的摄像头驱动，所以得基于 .net 写，不能用 electron 这样的方案。本来打算老老实实用 WPF，但是界面实在有点复杂，用 WPF 写要累死，调研了一下，发现了 Neutronium 这个好东西。]]></summary>
        <content type="html"><![CDATA[<p>最近公司项目需要写一个 Windows 客户端，需要调用 SR300 的摄像头驱动，所以得基于 .net 写，不能用 electron 这样的方案。本来打算老老实实用 WPF，但是界面实在有点复杂，用 WPF 写要累死，调研了一下，发现了 Neutronium 这个好东西。</p><p>使用了几天，遇到了不少坑，第一个比较蛋疼的就是页面在 app 中可以正常渲染，在浏览器中调试总是拿不到绑定的数据，一直报错。无奈，跟了下源码，发现罪魁祸首是这一行：</p><pre><code><span class="line" line="1"><span class="su5hD">Object</span><span class="sP7_E">.</span><span class="sGLFI">assign</span><span class="su5hD">(vm</span><span class="sP7_E">,</span><span class="sP7_E"> {</span><span class="skxfh"> ViewModel</span><span class="sP7_E">:</span><span class="sP7_E"> {</span><span class="skxfh"> Router</span><span class="sP7_E">:</span><span class="sP7_E"> {</span><span class="skxfh"> BeforeResolveCommand</span><span class="sP7_E">:</span><span class="s39Yj"> null</span><span class="sP7_E"> }</span><span class="sP7_E"> }</span><span class="sP7_E"> }</span><span class="su5hD">)
</span></span></code></pre><p><code>Object.assign</code> 的时候，把 ViewModel 里面的其他东西都替换没了。</p><p>前往 GitHub 上看，发现作者 4 小时前刚刚更新 <code>1.4.0</code>，里面已经把这个修复了，😓</p><p>被改成了以下代码：</p><pre><code><span class="line" line="1"><span class="su5hD">vm</span><span class="sP7_E">.</span><span class="su5hD">ViewModel</span><span class="sP7_E">.</span><span class="su5hD">Router </span><span class="smGrS">=</span><span class="sP7_E"> {</span><span class="skxfh"> BeforeResolveCommand</span><span class="sP7_E">:</span><span class="s39Yj"> null</span><span class="sP7_E"> }
</span></span></code></pre><p>果断先升级 <code>1.4.0</code> 再说。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[再见，2018]]></title>
        <id>/posts/2018/good-bye-2018</id>
        <link href="https://hadb.me/posts/2018/good-bye-2018"/>
        <updated>2018-12-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是 2018 年的最后一天，终于有时间写些东西。]]></summary>
        <content type="html"><![CDATA[<p>今天是 2018 年的最后一天，终于有时间写些东西。</p><p>有史以来最忙碌的一年，最辛苦的一年，睡眠最少的一年，也是收入最高的一年。</p><p>平均睡眠时间小于 6 小时，GitLab 活跃天数接近 300 天，各种收入加起来超过 60 万。</p><p>回顾 2018 年，值得关注的事件大概如下：</p><ul><li>1 月，路灯项目、岭界建筑官网项目</li><li>3 月，Botanikiss 的官网项目</li><li>5 月，拿到了牛拜的工资和赔偿款；接到了机配云的项目</li><li>6 月，接到了智慧 e 充的项目；入职红星美凯龙</li><li>7 月，接到了上大的外包项目</li><li>10 月，搬到新房子住；获得了红星美凯龙第二届黑客马拉松冠军</li><li>11 月，买了 PS4</li><li>12 月，老家的院墙终于砌起来了</li></ul><p>其中机配云和智慧 e 充的项目一直持续到目前，基本上每个月都在开发。</p><p>期间也接了一些短平快的项目。</p><p>上半年，主要在帮忙家里搞装修，以及一些小项目。</p><p>下半年，几个项目同时开搞，白天的时间又得贡献在红星，导致睡眠严重不足，基本每天都是两三点睡觉，甚至有多次四五点才睡觉。Apple Watch 统计到的平均睡眠时间少于 6 小时。</p><p>今年，没有完全按照年初的计划，中途还是去打工了。主要原因还是不会拒绝。其实当时是不想过去的，当时机配云还没搞完，手头很忙，拒绝了几次面试，但是 Mylo 三番五次喊我过去看看，又不好意思拒绝。后面就直接给我面试了，薪资给的不高，当时的想法是学学人工智能，给自己未来接外包多一条路，就过去了。</p><p>经过半年的时间，发现做的东西并不是机器学习，还是各种业务项目，加班也多，压榨得很厉害。没有太多成长。</p><p>明年，找个机会跑路。太累，不太值得。还是要多陪陪老婆孩子。</p><p>但，2018 年，至少有了两个稳定的外包项目，能够带来不错的收入，也算是完成了年初定下的小目标：</p><blockquote><p>2018 年，争取能够有稳定的项目、稳定的资金来源，证明自己，不再需要去打工，可以在自己的 Plan B 上越走越远，变成 Plan A。</p></blockquote><p>好了，2018 年总结的差不多了，2019 年，需要制定一些小目标了 。</p><ul className="contains-task-list"><li className="task-list-item"><input checked disabled type="checkbox"></input> 出国旅游一次</li><li className="task-list-item"><input disabled type="checkbox"></input> 锻炼减肥，目标减到 70kg 以下</li><li className="task-list-item"><input disabled type="checkbox"></input> 荒野大镖客通关</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 平均睡眠时间达到 6 小时</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 完成 10 篇可发布的文章</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[你好，2019]]></title>
        <id>/posts/2019/hello-2019</id>
        <link href="https://hadb.me/posts/2019/hello-2019"/>
        <updated>2019-01-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨晚发烧了，39 度多，一夜睡得很难受。]]></summary>
        <content type="html"><![CDATA[<p>昨晚发烧了，39 度多，一夜睡得很难受。</p><p>但昨天接到一个新项目，明天坐飞机去广州谈。张志伟介绍的，关于共享单车的，有个老板想低价收二手车，然后用章老师库存的智能锁，改装一下自己搞个共享单车品牌。</p><p>丈人不放心我一个人去广州，所以老婆和孩子也一起过去，退了之前的机票，重新买了同一班可以带婴儿的航班。第一次带儿子坐飞机，还是要参与下。</p><p>今天花 17600 元购买了 yuanfen.net，心仪了一两年了，还是中介靠谱，本来预期 3 万，估计报了个 1 万，中介最终谈到 17600。还算比较满意，可以接受。作为猿奋网络的官网域名再合适不过了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ghost Docker 部署方式配置邮箱]]></title>
        <id>/posts/2019/ghost-docker-mail-config</id>
        <link href="https://hadb.me/posts/2019/ghost-docker-mail-config"/>
        <updated>2019-01-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[很久没登录博客了，今天登录时，发现忘记密码了，之前都是自动登录的，估计是自动登录过期了，没办法自动登录了，试了几次，账号被锁定了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190109.ghost-docker-mail-config/cover.png" alt="封面" /><p>很久没登录博客了，今天登录时，发现忘记密码了，之前都是自动登录的，估计是自动登录过期了，没办法自动登录了，试了几次，账号被锁定了。</p><p>尝试找回密码，发现好像没有配置 SMTP 邮箱。于是找了下配置项，用 Docker 部署的话，在编排模板的 environment 中添加如下配置：</p><pre><code>- 'mail__transport=SMTP'
- 'mail__from=Ghost <xx@xxx.com>'
- 'mail__options__host=smtp.qiye.aliyun.com'
- 'mail__options__secureConnection=true'
- 'mail__options__port=465'
- 'mail__options__auth__user=xx@xxx.com'
- 'mail__options__auth__pass=YOUR_PASSWORD'
</code></pre><p>重新部署一下即可。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于打工]]></title>
        <id>/posts/2019/about-work</id>
        <link href="https://hadb.me/posts/2019/about-work"/>
        <updated>2019-01-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[“打工是不可能打工的，这辈子不可能打工的。”]]></summary>
        <content type="html"><![CDATA[<p>“打工是不可能打工的，这辈子不可能打工的。”</p><p>这句话，无疑可以算得上是这几年的网络流行语之一。之所以能够有这么高的流行度，除了偷车男子令人匪夷所思和啼笑皆非的想法外，还因为这句话道出了很多人的心声。</p><p>大部分人都经历过打工，很多人也都有着相似的经历。挤地铁；赶公交；在地铁站换乘的路上边走边啃着早饭；在风雨交加的夜晚，扛着感冒，强撑着伞从地铁站走回家；夜深人静，才从公司加班回到家，才有了属于自己的一点点时间。</p><hr></hr><p>之前没写完，只写了这么一点。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190127]]></title>
        <id>/posts/2019/diary-20190127</id>
        <link href="https://hadb.me/posts/2019/diary-20190127"/>
        <updated>2019-01-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[近期，阿里云上有一个负载均衡遭受了异常的流量，host 均来自 mobile.12306.cn，疑似和 12306 或者抢票软件有关，所有请求都返回 403，依旧锲而不舍地在请求。这个周末达到有史以来的峰值，一上午不到就达到 1000 多万次请求了，SLB 流量也上升了，实在顶不住，只能换 IP 了。]]></summary>
        <content type="html"><![CDATA[<p>近期，阿里云上有一个负载均衡遭受了异常的流量，host 均来自 <code>mobile.12306.cn</code>，疑似和 12306 或者抢票软件有关，所有请求都返回 403，依旧锲而不舍地在请求。这个周末达到有史以来的峰值，一上午不到就达到 1000 多万次请求了，SLB 流量也上升了，实在顶不住，只能换 IP 了。</p><p>重新申请了一个 SLB，把监听都创建好，把所有涉及到的域名解析一个个改成新 IP。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Hello, Osmo Pocket!]]></title>
        <id>/posts/2019/hello-osmo-pocket</id>
        <link href="https://hadb.me/posts/2019/hello-osmo-pocket"/>
        <updated>2019-03-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天去新天地的大疆旗舰店买了 Osmo Pocket，过程用手机录制并剪辑了第一个 Vlog。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190302.hello-osmo-pocket/cover.jpg" alt="封面" /><p>今天去新天地的大疆旗舰店买了 Osmo Pocket，过程用手机录制并剪辑了第一个 Vlog。</p><a href="//player.bilibili.com/player.html?aid=45116878&cid=79008801&page=1" target="_blank" rel="noopener noreferrer">点击查看</a>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[折腾了一下博客的新主题]]></title>
        <id>/posts/2019/new-blog-theme</id>
        <link href="https://hadb.me/posts/2019/new-blog-theme"/>
        <updated>2019-03-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[心仪这个主题很久了，第一次在 themeforest 上购买主题，感觉还是挺爽的，比自己从零开始做一个要省事多了。😎]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190306.new-blog-theme/cover.png" alt="封面" /><p>心仪这个主题很久了，第一次在 themeforest 上购买主题，感觉还是挺爽的，比自己从零开始做一个要省事多了。😎</p><p>自定义了一些样式，有空还需要汉化一下。</p><p>睡觉🌛</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于创造价值的思考]]></title>
        <id>/posts/2019/thoughts-about-value</id>
        <link href="https://hadb.me/posts/2019/thoughts-about-value"/>
        <updated>2019-03-08T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190308.thoughts-about-value/cover.jpg" alt="封面" /><blockquote><p>在「首席科学家划水摸鱼研究群」一番讨论之后所写</p></blockquote><p>我们这个年纪的人，是不是到了一个迷茫的阶段。</p><p>码代码，能力已经到了一定的境界了，再突破有点难了，就像下面的成长曲线：</p><figure><img src="https://hadb.me/static/posts/2019/20190308.thoughts-about-value/01.png"></img></figure><p>如何才能突破这个成长曲线，获得跨越式的进步？</p><p>是不是需要逐渐放弃一些写代码的快乐，而去追求一些其他方面的进步？</p><p>是不是要去容忍自己的手越来越生疏，技术越来越落后？</p><p>是不是得在看到新技术时，控制想去深入学习的欲望？</p><p>而去把时间精力放到其他方面，比如溜须拍马，比如搞好上下级关系，为未来布局。</p><p>我们这样整天写代码，前途在哪里？为啥别人不写代码能够创造更大的价值？</p><p>是不是得把代码实现这种可以量化的工作交给更年轻的人去做，而我们靠着多年的项目经验，去做更有挑战性、无法量化的东西？</p><p>妈的，这么一想，好像我走错方向了。太投入于写代码这件事了。虽然这是我的爱好，并且当爱好就是工作的时候，工作起来会非常爽。但是我工作的目的，我写代码的目的又是为了什么？我太过沉溺于写代码这件事情本身，而忘了我想要的是什么。我要的并不是代码写得好，也并不是年薪百万。而是成功所带来的成就感，它可以通过很多方式来实现，比如赚很多的钱，比如研发一个改变世界的产品，比如创建一个伟大的公司，等等……</p><p>在互联网行业，大部分写代码的都是底层的劳动力。跟工地干活是一个意思，尽管盖起了大厦有成就感，但那毕竟不是你自己的大厦，成就感还是很有限的。世人也只会记住这个大厦的设计师、拥有者，而不会记得当初一砖一瓦干活的工人。</p><p>虽然我以前一直很不屑用码农、搬砖这样的词汇，认为我做的事情很有艺术感，是在创造，怎么能用搬砖来形容呢？我这么牛逼又怎么能用码农来形容呢？但是如今，越来越觉得我的工作其实也不过如此。当多年之后，回顾我现在做的这些事情的时候，又会发现，我从中创造了多少的价值呢？</p><p>要量化创造的价值，一个最容易的方法就是赚了多少钱。码农们都在用时间换金钱，金钱只能是线性增长的，想多挣钱，只能靠提高单价或者多投入时间，单价有瓶颈，人的精力更是有限的。得换换思路，怎么才能跳出线性增长的框架，创造指数级的价值。</p><p>我想解放自己的时间，实现可复制的挣钱模式。现在不管是上班还是接外包，都还是一份时间换一份钱。不像工厂，可以加设备，加工人，实现成倍的效率。</p><p>我只能靠压榨自己，来挣更多的钱，但我现在感觉，已经到极限了。我终于想明白，我一直感受到被压榨，其实不是来自公司，不是来自外包项目，而是来自我自己，我是自己在压榨自己。</p><p>其实我刚开始是想问，如果你们感受到被压榨，要怎么安慰自己。现在才恍然大悟，我是自己在压榨自己。</p><p>如果是别人压榨我，我完全可以逃离，可以划水，可以选择不干。正是因为是我自己在压榨自己，自己要揽这些活，自己要同时做多个项目，所以才会感受到成吨的压力，才会无法逃离，才会感受到无比的累。</p><p>虽然今晚还要发布充电桩项目的新功能，还没开发完。浪费了不少时间在思考上面，但我觉得是值得的。让我有种顿悟的感觉。等日后闲下来，还是需要好好理理思路，考虑下未来该怎么走。</p><p>感谢闯，感谢你的鼓励，很喜欢你刚才说的那句话：</p><blockquote><p>不要给自己设限，放飞你自己。</p></blockquote><p>好的，我知道了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190310]]></title>
        <id>/posts/2019/diary-20190310</id>
        <link href="https://hadb.me/posts/2019/diary-20190310"/>
        <updated>2019-03-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[刚才，儿子自己玩的时候，完整地从 1 数到了 10。]]></summary>
        <content type="html"><![CDATA[<p>刚才，儿子自己玩的时候，完整地从 1 数到了 10。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190313]]></title>
        <id>/posts/2019/diary-20190313</id>
        <link href="https://hadb.me/posts/2019/diary-20190313"/>
        <updated>2019-03-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天公司投资部要去深圳调研一个公司，我作为技术顾问一同前往。]]></summary>
        <content type="html"><![CDATA[<p>今天公司投资部要去深圳调研一个公司，我作为技术顾问一同前往。</p><p>这会儿在飞机上，起飞时小憩了一会儿，刚刚吃完了飞机餐，喝完了饮料。</p><p>心想可以趁这个时间写点什么。</p><p>起飞前跟投资部的高飞聊得很欢。</p><p>趁这次去深圳出差的机会，打算跟沈熠和剑芳分别见个面，面个基。讨论讨论未来。</p><p>未来如果想做和硬件相关的项目，深圳是个重要的地方，硬件公司多，市场丰富。</p><p>未来还是希望能往智能硬件的方向发展。做 1 对 1 的软件外包，终究没有太大前途，精力有限，难以实现复制；靠做一套软件产品去卖，又很难有不错的方向；现在能想到的是自己做智能硬件，自己的软件给自己用，用软件赋能硬件，通过卖硬件的方式带动软件，用硬件的复制实现软件的复制。</p><p>软件总归是需要一个载体去运行，与其寻找软件方向，不如直接造一个载体，这个载体上必然需要软件，这样软件的方向跟着载体的方向走就可以了。于是，问题就变成，如何寻找一个载体的方向。</p><p>软件的载体，也就是智能硬件，智能硬件的方向就缩减了很多了。</p><p>手机这种通用的，必然不在考虑范围内，连如日中天的罗永浩都做不好，更何况如今的我呢？</p><p>手表手环这种大公司已经介入了的，也不再考虑范围内，有何种的自信才敢跟苹果、小米这样的公司，人家有那么多设计师、工程师、销售运营的情况下，硬碰硬呢？</p><p>智能儿童玩具是个方向，未来是一个非常大的市场。现在大多数产品还是着眼于语音类的产品，讲故事、听音乐、学国学的米兔就是一个比较成功的例子。小米还做了智能积木机器人，了解下来太过复杂，而且可编程其实又太过智能，太超前。</p><p>前几天，想搞智能玩具汽车，加一些避障、语音控制等功能进去，就已经足够吸引眼球了，并且实现的难度并不大。高端一点的，还可以回传画面，录视频等，做那种高端的仿真车。需要打通的是玩具机身和控制器的生产和组装，是找玩具厂合作，全部交由他们组装，还是问他们买来零件自己改装，还是未来全部自己生产，这些都是需要考虑的。还有销售，怎么去做，是跟现有厂商合作，借他们的渠道帮他们搞，还是自己自立门户去从头开始，都是问题。</p><p>其他方向还有智能家居，这个搞的厂家有点多了，智能门锁、智能猫眼、电动窗帘、智能开关、净水器、净化器等等，米家做的那一套。这些其实也并没有太智能，基本还是简单的电路控制和数据读取，只是绑定到手机了而已。最多加了一些语音控制和人脸识别。</p><p>车载 OBD 设备也是我很早以前就想做的，因为市面上没有一款满足我的需求。不过这东西，需求量不大，市场偏小。并且数据处理的工作量很大，要适配的车型多，并且很多没有开放的数据结构，需要靠经验，甚至破解来搞，有一定难度。</p><p>给宠物做一些智能设备不知道有没有竞品，防走失定位器，app 实时查看位置。怎么充电也是个问题。学共享单车搞太阳能充电那套？体积要小，要塞下一个主板，不知道实施的可行性如何。</p><p>倒是可以用在汽车的监控上，比如运输公司监控自家运输车。这个已有一些公司在做，之前还搞过一个后台管理的前端静态页面。除了汽车，还可以装在船舶、电动车等上面。</p><p>主要还是优先考虑那些对体积要求没有那么高的场景，初期要求小一点，像穿戴设备要求都挺高的，不太适合。</p><hr></hr><p>刚才跟沈熠见了一面，刚回到酒店。跟他长聊了很久。也跟他讨论了智能硬件的方向，他提到几点，做硬件太难了，重资产、太容易被复制、销路难打开、品牌商压价严重。</p><p>主要问题是，销量难，如果靠自己搞销量，太难了，靠品牌商的话，他们压价很厉害，细到每个元部件，多少钱，他们都会有自己的价格。另外，厂商那边所有东西都是透明的，很可能直接其他客户过去看最近在生产啥东西，搞一个回去，代码都能直接读出来，完全透明，厂家甚至可以直接做一套给别人。主要还是靠销路，你有销路，他们拿这些东西过去也没用，你没销路，量上不去，成本都收不回。他提到一个做投影仪的朋友，芯片都能自己研发，蓝牙模块，外面七八块钱，他们成本能压到 1 块钱，而且他们不仅自己亚马逊、京东、天猫这些地方开店，同时也给其他厂商贴牌做、提供方案、甚至做零件，都搞，最后都搞不起来。就是没销路。</p><hr></hr><p>深圳的出租车都是电动的，充一次电大概能开 300 公里，出租车司机一天要充 3 次。虽然费用降低了，但是浪费时间了，每天挣的钱还是差不多，并不划算。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190314]]></title>
        <id>/posts/2019/diary-20190314</id>
        <link href="https://hadb.me/posts/2019/diary-20190314"/>
        <updated>2019-03-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这会儿躺在洗脚店的椅子上按摩脚。]]></summary>
        <content type="html"><![CDATA[<p>这会儿躺在洗脚店的椅子上按摩脚。</p><p>昨晚跟剑芳聊到 3 点多。</p><p>今天一天在瑞为开了一天的会，最后的时候很瞌睡。总的来说还是有收益，对芯片这块又有了一些新的认识。</p><p>晚上和廖婕剑芳一起吃饭，我抢先买的单。他们现在都挺困难的，还是不要他们请了。</p><p>剑芳说要请我按摩脚，找了个看起来比较正规的店进去了。</p><p>第一次修脚，体验还可以。就是按摩小腿和脚板的时候有点痛。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190315]]></title>
        <id>/posts/2019/diary-20190315</id>
        <link href="https://hadb.me/posts/2019/diary-20190315"/>
        <updated>2019-03-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[躺在宾馆的床上，看着电视里放的《邓小平》，手机上刷着学习强国。]]></summary>
        <content type="html"><![CDATA[<p>躺在宾馆的床上，看着电视里放的《邓小平》，手机上刷着学习强国。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于未来发展方向的思考]]></title>
        <id>/posts/2019/thoughts-about-the-future</id>
        <link href="https://hadb.me/posts/2019/thoughts-about-the-future"/>
        <updated>2019-03-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天是儿子两周岁的生日。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190319.thoughts-about-the-future/cover.jpg" alt="封面" /><p>今天是儿子两周岁的生日。</p><p>聪明、活泼、可爱的儿子，给我带来了很多欢乐，也就最近这半年，才越来越开始有当爸爸的感觉。儿子每天叫爸爸，拉着我陪他玩，要抱抱，要亲亲，会说一些爸爸爱你之类的话，开始有了互动，情感的联系也越来越深。</p><p>同时，我的人生也到了一个迷茫的阶段。晚上跟 Ben 聊了会儿天，又引发了我的思考。我觉得我需要好好理一理思路，正如前几天的文章里所说的，当时我还没有时间去好好理一理。今天可以好好理一下。</p><p>我不缺钱。虽然有房贷，但到今年年底，基本上存款也够还完房贷了。后面其实也没啥压力了，够用就行了。</p><p>我总想着证明自己，而挣钱多少是一个比较简单、比较客观的衡量标准。那就先以挣钱多为目标，分析下我未来的几个路子：</p><ol><li>技术管理路线：在一个大公司踏实工作，至少干个 5 年，晋升到管理层。</li><li>技术专家路线：在某个领域埋头苦干，至少干个 5 年，成为该领域的专家。</li><li>兼职外包路线：边上班边接外包，只接大单，性价比高的单子，价钱往高了谈，谈不下来就不做。</li><li>全职外包路线：全职干外包，价钱尽量往高谈，谈不上去能不亏就干。活越多越好，拼效率，多劳多得。通过积累，实现一些项目的模块化，可通过简单配置实现同类项目的复制，提升效率。</li><li>辅助创业路线：傍一个金主，帮他全包技术活，初期按项目外包谈钱，项目搞大了，招人干，按月拿工资，走管理路线，拿股份，往融资方向发展。</li><li>自主创业路线：找准一个好的项目方向，埋头苦干，发展客户，靠项目赚钱，一份劳动力卖多次。</li></ol><p>大致有这几个路线，其中技术管理和技术专家的路线的上升趋势比较稳定，5 年后收入也应该比较可观。并且人会比较轻松。</p><p>兼职外包的路线，由于分散精力，技术能力和管理能力的上升有限，在前期可能总收入比前两者高，但外包有瓶颈，往后的发展未必比前两者好。姑且算 5 年后总收入差不多，但个人发展会受影响，毕竟有很多精力投入在外包项目上，拿时间换了钱，成长有限。人也会比较累。</p><p>全职外包路线，人会比较自由，忙的时候累成狗，闲的时候闲成猪。可以有时间做一些其他的事情。但挣的钱也有瓶颈，5 年后很难与前两种相比。除非项目多，招人做。但是招一个愿意一起做外包的靠谱的人很难。</p><p>辅助创业路线，应该是在走全职外包路线的时候，如果能傍上一个金主，那么就进入这条路。但也要看运气，项目好不好，金主够不够有钱，资本运作的能力等，这个要看创始人能力。搞得好了，可能就财务自由了，搞得不好，浪费几年技术成长，其他方面的经验倒肯定会有所成长。</p><p>自主创业路线，更是未知数。首先好的方向很难找。开饭店、做智能玩具、做 PAAS 等等，点子之前倒是想了很多，但是还没有哪个能让我毅然决然坚定地做下去。这个看未来的风向，如果遇到一个比较好的方向，在风头上，倒是可以搞一搞。搞个自己的项目，从卖一次劳动力转为同一次劳动力卖多次，实现成倍地挣钱。5 年后说不好，项目发展好，躺着挣钱的目标应该能达成，搞成了就可以滚雪球，扩大规模搞。</p><p>这么一分析，似乎之前头脑一热，有些想法只考虑了眼前利益。放眼 5 年后，似乎前两种路线更加稳妥。至少这 5 年时间，可以用来再提升自己，让自己再上一层楼。并且 5 年后的收益，未必比下面几种要差。</p><p>我现在认为，我不应该那么浮躁，静下心来，踏实积累 5 年，有这 5 年的积累后，再去追寻自己的梦想不迟。毕竟未来还很长，机遇还很多。提前积累好，时刻准备好，在适当的时候抓住适当的机遇。</p><p>今天这么整理一下，还是很有收获的。至少给自己理清了方向。我要有耐心，不要急于做决定。可以花一年的时间来思考，然后为至少未来 5 年做一个慎重的决定。</p><p>今天先思考到这里。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于未来的思考 —— 给老板的邮件]]></title>
        <id>/posts/2019/thoughts-about-the-feture-email</id>
        <link href="https://hadb.me/posts/2019/thoughts-about-the-feture-email"/>
        <updated>2019-03-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Mylo，我最近有些新的想法，想跟你同步一下。]]></summary>
        <content type="html"><![CDATA[<p>Mylo，我最近有些新的想法，想跟你同步一下。</p><p>之前跟你说的要跑路的想法，有点草率了。</p><p>2018 年，总结下来，我做得很累。红星只是其中一个很小的原因，主要还是因为我自己没能经受住眼前的利益，给自己揽了太多的活，给了自己太多的压力。</p><p>今年年初，之前谈的一个项目发生了一些变故，也使得我重新花时间去思考，我未来到底应该走怎样的发展路线。我这段时间也跟几个朋友讨论过。</p><p>经过这段时间的思考，我发现，我之前的想法只考虑了眼前的利益，并没有往长远的未来去想。可能我接外包，确实在短时间内可以多挣些钱，但毕竟需要花费我很多精力，很难把精力花在个人成长上面。而且会让人变得很累，甚至工作的积极性都会下降。</p><p>我给自己分析了几个可能的发展路线：技术管理路线、技术专家路线、兼职外包路线、全职外包路线、辅助创业路线、自主创业路线。</p><p>之前我的工作一直变来变去，不管是客观原因还是主观原因，没有一个公司能让我呆上 1 年以上。这也导致我变得越来越浮躁，在接外包的路上越走越远。</p><p>经过去年这一年，我发现，兼职外包的路线会让我变得很累，鱼和熊掌不可兼得。全职外包不确定性因素太大，辅助创业、自主创业更需要机遇，并且未知数很多。</p><p>我开始考虑，我是不是应该静下心来，把目光放长远些，先做一个至少 5 年的长远计划。</p><p>在红星有钟总，有你，有比较丰厚的加薪制度和年终奖，在这里埋头干个 5 年，发展起来比我做外包似乎更有前途，并且人会比较有规律，也会更轻松一些。</p><p>今天儿子两周岁了，突然感觉肩上的担子开始变得沉重些了。虽然内心还是有不安分的梦想，但我现在认为，我不应该那么浮躁。应该静下心来，踏实积累个 5 年，有这 5 年的积累后，再去追寻自己的梦想不迟。毕竟未来还很长，机遇还很多。提前积累好，时刻准备好，在适当的时候抓住适当的机遇。</p><p>所以我决定，收回之前跟你说的今年要跑路的想法。减少外包的量，不接新外包了，只做老项目的维护，到下半年，基本上可以全身心投入到工作中。踏实积累，不管是往管理方向，还是往技术专家的方向，耐心沉淀几年。</p><p>未来的规划，主要还是听 Mylo 安排，优先以项目需要为主。未来在人手充足的情况下，给我一些机会，能够有时间，去做一些需要花精力去沉淀的工作。</p><p>基本上就这么多，还是希望继续跟着 Mylo 效力，踏实干活，在红星跟着分一杯羹。</p><p>Bean</p><p>2019 年 03 月 20 日 凌晨</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[声波动画教程]]></title>
        <id>/posts/2019/audiowave-animation</id>
        <link href="https://hadb.me/posts/2019/audiowave-animation"/>
        <updated>2019-03-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近项目中有一个声波动画的效果的需求，网上没找到合适的，于是手撸了一个。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190324.audiowave-animation/cover.png" alt="封面" /><p>最近项目中有一个声波动画的效果的需求，网上没找到合适的，于是手撸了一个。</p><h4 id="效果">效果</h4><figure><img src="https://hadb.me/static/posts/2019/20190324.audiowave-animation/01.gif"></img></figure><h4 id="demo">Demo</h4><p><a href="https://jsfiddle.net/HADB/x8vdkmqh/" rel="nofollow">https://jsfiddle.net/HADB/x8vdkmqh/</a></p><h4 id="示例代码">示例代码</h4><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">div</span><span class="s9AJx"> class</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">background</span><span class="sjJ54">"</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">  <</span><span class="sQzsp">div</span><span class="s9AJx"> class</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">audiowave</span><span class="sjJ54">"</span><span class="sP7_E">></</span><span class="sQzsp">div</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E"></</span><span class="sQzsp">div</span><span class="sP7_E">>
</span></span></code></pre><pre><code><span class="line" line="1"><span class="sutJx">/*
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sutJx">Author: HADB
</span></span><span class="line" line="4"><span class="sutJx">Date: 2019-03-24
</span></span><span class="line" line="5"><span class="sutJx">My Blog: https://hadb.me
</span></span><span class="line" line="6"><span class="sutJx">My GitHub: https://github.com/HADB
</span></span><span class="line" line="7"><span emptyLinePlaceholder>
</span></span><span class="line" line="8"><span class="sutJx">*/
</span></span><span class="line" line="9"><span emptyLinePlaceholder>
</span></span><span class="line" line="10"><span class="sbsja">const</span><span class="s_hVV"> wavePillarCount</span><span class="smGrS"> =</span><span class="srdBf"> 100</span><span class="sutJx"> // 柱子总数（用来调整密度）
</span></span><span class="line" line="11"><span class="sbsja">const</span><span class="s_hVV"> waveCount</span><span class="smGrS"> =</span><span class="srdBf"> 5</span><span class="sutJx"> // 波形总数（用来调整波形数量）
</span></span><span class="line" line="12"><span class="sbsja">const</span><span class="s_hVV"> waveAnimationDuration</span><span class="smGrS"> =</span><span class="srdBf"> 3</span><span class="sutJx"> // 单个动画秒数（与 animation-duration 一致）
</span></span><span class="line" line="13"><span class="sbsja">const</span><span class="s_hVV"> randomRate</span><span class="smGrS"> =</span><span class="srdBf"> 1</span><span class="sutJx"> // 随机倍率，越大越随机
</span></span><span class="line" line="14"><span emptyLinePlaceholder>
</span></span><span class="line" line="15"><span class="sVHd0">for</span><span class="su5hD"> (i </span><span class="smGrS">=</span><span class="srdBf"> 0</span><span class="sP7_E">;</span><span class="su5hD"> i </span><span class="smGrS"><</span><span class="su5hD"> wavePillarCount</span><span class="sP7_E">;</span><span class="su5hD"> i</span><span class="smGrS">++</span><span class="su5hD">) </span><span class="sP7_E">{
</span></span><span class="line" line="16"><span class="sbsja">  const</span><span class="s_hVV"> offset</span><span class="smGrS"> =</span><span class="skxfh"> ((</span><span class="su5hD">i</span><span class="smGrS"> /</span><span class="su5hD"> wavePillarCount</span><span class="smGrS"> -</span><span class="srdBf"> 1</span><span class="skxfh">) </span><span class="smGrS">*</span><span class="su5hD"> waveCount</span><span class="smGrS"> *</span><span class="su5hD"> waveAnimationDuration</span><span class="skxfh">) </span><span class="smGrS">-</span><span class="su5hD"> Math</span><span class="sP7_E">.</span><span class="sGLFI">random</span><span class="skxfh">() </span><span class="smGrS">*</span><span class="su5hD"> randomRate
</span></span><span class="line" line="17"><span class="su5hD">  document</span><span class="sP7_E">.</span><span class="sGLFI">querySelector</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">.audiowave</span><span class="sjJ54">'</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="su5hD">innerHTML</span><span class="smGrS"> +=</span><span class="sjJ54"> `</span><span class="s_sjI"><div style="-webkit-animation-delay:</span><span class="sjJ54">${</span><span class="su5hD">offset</span><span class="sjJ54">}</span><span class="s_sjI">s;"></div></span><span class="sjJ54">`
</span></span><span class="line" line="18"><span class="sP7_E">}
</span></span></code></pre><pre><code><span class="line" line="1"><span class="s83vy">body</span><span class="sP7_E"> {
</span></span><span class="line" line="2"><span class="soE4H">  padding</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="soE4H">  margin</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="4"><span class="sP7_E">}
</span></span><span class="line" line="5"><span emptyLinePlaceholder>
</span></span><span class="line" line="6"><span class="stp6e">.</span><span class="sbgvK">background</span><span class="sP7_E"> {
</span></span><span class="line" line="7"><span class="soE4H">  width</span><span class="sP7_E">:</span><span class="srdBf"> 100</span><span class="sw1J6">vw</span><span class="sP7_E">;
</span></span><span class="line" line="8"><span class="soE4H">  height</span><span class="sP7_E">:</span><span class="srdBf"> 100</span><span class="sw1J6">vh</span><span class="sP7_E">;
</span></span><span class="line" line="9"><span class="soE4H">  background-color</span><span class="sP7_E">:</span><span class="s39Yj"> #</span><span class="s_hVV">193082</span><span class="sP7_E">;
</span></span><span class="line" line="10"><span class="sP7_E">}
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="stp6e">.</span><span class="sbgvK">audiowave</span><span class="sP7_E"> {
</span></span><span class="line" line="13"><span class="soE4H">  position</span><span class="sP7_E">:</span><span class="s_hVV"> absolute</span><span class="sP7_E">;
</span></span><span class="line" line="14"><span class="soE4H">  left</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="15"><span class="soE4H">  top</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="16"><span class="soE4H">  width</span><span class="sP7_E">:</span><span class="srdBf"> 100</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="17"><span class="soE4H">  height</span><span class="sP7_E">:</span><span class="srdBf"> 200</span><span class="sw1J6">px</span><span class="sP7_E">;
</span></span><span class="line" line="18"><span class="soE4H">  display</span><span class="sP7_E">:</span><span class="s_hVV"> flex</span><span class="sP7_E">;
</span></span><span class="line" line="19"><span class="soE4H">  flex-direction</span><span class="sP7_E">:</span><span class="s_hVV"> row</span><span class="sP7_E">;
</span></span><span class="line" line="20"><span class="soE4H">  align-items</span><span class="sP7_E">:</span><span class="s_hVV"> flex-start</span><span class="sP7_E">;
</span></span><span class="line" line="21"><span class="soE4H">  justify-content</span><span class="sP7_E">:</span><span class="s_hVV"> space-between</span><span class="sP7_E">;
</span></span><span class="line" line="22"><span class="sP7_E">}
</span></span><span class="line" line="23"><span emptyLinePlaceholder>
</span></span><span class="line" line="24"><span class="stp6e">.</span><span class="sbgvK">audiowave</span><span class="s83vy"> div</span><span class="sP7_E"> {
</span></span><span class="line" line="25"><span class="soE4H">  top</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="26"><span class="soE4H">  background</span><span class="sP7_E">:</span><span class="sptTA"> rgba</span><span class="sP7_E">(</span><span class="srdBf">44</span><span class="sP7_E">,</span><span class="srdBf"> 99</span><span class="sP7_E">,</span><span class="srdBf"> 255</span><span class="sP7_E">,</span><span class="srdBf"> 0.25</span><span class="sP7_E">);
</span></span><span class="line" line="27"><span class="soE4H">  z-index</span><span class="sP7_E">:</span><span class="srdBf"> 9</span><span class="sP7_E">;
</span></span><span class="line" line="28"><span class="soE4H">  width</span><span class="sP7_E">:</span><span class="srdBf"> 3</span><span class="sw1J6">px</span><span class="sP7_E">;
</span></span><span class="line" line="29"><span class="soE4H">  height</span><span class="sP7_E">:</span><span class="srdBf"> 0</span><span class="sP7_E">;
</span></span><span class="line" line="30"><span class="soE4H">  animation</span><span class="sP7_E">:</span><span class="su5hD"> audiowave </span><span class="srdBf">3</span><span class="sw1J6">s</span><span class="s_hVV"> infinite</span><span class="s_hVV"> linear</span><span class="sP7_E">;
</span></span><span class="line" line="31"><span class="sP7_E">}
</span></span><span class="line" line="32"><span emptyLinePlaceholder>
</span></span><span class="line" line="33"><span class="sVHd0">@keyframes</span><span class="s99_P"> audiowave</span><span class="sP7_E"> {
</span></span><span class="line" line="34"><span class="sbgvK">  0%</span><span class="sP7_E"> {
</span></span><span class="line" line="35"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 28.75</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="36"><span class="sP7_E">  }
</span></span><span class="line" line="37"><span emptyLinePlaceholder>
</span></span><span class="line" line="38"><span class="sbgvK">  5%</span><span class="sP7_E"> {
</span></span><span class="line" line="39"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 37.5</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="40"><span class="sP7_E">  }
</span></span><span class="line" line="41"><span emptyLinePlaceholder>
</span></span><span class="line" line="42"><span class="sbgvK">  10%</span><span class="sP7_E"> {
</span></span><span class="line" line="43"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 56.25</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="44"><span class="sP7_E">  }
</span></span><span class="line" line="45"><span emptyLinePlaceholder>
</span></span><span class="line" line="46"><span class="sbgvK">  15%</span><span class="sP7_E"> {
</span></span><span class="line" line="47"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 48.75</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="48"><span class="sP7_E">  }
</span></span><span class="line" line="49"><span emptyLinePlaceholder>
</span></span><span class="line" line="50"><span class="sbgvK">  20%</span><span class="sP7_E"> {
</span></span><span class="line" line="51"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 68.75</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="52"><span class="sP7_E">  }
</span></span><span class="line" line="53"><span emptyLinePlaceholder>
</span></span><span class="line" line="54"><span class="sbgvK">  25%</span><span class="sP7_E"> {
</span></span><span class="line" line="55"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 82.5</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="56"><span class="sP7_E">  }
</span></span><span class="line" line="57"><span emptyLinePlaceholder>
</span></span><span class="line" line="58"><span class="sbgvK">  30%</span><span class="sP7_E"> {
</span></span><span class="line" line="59"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 71.25</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="60"><span class="sP7_E">  }
</span></span><span class="line" line="61"><span emptyLinePlaceholder>
</span></span><span class="line" line="62"><span class="sbgvK">  35%</span><span class="sP7_E"> {
</span></span><span class="line" line="63"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 78.125</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="64"><span class="sP7_E">  }
</span></span><span class="line" line="65"><span emptyLinePlaceholder>
</span></span><span class="line" line="66"><span class="sbgvK">  40%</span><span class="sP7_E"> {
</span></span><span class="line" line="67"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 68.75</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="68"><span class="sP7_E">  }
</span></span><span class="line" line="69"><span emptyLinePlaceholder>
</span></span><span class="line" line="70"><span class="sbgvK">  45%</span><span class="sP7_E"> {
</span></span><span class="line" line="71"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 80.875</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="72"><span class="sP7_E">  }
</span></span><span class="line" line="73"><span emptyLinePlaceholder>
</span></span><span class="line" line="74"><span class="sbgvK">  50%</span><span class="sP7_E"> {
</span></span><span class="line" line="75"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 90</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="76"><span class="sP7_E">  }
</span></span><span class="line" line="77"><span emptyLinePlaceholder>
</span></span><span class="line" line="78"><span class="sbgvK">  55%</span><span class="sP7_E"> {
</span></span><span class="line" line="79"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 91.875</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="80"><span class="sP7_E">  }
</span></span><span class="line" line="81"><span emptyLinePlaceholder>
</span></span><span class="line" line="82"><span class="sbgvK">  60%</span><span class="sP7_E"> {
</span></span><span class="line" line="83"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 87</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="84"><span class="sP7_E">  }
</span></span><span class="line" line="85"><span emptyLinePlaceholder>
</span></span><span class="line" line="86"><span class="sbgvK">  65%</span><span class="sP7_E"> {
</span></span><span class="line" line="87"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 70</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="88"><span class="sP7_E">  }
</span></span><span class="line" line="89"><span emptyLinePlaceholder>
</span></span><span class="line" line="90"><span class="sbgvK">  70%</span><span class="sP7_E"> {
</span></span><span class="line" line="91"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 60</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="92"><span class="sP7_E">  }
</span></span><span class="line" line="93"><span emptyLinePlaceholder>
</span></span><span class="line" line="94"><span class="sbgvK">  75%</span><span class="sP7_E"> {
</span></span><span class="line" line="95"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 55</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="96"><span class="sP7_E">  }
</span></span><span class="line" line="97"><span emptyLinePlaceholder>
</span></span><span class="line" line="98"><span class="sbgvK">  80%</span><span class="sP7_E"> {
</span></span><span class="line" line="99"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 45</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="100"><span class="sP7_E">  }
</span></span><span class="line" line="101"><span emptyLinePlaceholder>
</span></span><span class="line" line="102"><span class="sbgvK">  85%</span><span class="sP7_E"> {
</span></span><span class="line" line="103"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 40</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="104"><span class="sP7_E">  }
</span></span><span class="line" line="105"><span emptyLinePlaceholder>
</span></span><span class="line" line="106"><span class="sbgvK">  90%</span><span class="sP7_E"> {
</span></span><span class="line" line="107"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 35</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="108"><span class="sP7_E">  }
</span></span><span class="line" line="109"><span emptyLinePlaceholder>
</span></span><span class="line" line="110"><span class="sbgvK">  95%</span><span class="sP7_E"> {
</span></span><span class="line" line="111"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 30</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="112"><span class="sP7_E">  }
</span></span><span class="line" line="113"><span emptyLinePlaceholder>
</span></span><span class="line" line="114"><span class="sbgvK">  100%</span><span class="sP7_E"> {
</span></span><span class="line" line="115"><span class="soE4H">    height</span><span class="sP7_E">:</span><span class="srdBf"> 28.75</span><span class="sw1J6">%</span><span class="sP7_E">;
</span></span><span class="line" line="116"><span class="sP7_E">  }
</span></span><span class="line" line="117"><span class="sP7_E">}
</span></span></code></pre><h4 id="代码解析">代码解析</h4><p>核心还是通过 <code>animation-delay</code> 动画延迟来实现，<code>audiowave</code> 的 <code>keyframes</code> 是根据设计图中一个完整波形的大致高度来调整的，不需要太精确，因为我们会加一些随机数进去，这样可以使每个波形之间不完全一致，不然就太死板了。</p><p><code>wavePillarCount</code> 是指整个声波中柱子的数量，可以根据总宽度和每个柱子的宽度按需调整。</p><p><code>waveCount</code> 是波的数量，可以尝试把下面的 <code>randomRate</code> 设为 <code>0</code> 就可以清楚地看出来了。</p><p><code>randomRate</code> 如上所述，是添加的随机延迟倍率，以便增加波形的杂音，不至于太死板。值越大杂音越大，值为 <code>0</code> 的时候没有杂音。下面是把 <code>randomRate</code> 设为 <code>0</code> 的效果。</p><figure><img src="https://hadb.me/static/posts/2019/20190324.audiowave-animation/02.gif"></img></figure><p>另外，<code>offset</code> 计算的时候之所以要减去 <code>1</code>，是为了保证 <code>offset</code> 的值为负数，这样可以保证动画在一开始就是完整的，不然部分波形一开始将不完整，等到了那个 <code>offset</code> 时才会开始动。</p><p>基本上就是这样，通过通过 <code>keyframes</code> 来设置初始波形，通过微调 <code>wavePillarCount</code>、 <code>waveCount</code>、 <code>randomRate</code> 来获得更加的效果。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[及时行乐]]></title>
        <id>/posts/2019/carpe-diem</id>
        <link href="https://hadb.me/posts/2019/carpe-diem"/>
        <updated>2019-04-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天又想到一个新的关于消费的理论：]]></summary>
        <content type="html"><![CDATA[<p>今天又想到一个新的关于消费的理论：</p><ol><li>有一个 M 块钱的外包在我面前，我接了，花了 N 时间辛苦加班熬夜干完了。</li><li>假如我不接，相比做外包的这 N 时间加班的时间内我可以学习、打游戏、陪家人、锻炼身体、睡觉等等其他方式度过。</li><li>两种方式的结果的不同是：方案一，辛苦了 N 时间，最终有 M 块钱；方案二，没有 M 块钱，但这 N 时间内以其他方式享受生活、充实自己。</li><li>如果方案一的 M 块钱不花掉的话，方案一比方案二要差很多，牺牲了 N 时间享受生活换了 M 块钱的数字在账上，如果想要让方案一更值，就要让 M 块钱产生比在那 N 时间更大的价值。</li><li>也就是说，M 块钱只有消费掉，而且消费带来的价值要比 N 时间更大的话，才值得，否则还不如不要这 M 块钱，而去享受 N 时间的快乐。</li><li>为了让这 M 块钱产生价值，它必须要消费掉。而且要用来买一些之前舍不得买的东西，如果用来买生活必需品，那是没意义的，因为即便你没有这 M 块钱，这些东西还是要买，相当于这 M 块钱还一直在账上。</li><li>这么一想的话，是不是就变得简单了。这 M 块钱如果不用来及时行乐的话，等你老了，这笔钱还在账上，结果等于你花了 N 时间去换了 M 块钱的数字在账上，那还不如不要。</li></ol><p>公司加班也是一个道理，加班带来的额外收益，就得及时花掉。如果舍不得花，说明它并没有给你带来足够的享受，还不如不加班。还不如舍弃那份额外的收益（升职、加薪的空间等），直接享受生活算了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[重庆趣事]]></title>
        <id>/posts/2019/an-interesting-thing-in-chongqing</id>
        <link href="https://hadb.me/posts/2019/an-interesting-thing-in-chongqing"/>
        <updated>2019-06-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨晚吃火锅，隔壁桌的外国人突然送我们一盘肉。我们很诧异，但表示感谢，以为他们吃不下了。
后来发现是一盘切的很薄的腰子片。看来他们吃不惯腰子。]]></summary>
        <content type="html"><![CDATA[<p>昨晚吃火锅，隔壁桌的外国人突然送我们一盘肉。我们很诧异，但表示感谢，以为他们吃不下了。
后来发现是一盘切的很薄的腰子片。看来他们吃不惯腰子。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[「三万个车建新」学习心得]]></title>
        <id>/posts/2019/speech-of-study-cheche</id>
        <link href="https://hadb.me/posts/2019/speech-of-study-cheche"/>
        <updated>2019-06-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[我第一次听说「三万个车建新」这个概念是在金桥商场的开放日上，当时车总讲了很多他的故事以及他最新的思考。我听车总讲话的次数不多，但每次都能学到很多道理。比如，剖析事物的基因、微因、纳因的「创新法则」、四大创新方法论等，以及这次的「三万个车建新」。]]></summary>
        <content type="html"><![CDATA[<p>我第一次听说「三万个车建新」这个概念是在金桥商场的开放日上，当时车总讲了很多他的故事以及他最新的思考。我听车总讲话的次数不多，但每次都能学到很多道理。比如，剖析事物的基因、微因、纳因的「创新法则」、四大创新方法论等，以及这次的「三万个车建新」。</p><p>我自己也有过类似的想法，当自己很忙，有很多事情做不完的时候，就希望能复制一个我出来，帮我做一些事情。比如周末的时候，一个我来公司加班干活，另一个我在家里陪老婆孩子。</p><p>车总的「三万个车建新」比我想得远多了，不只是局限于分身做事情这么简单。「三万个车建新」更多的是鼓励我们进行「职业创业」，这个概念大家应该也不陌生，车总讲过很多次，他也致力于打造一个这样的平台。</p><p>职业创业与自己创业，虽然都是创业，但却是两种截然不同的路子。</p><p>我自己有过创业的经历，做过的项目也不少，团队成员最多也发展到十来个，拿很多小型的互联网创业团队比，这个规模也算可以了。但最终没有能够坚持下来。我常常也会反思，为什么我没有能够成功。一开始我还不太承认是我的原因导致失败了，现在我越来越敢于大方地承认，我的第一次创业是失败的。究其原因，我也总结了几点：</p><ol><li>盈利模式过于单一：我的创业以技术外包为主，基本是拿钱帮别人做项目。没有一个是属于自己的项目。</li><li>没有足够的资金支持：没有任何天使资金，全靠项目资金支持团队运作。</li><li>没有未雨绸缪，提前准备好 PLAN B：后期基本上所有人都是围绕一个大项目在转，这个项目是做共享单车的，但这个行业大家也知道变化太快，甲方突然倒闭，导致我们措手不及，没有任何备选方案，没有任何其他项目能够养活团队，甚至甲方还欠我们一个月的研发费，当时一时也找不到别的出路，为了大家着想，只能解散。</li></ol><p>创业之艰辛，只有真正创过业的人才懂。尤其是从零开始创业，需要处理各种杂事。公司注册、代账、办公地点选址、招人、办理社保公积金、发工资、人员管理、项目管理、催项目款，甚至讨债，各种事情。当你一个人处理不过来的时候，只能招人，而招人就意味着成本的增加，尤其对于创业初期没有资金支持的小公司来说，人越多，意味着担子越重，风险也越大。</p><p>职业创业则不同，在公司的平台上进行职业创业，风险要小很多，就光是无需承担人力成本这一项，就已经足够了。利用公司的平台，进行职业创业，往职业经理人的方向去发展，也是一条康庄大道。</p><p>当然，「三万个车建新」只是一个比喻，所有人都去当职业经理人，也是一件不现实的事情，不是每个人都有这个能力，公司也没有这么多的岗位。这是一个期望，期望大家能够发扬职业创业的精神，努力去提高自己，要有主人翁精神。公司提供了这样一个职业创业的机会，就看你自己能不能去抓住这个机会，往这个方向去发展自己。</p><p>以上是关于「职业创业」的思考，下面是关于人生的一些思考。</p><p>我们每个人，来到这个世界走一遭，最终都会回归尘土。是碌碌无为过一生，还是要为这个世界留下些什么？每个人都有不同的选择。当然，也不是说你想选择成功就一定能成功，但是如果你想选择碌碌无为，那一定可以碌碌无为。成功有很多个层次，罗马非一日建成，我们需要做的就是不断给自己定下目标，短期的目标、1 年的目标、3 年的目标、5 年的目标、10 年的目标、甚至更长远的目标，然后不断把目标量化，不断去努力，去挑战去完成一个又一个目标。每达成一个目标，就是一次成功，随着更大目标的达成，你也会收获越来越大的成功。</p><p>我相信，用职业创业的激情去工作，不断去追求更大的成功，即便不能成为职业经理人，至少也可以让自己的生命变得更优秀更有价值。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[CentOS 有线网卡配置 IEEE 802.1X 上网]]></title>
        <id>/posts/2019/centos-wire-ieee8021x-config</id>
        <link href="https://hadb.me/posts/2019/centos-wire-ieee8021x-config"/>
        <updated>2019-07-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[公司网络使用 IEEE 802.1X 认证网络接入，手机端和 Windows 端都很方便，Linux 上稍微麻烦一些。最近有个测试服务器（CentOS 7.6）需要接入办公网络测试，折腾了一番。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190710.centos-wire-ieee8021x-config/cover.png" alt="封面" /><p>公司网络使用 IEEE 802.1X 认证网络接入，手机端和 Windows 端都很方便，Linux 上稍微麻烦一些。最近有个测试服务器（CentOS 7.6）需要接入办公网络测试，折腾了一番。</p><p>中间走的弯路就不讲了，直接讲最终的解决方案吧。</p><ol><li>取消 <code>/etc/sysconfig/network-scripts/</code> 中活动网卡（本例中是 <code>ifcfg-em1</code>）的任何配置，例如 <code>ONBOOT</code> 等。</li><li><code>chkconfig --list</code> ，查看是否有 <code>network</code> 的服务，如果有，执行 <code>chkconfig --del network</code> 删除</li><li>修改 <code>/etc/wpa_supplicant/wpa_supplicant.conf</code>，写入如下内容：<pre><code><span class="line" line="1"><span class="su5hD">ctrl_interface</span><span class="smGrS">=</span><span class="s_sjI">/var/run/wpa_supplicant
</span></span><span class="line" line="2"><span class="su5hD">ap_scan</span><span class="smGrS">=</span><span class="s_sjI">0
</span></span><span class="line" line="3"><span class="su5hD">network</span><span class="smGrS">=</span><span class="su5hD">{
</span></span><span class="line" line="4"><span class="su5hD">    key_mgmt</span><span class="smGrS">=</span><span class="s_sjI">IEEE8021X
</span></span><span class="line" line="5"><span class="su5hD">    eap</span><span class="smGrS">=</span><span class="s_sjI">PEAP
</span></span><span class="line" line="6"><span class="su5hD">    identity</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">YOUR_USER_NAME</span><span class="sjJ54">"
</span></span><span class="line" line="7"><span class="su5hD">    password</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">YOUR_PASSWORD</span><span class="sjJ54">"
</span></span><span class="line" line="8"><span class="su5hD">    phase2</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">autheap=MSCHAPV2</span><span class="sjJ54">"
</span></span><span class="line" line="9"><span class="su5hD">}
</span></span></code></pre><br></br>认证的账号和加密方式需要根据具体需求做更改。<br></br>可通过如下代码来测试是否成功（根据网卡名称 <code>em1</code> 需要根据实际使用的网卡做调整）：<pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> ifdown</span><span class="s_sjI"> em1
</span></span><span class="line" line="2"><span class="sbgvK">$</span><span class="s_sjI"> wpa_supplicant</span><span class="stzsN"> -B</span><span class="stzsN"> -i</span><span class="s_sjI"> em1</span><span class="stzsN"> -c</span><span class="s_sjI"> /etc/wpa_supplicant/wpa_supplicant.conf</span><span class="stzsN"> -D</span><span class="s_sjI"> wired
</span></span><span class="line" line="3"><span class="sbgvK">$</span><span class="s_sjI"> ifup</span><span class="s_sjI"> em1
</span></span><span class="line" line="4"><span class="sbgvK">$</span><span class="s_sjI"> dhclient</span><span class="s_sjI"> em1
</span></span><span class="line" line="5"><span class="sbgvK">$</span><span class="s_sjI"> ip</span><span class="s_sjI"> addr</span><span class="sutJx"> # 查看IP地址
</span></span><span class="line" line="6"><span class="sbgvK">$</span><span class="s_sjI"> ping</span><span class="s_sjI"> baidu.com</span><span class="sutJx"> # 检查是否可以 Ping 通百度
</span></span></code></pre></li><li>下面设置开机启动，在 <code>/etc/init.d/</code> 中创建 <code>wpa_network</code>，写入如下内容：<pre><code><span class="line" line="1"><span class="sutJx">#!/bin/bash
</span></span><span class="line" line="2"><span class="sutJx"># chkconfig: 2345 10 90
</span></span><span class="line" line="3"><span class="sutJx"># description: wpa network
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sbgvK">ifdown</span><span class="s_sjI"> em1
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sbgvK">wpa_supplicant</span><span class="stzsN"> -B</span><span class="stzsN"> -i</span><span class="s_sjI"> em1</span><span class="stzsN"> -c</span><span class="s_sjI"> /etc/wpa_supplicant/wpa_supplicant.conf</span><span class="stzsN"> -D</span><span class="s_sjI"> wired
</span></span><span class="line" line="8"><span emptyLinePlaceholder>
</span></span><span class="line" line="9"><span class="sbgvK">ifup</span><span class="s_sjI"> em1
</span></span><span class="line" line="10"><span emptyLinePlaceholder>
</span></span><span class="line" line="11"><span class="sbgvK">dhclient</span><span class="s_sjI"> em1
</span></span></code></pre></li><li><code>chkconfig --add wpa-network</code>，加入到 <code>chkconfig</code> 中</li><li><code>chkconfig wpa-network on</code>，开启</li><li><code>reboot</code> 重启检测是否成功自动联网</li></ol><blockquote><p>后记：后来发现运维可以直接通过 MAC 地址配置上网，于是又取消了 wpa 自动联网，直接在 <code>/etc/sysconfig/network-scripts/</code> 中把 <code>ifcfg-em1</code> 中的 <code>ONBOOT</code> 设为 <code>yes</code> 即可。</p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[腾出点时间给自己放空]]></title>
        <id>/posts/2019/empty-your-self</id>
        <link href="https://hadb.me/posts/2019/empty-your-self"/>
        <updated>2019-07-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[知乎上有这样一个问题：「为什么有些人开车到家后会独自坐在车中发呆」，很有意思。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190718.empty-your-self/cover.jpg" alt="封面" /><p>知乎上有这样一个问题：「<a href="https://www.zhihu.com/question/28476510" rel="nofollow">为什么有些人开车到家后会独自坐在车中发呆</a>」，很有意思。</p><p>每天忙忙碌碌，有时候真的需要放空一下自己，熄了火的车子，是一个全封闭的空间，很适合发呆。事情很多的时候，甚至都没有时间来思考，这个时候就需要独处一会儿，让脑子放空一会儿，给点时间面对自己，思考人生。</p><p>前不久有一次下班早，在公司楼下等人，坐在石头上，看着夕阳西下，吹着凉爽的风，看着躺在地上慵懒的猫，瞬间有一种久违的感觉，似乎是童年记忆中的某个时候的感觉。这种强烈的感觉让我倍感舒适，非常愉悦。</p><p>上次开车时，广播里听到这样一个问题，古代诗人们为什么要告老还乡？贺知章 86 岁高龄，还坚持要长途跋涉个把月回老家，还写出了流传千古的《回乡偶书》。几十年在外，老家早已物是人非，亲人们也都没了，还要拖着孱弱的身躯长途奔波回去图啥呢？有一个观点是，告老还乡，为的是找寻自己，因为童年的记忆都在故乡。一个人在外拼搏，一辈子忙忙碌碌，又有多少真正属于自己的时间？又能有多少时候能像童年那样无忧无虑？童年的记忆，有很多是第一次接触世界，有着难忘而深刻的记忆。比如第一次在河边摘下杨柳枝，编织成圈，戴在头上；比如春耕时放学后，第一次在田里抛秧；比如炎热的夏天，坐在巷子里乘凉，摸着身旁躺着的狗狗，耳边还响着没完没了的知了声；比如第一次趴在桥上，把扣着青蛙腿的绳子伸到水下面钓龙虾；比如第一次在蚊帐里看到萤火虫一闪一闪绕着圈圈；比如农忙时，爷爷奶奶在门口打着油菜籽，空气中弥漫着的收获的味道；比如下着浓雾的早晨，跑到大路上在雾中望眼欲穿等待着从外地归来的爸妈；比如寒冷的冬天，外面下着雪，脚放在炉子上取暖……太多属于童年无法忘记的回忆了。</p><p>而随着年龄增长，如此难忘的回忆片段却越来越少，究其原因，一个是很少能像小时候那样，可以有大把的时间做各种各样的事情，去积累这样的记忆。成年后，要么花了大量的时间学习，要么花了大量的时间工作。以至于到如今，如果我把时间用来玩耍，甚至都会有一种愧疚感，很难去无忧无虑地放空自己。另一个原因来自于互联网的发达，以至于人们工作之外大多数的时间都花在网络上了，网络虽然减少了我们获取信息的成本，可以大量获取到各种各样的信息，但大量的信息却让我们忍不住去获取，导致花很多时间在这些信息上，聊天、段子、新闻、小视频，等等。一个很恐怖的事情是，iPhone 统计出我每天屏幕使用时间有 5 个小时之多。然而虽然现在有了各种各样的娱乐活动，却很少有童年的时候那样纯粹的快乐。</p><p>于我而言，唯一属于我自己的时间就是深夜，所有人都睡着的时候。这个时候，我可以静下心来写代码，可以看个电影，可以思考人生，可以写点东西。这也练就了我熬夜的本领，从大学到如今，基本上很少在 12 点前睡觉，一两点居多，三点四点也不稀奇，看片子看到天蒙蒙亮的时候也是有的。每每这个时候，我可以拥有属于自己的时间，可以去做自己想做的事，可以放空自己，可以有时间去独立思考。</p><p>生命不应该拿来挥霍，但却应该时常有一些这样的时刻，就那么让时间去流逝，什么都不做，去感受生命的逝去，去感受这个世界，去享受这种挥霍时间的感觉。正如我标题中所说的那样，腾出点时间给自己放空。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[解决 PyCharm 设置 pipenv 报错的问题]]></title>
        <id>/posts/2019/solve-pycharm-adding-pipenv-error</id>
        <link href="https://hadb.me/posts/2019/solve-pycharm-adding-pipenv-error"/>
        <updated>2019-07-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[使用 PyCharm 添加 pipenv 会报如下错误：]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190719.solve-pycharm-adding-pipenv-error/cover.png" alt="封面" /><p>使用 PyCharm 添加 pipenv 会报如下错误：</p><pre><code><span class="line" line="1"><span class="su5hD">Executed command:
</span></span><span class="line" line="2"><span class="su5hD">/usr/local/bin/pipenv --python /usr/local/bin/python3.</span><span class="s39Yj">6</span><span class="su5hD"> install --dev
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="s_sjI">Error</span><span class="su5hD"> occurred:
</span></span><span class="line" line="5"><span class="s_sjI">Error</span><span class="su5hD"> Running Pipenv
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="su5hD">Command output:
</span></span><span class="line" line="8"><span class="su5hD">Traceback (most recent call last):
</span></span><span class="line" line="9"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/pipenv/2018.11.26_2/libexec/bin/pipenv"</span><span class="su5hD">, line </span><span class="s39Yj">6</span><span class="su5hD">, in <module>
</span></span><span class="line" line="10"><span class="su5hD">    from pkg_resources import load_entry_point
</span></span><span class="line" line="11"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">3241</span><span class="su5hD">, in <module>
</span></span><span class="line" line="12"><span class="su5hD">    @_call_aside
</span></span><span class="line" line="13"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">3225</span><span class="su5hD">, in _call_aside
</span></span><span class="line" line="14"><span class="su5hD">    f(*args, **kwargs)
</span></span><span class="line" line="15"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">3254</span><span class="su5hD">, in _initialize_master_working_set
</span></span><span class="line" line="16"><span class="su5hD">    working_set = </span><span class="s39Yj">WorkingSet._build_master</span><span class="su5hD">()
</span></span><span class="line" line="17"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">583</span><span class="su5hD">, in _build_master
</span></span><span class="line" line="18"><span class="s39Yj">    ws.require</span><span class="su5hD">(__requires__)
</span></span><span class="line" line="19"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">900</span><span class="su5hD">, in require
</span></span><span class="line" line="20"><span class="su5hD">    needed = </span><span class="s39Yj">self.resolve</span><span class="su5hD">(parse_requirements(requirements))
</span></span><span class="line" line="21"><span class="su5hD">  File </span><span class="s_sjI">"/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pkg_resources/__init__.py"</span><span class="su5hD">, line </span><span class="s39Yj">786</span><span class="su5hD">, in resolve
</span></span><span class="line" line="22"><span class="su5hD">    raise DistributionNotFound(req, requirers)
</span></span><span class="line" line="23"><span class="s39Yj">pkg_resources.DistributionNotFound</span><span class="su5hD">: The </span><span class="s_sjI">'pipenv==2018.11.26'</span><span class="su5hD"> distribution was not found and is required by the application
</span></span></code></pre><figure><img src="https://hadb.me/static/posts/2019/20190719.solve-pycharm-adding-pipenv-error/01.png"></img></figure><p>这个问题是因为系统同时存在 Python 3.6 和 Python 3.7 导致的，查看了下，Homebrew 安装的 Python 3.7，而通过官网的 dmg 安装的 Python 3.6。我觉得这个与 PyCharm 内部的运行环境有关，即使我默认的已经是 Python 3.6 了，它不知为何依然调用了 Python 3.7 来执行。尝试卸掉了一个，发现这个问题解决了。</p><p>再次尝试，报了另外的错：</p><pre><code><span class="line" line="1"><span class="su5hD">Executed command:
</span></span><span class="line" line="2"><span class="su5hD">/Users/bean/Library/Python/</span><span class="s39Yj">3</span><span class="su5hD">.</span><span class="s39Yj">6</span><span class="su5hD">/bin/pipenv --python /usr/local/bin/python3.</span><span class="s39Yj">6</span><span class="su5hD"> install --dev
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="s_sjI">Error</span><span class="su5hD"> occurred:
</span></span><span class="line" line="5"><span class="su5hD">RuntimeError: Click will abort further execution because Python </span><span class="s39Yj">3</span><span class="su5hD"> was configured to use ASCII as encoding for the environment. Consult </span><span class="s39Yj">https://click.palletsprojects.com/en/7.x/python3/</span><span class="su5hD"> for mitigation steps.
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="su5hD">Traceback (most recent call last):
</span></span><span class="line" line="8"><span class="su5hD">  File </span><span class="s_sjI">"/Users/bean/Library/Python/3.6/bin/pipenv"</span><span class="su5hD">, line </span><span class="s39Yj">10</span><span class="su5hD">, in <module>
</span></span><span class="line" line="9"><span class="s39Yj">    sys.exit</span><span class="su5hD">(cli())
</span></span><span class="line" line="10"><span class="su5hD">  File </span><span class="s_sjI">"/Users/bean/Library/Python/3.6/lib/python/site-packages/pipenv/vendor/click/core.py"</span><span class="su5hD">, line </span><span class="s39Yj">764</span><span class="su5hD">, in __call__
</span></span><span class="line" line="11"><span class="su5hD">    return </span><span class="s39Yj">self.main</span><span class="su5hD">(*args, **kwargs)
</span></span><span class="line" line="12"><span class="su5hD">  File </span><span class="s_sjI">"/Users/bean/Library/Python/3.6/lib/python/site-packages/pipenv/vendor/click/core.py"</span><span class="su5hD">, line </span><span class="s39Yj">696</span><span class="su5hD">, in main
</span></span><span class="line" line="13"><span class="su5hD">    _verify_python3_env()
</span></span><span class="line" line="14"><span class="su5hD">  File </span><span class="s_sjI">"/Users/bean/Library/Python/3.6/lib/python/site-packages/pipenv/vendor/click/_unicodefun.py"</span><span class="su5hD">, line </span><span class="s39Yj">124</span><span class="su5hD">, in _verify_python3_env
</span></span><span class="line" line="15"><span class="s_sjI">    ' mitigation steps.'</span><span class="su5hD"> + extra
</span></span><span class="line" line="16"><span class="su5hD">RuntimeError: Click will abort further execution because Python </span><span class="s39Yj">3</span><span class="su5hD"> was configured to use ASCII as encoding for the environment. Consult </span><span class="s39Yj">https://click.palletsprojects.com/en/7.x/python3/</span><span class="su5hD"> for mitigation steps.
</span></span><span class="line" line="17"><span emptyLinePlaceholder>
</span></span><span class="line" line="18"><span class="su5hD">This system lists a couple of UTF-</span><span class="s39Yj">8</span><span class="su5hD"> supporting locales that
</span></span><span class="line" line="19"><span class="su5hD">you can pick from.  The following suitable locales were
</span></span><span class="line" line="20"><span class="su5hD">discovered: </span><span class="s39Yj">af_ZA.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">am_ET.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">be_BY.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">bg_BG.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">ca_ES.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">cs_CZ.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">da_DK.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">de_AT.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">de_CH.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">de_DE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">el_GR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_AU.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_CA.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_GB.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_IE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_NZ.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">en_US.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">es_ES.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">et_EE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">eu_ES.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">fi_FI.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">fr_BE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">fr_CA.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">fr_CH.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">fr_FR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">he_IL.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">hr_HR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">hu_HU.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">hy_AM.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">is_IS.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">it_CH.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">it_IT.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">ja_JP.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">kk_KZ.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">ko_KR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">lt_LT.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">nl_BE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">nl_NL.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">no_NO.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">pl_PL.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">pt_BR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">pt_PT.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">ro_RO.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">ru_RU.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">sk_SK.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">sl_SI.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">sr_YU.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">sv_SE.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">tr_TR.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">uk_UA.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">zh_CN.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">zh_HK.UTF-8</span><span class="su5hD">, </span><span class="s39Yj">zh_TW.UTF-8
</span></span></code></pre><p>Google 了半天，没找到有效的答案，大多数是让重新安装之类的，没意义。后来在 JetBrains 的 Issues 平台上提交了问题，最终发现这是一个已知问题，并且与 mac 上使用了 <code>zsh</code> 终端相关，可能 <code>oh-my-zsh</code> 重置了编码，导致 PyCharm 的运行环境中存在问题，最终解决方法如下：</p><p>在 <code>.zshrc</code> 文件最后指定编码：</p><pre><code><span class="line" line="1"><span class="sbsja">export</span><span class="su5hD"> LANG</span><span class="smGrS">=</span><span class="su5hD">zh_CN.UTF-8
</span></span></code></pre><p>重启 PyCharm，问题解决！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[七牛 SSL 证书过期不刷新的坑]]></title>
        <id>/posts/2019/qiniu-ssl-certificate-expire-problem</id>
        <link href="https://hadb.me/posts/2019/qiniu-ssl-certificate-expire-problem"/>
        <updated>2019-07-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近一个七牛上的 SSL 证书到期了，导致 CDN 上的图片访问的时候提示证书失效。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20190730.qiniu-ssl-certificate-expire-problem/cover.png" alt="封面" /><p>最近一个七牛上的 SSL 证书到期了，导致 CDN 上的图片访问的时候提示证书失效。</p><p>但其实早在一个多月前，我已经针对那个域名重新签发了新的证书。</p><p>在发现提示证书失效后，我查看了 CDN 上的 HTTPS 证书，发现已经显示为了最新的证书，并且有效期都是正常的。</p><p>初步猜测是主域的 SSL 证书虽然更新了，但各个节点上的 CDN 证书没更新。</p><p>于是在 CDN 上的 HTTPS 配置中，重新强制更新下证书，提示 8~15 分钟生效。</p><p>果然，更新完之后，图片访问正常了。</p><p>七牛竟然没有在证书过期后自动去强制更新所有节点的证书，有点坑。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20190810]]></title>
        <id>/posts/2019/diary-20190810</id>
        <link href="https://hadb.me/posts/2019/diary-20190810"/>
        <updated>2019-08-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[台风登陆浙江。凌晨 1 点半，窗外狂风怒号，想起那句，八月秋高风怒号，卷我屋上三重茅。]]></summary>
        <content type="html"><![CDATA[<p>台风登陆浙江。凌晨 1 点半，窗外狂风怒号，想起那句，八月秋高风怒号，卷我屋上三重茅。</p><p>现在我们住在风雨不动安如山的广厦中，听着外面怒号的台风，不再像当年杜甫那样艰苦，然而我们生活在这样安逸的环境中，又能像杜甫那样写出名垂千古的文字吗？</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Docker 同一域名下多个 Registry 保存凭证的方式]]></title>
        <id>/posts/2019/docker-registry-auth-with-same-domain</id>
        <link href="https://hadb.me/posts/2019/docker-registry-auth-with-same-domain"/>
        <updated>2019-12-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[阿里云的容器镜像服务是个好东西，配合在阿里云上容器服务，速度非常快。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2019/20191201.docker-registry-auth-with-same-domain/cover.png" alt="封面" /><p>阿里云的容器镜像服务是个好东西，配合在阿里云上容器服务，速度非常快。</p><p>但是阿里云的容器服务不支持自定义域名，都是在同一个域名下，通过不同的 <code>namespace</code> 来实现的。当需要管理多个账户下的不同 <code>namespace</code> 的时候，Docker 默认的认证存储方式就不太适用了。默认的 <code>~/.docker/config.json</code> 中的 <code>auths</code> 是根据域名来区分的，会出现登录了这个 <code>namespace</code> 之后，另一个 <code>namespace</code> 认证会失效的情况。经过一番搜索，发现可以通过 <code>docker --config</code> 来实现。</p><p>通过如下方式来创建一个名为 <code>config-a</code> 的配置</p><pre><code><span class="line" line="1"><span class="sbgvK">docker</span><span class="stzsN"> --config</span><span class="s_sjI"> ~/.docker/config-a</span><span class="s_sjI"> login</span><span class="stzsN"> --username=config-a-username</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com
</span></span></code></pre><p>之后 <code>push</code> 之类的命令，在前面加个 <code>--config ~/.docker/config-a</code> 即可，例如：</p><pre><code><span class="line" line="1"><span class="sbgvK">docker</span><span class="s_sjI"> build</span><span class="stzsN"> -t</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com/xxx/hblb-web:</span><span class="su5hD">$npm_package_version </span><span class="stzsN">-t</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com/xxx/hblb-web:latest</span><span class="s_sjI"> .</span><span class="sP7_E"> &&</span><span class="sbgvK"> docker</span><span class="stzsN"> --config</span><span class="s_sjI"> ~/.docker/config-a</span><span class="s_sjI"> push</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com/xxx/hblb-web:</span><span class="su5hD">$npm_package_version </span><span class="sP7_E">&&</span><span class="sbgvK"> docker</span><span class="stzsN"> --config</span><span class="s_sjI"> ~/.docker/config-a</span><span class="s_sjI"> push</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com/xxx/hblb-web:latest
</span></span></code></pre><p>同理，可以增加其他的 <code>config</code> 来完成同一域名下多个账号的认证存储。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[阿里云 k8s 集群搭建]]></title>
        <id>/posts/2019/aliyun-k8s-setup</id>
        <link href="https://hadb.me/posts/2019/aliyun-k8s-setup"/>
        <updated>2019-12-29T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h3 id="为-vpc-配置-snat">为 VPC 配置 SNAT</h3><p><strong>注：SNAT 已关闭，看起来两个 ECS 节点都有公网 IP，不需要了。（2024-06-04）</strong></p><p><del>阿里云的 NAT 网关太贵，考虑自行搭建 SNAT。</del></p><p><del>购买最廉价 ECS，配置如下设置</del></p><pre><code><span class="line" line="1"><span class="sbgvK">sysctl</span><span class="s_sjI"> net.ipv4.ip_forward</span><span class="sutJx"> # 查看当前 IP 转发配置，0 为关闭，1 为打开
</span></span><span class="line" line="2"><span class="sbgvK">sysctl</span><span class="stzsN"> -w</span><span class="s_sjI"> net.ipv4.ip_forward=</span><span class="srdBf">1</span><span class="sutJx"> # 打开 IP 转发
</span></span><span class="line" line="3"><span class="sbgvK">iptables</span><span class="stzsN"> -t</span><span class="s_sjI"> nat</span><span class="stzsN"> -I</span><span class="s_sjI"> POSTROUTING</span><span class="stzsN"> -s</span><span class="s_sjI"> 172.16.0.0/16</span><span class="stzsN"> -j</span><span class="s_sjI"> SNAT</span><span class="stzsN"> --to-source</span><span class="srdBf"> 172.16.117.66
</span></span></code></pre><p><del>去 VPC 路由表中添加 <code>0.0.0.0/0</code> 下一跳为上述 ECS</del></p><p><del>设置 iptasbles 开机启动：</del></p><h3 id="dnat">DNAT</h3><p>通过 公网 IP 访问集群管理 API</p><pre><code><span class="line" line="1"><span class="sbgvK">iptables</span><span class="stzsN"> -t</span><span class="s_sjI"> nat</span><span class="stzsN"> -I</span><span class="s_sjI"> PREROUTING</span><span class="stzsN"> -p</span><span class="s_sjI"> tcp</span><span class="stzsN"> --dport</span><span class="srdBf"> 6443</span><span class="stzsN"> -j</span><span class="s_sjI"> DNAT</span><span class="stzsN"> --to</span><span class="s_sjI"> 172.16.117.67:6443
</span></span><span class="line" line="2"><span class="sbgvK">iptables</span><span class="stzsN"> -t</span><span class="s_sjI"> nat</span><span class="stzsN"> -I</span><span class="s_sjI"> POSTROUTING</span><span class="stzsN"> -d</span><span class="s_sjI"> 172.16.117.67/32</span><span class="stzsN"> -p</span><span class="s_sjI"> tcp</span><span class="stzsN"> --dport</span><span class="srdBf"> 6443</span><span class="stzsN"> -j</span><span class="s_sjI"> MASQUERADE
</span></span></code></pre><p>记得开启安全组规则允许 6443 端口</p><p>在 k8s 集群信息中设置 自定义证书 SAN 为 47.111.247.217 配置证书，解决以下证书问题：</p><pre><code><span class="line" line="1"><span class="sbgvK">Unable</span><span class="s_sjI"> to</span><span class="s_sjI"> connect</span><span class="s_sjI"> to</span><span class="s_sjI"> the</span><span class="s_sjI"> server:</span><span class="s_sjI"> x509:</span><span class="s_sjI"> certificate</span><span class="s_sjI"> is</span><span class="s_sjI"> valid</span><span class="s_sjI"> for</span><span class="s_sjI"> 172.21.0.1,</span><span class="s_sjI"> 127.0.0.1,</span><span class="s_sjI"> 7.20.49.48,</span><span class="s_sjI"> 172.16.117.67,</span><span class="s_sjI"> not</span><span class="srdBf"> 47.111.247.217
</span></span></code></pre><blockquote><p>参考链接：</p><p><a href="https://yq.aliyun.com/articles/112497" rel="nofollow">如何通过 EIP 实现 VPC 下的 SNAT 以及 DNAT</a></p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2019 年总结暨 2020 年展望]]></title>
        <id>/posts/2020/fighting-2020</id>
        <link href="https://hadb.me/posts/2020/fighting-2020"/>
        <updated>2020-01-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[不知不觉，又独自在电脑前工作到深夜。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200102.fighting-2020/cover.jpg" alt="封面" /><p>不知不觉，又独自在电脑前工作到深夜。</p><p>肚子早已咕噜噜在叫，容我先去搞点吃的。</p><p>没有什么管饱的东西，冰箱里找了两杯味全的酸奶。</p><p>终于忙完了手头的工作，把之前自己的一些外包项目全部迁移到 K8S 上了，阿里云即将停止对 Swarm 的支持，最后一刻完成了迁移。</p><p>又到年底，是时候写点小结了，虽然这会儿的时间点有些晚，但也只有这个时候，夜深人静，才得空。</p><p>回顾去年底定下的 19 年目标，完成了大半：</p><ul className="contains-task-list"><li className="task-list-item"><input checked disabled type="checkbox"></input> 出国旅游一次</li><li className="task-list-item"><input disabled type="checkbox"></input> 锻炼减肥，目标减到 70kg 以下</li><li className="task-list-item"><input disabled type="checkbox"></input> 荒野大镖客通关</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 平均睡眠时间达到 6 小时</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 完成 10 篇可发布的文章</li></ul><p>出国旅游的目标，19 年超额完成了，去了趟泰国，去了趟韩国，花了不少钱。</p><p>减肥一直是个老大难的问题，总是会以忙为借口拖延，自己原谅自己，所以 19 年的体重没有太多的变化。</p><p>游戏没通关，这个也有忙的原因，另一方面，过了那个热乎劲，就再没有动力接着玩了。</p><p>年度的平均睡眠时间勉强达到 6 小时，确实还是比较辛苦。</p><p>2019 年共计发布了 11 篇博客，还包括了一篇 Vlog，勉强完成了 10 篇文章的目标。</p><p>回顾整个 2019 年，主要有这些值得记录的事情：</p><ul><li>1 月，去广州谈了个自己的项目，签了合同一直没打款，没下文了</li><li>3 月，和公司投资部一起去了趟深圳，协助做一家公司的尽调，我负责技术部分。借机和剑芳、沈熠分别见面，聊了很多事情，讨论了很多未来的方向；一个人在深圳湾，吹着海风、看着海鸥、顶着小雨、骑着单车，散了散心，很是惬意。</li><li>3 月，上海建博会，在红星的展位上负责客流统计整套方案的实施和部署，期间也发生了很多曲折的故事，所幸最终还是在展会开始前完成了部署。建博会上，红星搞了很多发布会，智慧商场、机器人都亮了相。</li><li>4 月，公司搬迁到虹桥的办公楼，从此开始了每天开车上下班的生活。</li><li>4 月底，朋程结婚了。</li><li>五一，陪老婆一起去泰国，经澳门转机，去澳门赌场溜达了一圈，涨了涨见识。在泰国吃了好多好吃的，玩了好多好玩的。</li><li>516，熬了几天，完成了金桥开放日的项目。</li><li>5 月下半月，短暂地坚持健身了一段时间，每天去健身房。</li><li>5 月 21 日，珏总请我们参与 516 的同事一起吃饭，见到了不一样的领导们。</li><li>5 月下旬，儿子开始上幼托班了。</li><li>5 月 28 日，飞哥炒币赚了钱，请我们去西贝吃了一顿上千的午饭。</li><li>5 月 31 日，第一次去微软，参加微软上海新址乔迁暨生态庆典。</li><li>6 月初，和销售们一起去重庆，谈 WFC 的客流项目。</li><li>6 月中旬，和公司同事一起去韩国考察，玩了几天，第一次去夜店蹦迪，买东西花了好多钱。</li><li>6 月 25 日，和昔日车钥匙的战友们聚了个餐，讨论天下大事。</li><li>6 月 29 日，去南京苏宁总部参观。</li><li>7 月，经常抽空和兄弟们一起去打羽毛球。</li><li>8 月 3 日，参加了人山人海热爆了的 ChinaJoy，并表示以后再也不去了。那天和大学的两个好基友一起搓了顿饭。</li><li>8 月底，开始疯狂加班，做爱琴海的大屏项目。</li><li>9 月份，常驻爱琴海加班。9 月爱琴海大屏上线后，我开始担任大屏项目负责人的角色，身份和工作内容都发生了很大的转变。</li><li>9 月份，是我们炒币炒得最厉害的一个月，每天在一起聊天的内容就是炒币。晚上都两三点才合眼，火币 APP 统计数据大概每天使用五六个小时。</li><li>9 月 25 日早上醒来，发现夜里比特币合约爆仓，20 万归零，发誓从此再也不炒币。几个一起炒币的兄弟也是血本无归，相约再也不炒币。</li><li>国庆，回了趟老家。</li><li>10 月份，儿子得了肺炎，住院住了好久，回来后夜里经常哭闹，也闹了好久。</li><li>11 月底，金桥大屏新版上线，各种加班。</li><li>12 月初，去了趟珠海，给客户部署两块演示屏，和飞哥在香洲湾的海边骑车吹风，散心。</li><li>12 月下半月，冲刺安卓版，各种往死里加班。</li></ul><p>今年是有史以来第一年出现入不敷出的局面，主要原因有两点，一点是有了小孩之后，开销一下子增大，旅游方面的开销也超出了计划；还有一点是风险投资的比例太高，并且翻了车，这个教训我觉得很值得，现在的教训成本比未来手里有更多钱之后要小很多。今年已经制定了 2020 年的家庭预算，明年要严格执行，开源节流，扭转今年入不敷出的局面。</p><p>2020 年，总归还是要立一些 flag，立一些目标的，不然人活着没有目标岂不是很没意思。</p><p>明年，在工作上会有很大的不同，项目需要去很多城市部署，所以把明年去的城市数量作为一个目标。明年团队一共有 42 个城市需要去部署，我的目标先立个小点的吧，15 个城市。</p><p>减肥的目标今年没有达成，明年还是要继续努力，减到 70kg 的目标还是不变。</p><p>作息时间上，今年也仅仅比去年好一点，但睡眠还是太少，2020 年，希望平均时间能达到 7 小时，每天 1 点前睡觉，周末多睡点，差不多可以达到 7 小时。</p><ul className="contains-task-list"><li className="task-list-item"><input disabled type="checkbox"></input> 去 15 个不同的城市</li><li className="task-list-item"><input disabled type="checkbox"></input> 去健身房 100 次</li><li className="task-list-item"><input disabled type="checkbox"></input> 减脂至 70kg 以下</li><li className="task-list-item"><input disabled type="checkbox"></input> 完成 10 篇文章 / Vlog</li><li className="task-list-item"><input disabled type="checkbox"></input> 收入 > 支出</li><li className="task-list-item"><input disabled type="checkbox"></input> 平均睡眠时间 7 小时</li></ul><p>差不多了，明年能达成这些目标，已经很了不起了。</p><p>2020，加油！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20200105]]></title>
        <id>/posts/2020/diary-20200105</id>
        <link href="https://hadb.me/posts/2020/diary-20200105"/>
        <updated>2020-01-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[2019 年最大的变化就是我的角色的转变，我从一个执行者在向管理者转变。但是在转变的过程中，我发现我比以前更累了。以前做执行者，写代码的时候，由于能力强，效率高，写出来的代码 bug 少，非常轻松；在转管理的过程中，发现我的事情越来越多，也越来越累。这也是 Mylo 一直批评我，拿我当反面教材的地方，所有的事情都堆在我身上的话，我就会成为团队的瓶颈。钟总给 Mylo 他们下了指标，让他们把任务下放，Mylo 也给我们下了指标，让我们把任务也下放，这样才能有精力去思考在这个岗位上更高层次的东西。正如廖老师这次讲的，好的管理者，是很轻松的。这确实是我做的不到位的地方，2020 年需要继续努力，把一些事情交给下面的兄弟们做，锻炼他们，成就他们。]]></summary>
        <content type="html"><![CDATA[<p>2019 年最大的变化就是我的角色的转变，我从一个执行者在向管理者转变。但是在转变的过程中，我发现我比以前更累了。以前做执行者，写代码的时候，由于能力强，效率高，写出来的代码 bug 少，非常轻松；在转管理的过程中，发现我的事情越来越多，也越来越累。这也是 Mylo 一直批评我，拿我当反面教材的地方，所有的事情都堆在我身上的话，我就会成为团队的瓶颈。钟总给 Mylo 他们下了指标，让他们把任务下放，Mylo 也给我们下了指标，让我们把任务也下放，这样才能有精力去思考在这个岗位上更高层次的东西。正如廖老师这次讲的，好的管理者，是很轻松的。这确实是我做的不到位的地方，2020 年需要继续努力，把一些事情交给下面的兄弟们做，锻炼他们，成就他们。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[批量修改阿里云 OSS 的 ACL 权限]]></title>
        <id>/posts/2020/batch-edit-acl-for-oss</id>
        <link href="https://hadb.me/posts/2020/batch-edit-acl-for-oss"/>
        <updated>2020-02-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[oss-browser 是个好工具，但是在修改 ACL 权限上比较蛋疼，只能单个文件设置，不支持批量设置，这在某些默认 ACL 权限为私有的 bucket 上，需要批量设置某个目录为公共读时，会比较不便。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200215.batch-edit-acl-for-oss/cover.png" alt="封面" /><p><code>oss-browser</code> 是个好工具，但是在修改 ACL 权限上比较蛋疼，只能单个文件设置，不支持批量设置，这在某些默认 ACL 权限为私有的 bucket 上，需要批量设置某个目录为公共读时，会比较不便。</p><p>经过搜索，阿里云官方的 <code>ossutil</code> 工具可以用来解决这个问题。</p><h5 id="下载以-mac-系统为例">下载（以 Mac 系统为例）</h5><pre><code><span class="line" line="1"><span class="sbgvK">curl</span><span class="stzsN"> -o</span><span class="s_sjI"> ossutilmac64</span><span class="s_sjI"> http://gosspublic.alicdn.com/ossutil/1.6.10/ossutilmac64
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sbgvK">chmod</span><span class="srdBf"> 755</span><span class="s_sjI"> ossutilmac64
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sbgvK">./ossutilmac64</span><span class="s_sjI"> config</span><span class="sutJx"> # 按照提示填写相关配置，参考https://help.aliyun.com/document_detail/120075.html
</span></span></code></pre><h5 id="测试配置是否正确">测试配置是否正确</h5><pre><code><span class="line" line="1"><span class="sbgvK">./ossutilmac64</span><span class="s_sjI"> ls</span><span class="s_sjI"> oss://your-bucket-name/</span><span class="sutJx"> # 看看能否列出文件列表
</span></span></code></pre><h5 id="批量设置-acl-权限">批量设置 ACL 权限</h5><pre><code><span class="line" line="1"><span class="sbgvK">./ossutilmac64</span><span class="s_sjI"> set-acl</span><span class="s_sjI"> oss://your-bucket-name/your-folder/</span><span class="s_sjI"> public-read</span><span class="stzsN"> --include</span><span class="sjJ54"> "</span><span class="s_sjI">*</span><span class="sjJ54">"</span><span class="stzsN"> -r
</span></span></code></pre><p>Done.</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[有生之年系列之『荒野大镖客』]]></title>
        <id>/posts/2020/best-game-red-dead-redemption-2</id>
        <link href="https://hadb.me/posts/2020/best-game-red-dead-redemption-2"/>
        <updated>2020-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[去年我玩这个游戏的时候，还是看到人想干就干，看到能搜刮的财物就搜刮。但是玩到后面，到了第六章的时候，突然想让亚瑟做个好人，能不干人就不干人，欠债的也都免除了，帮别人做事，别人给你的传家宝也拒绝了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/cover.jpg" alt="封面" /><p>去年我玩这个游戏的时候，还是看到人想干就干，看到能搜刮的财物就搜刮。但是玩到后面，到了第六章的时候，突然想让亚瑟做个好人，能不干人就不干人，欠债的也都免除了，帮别人做事，别人给你的传家宝也拒绝了。</p><p>大镖客这部游戏，刚出来的时候，媒体评价非常高，几乎所有游戏测评机构都给的满分的评价，我当时买 PS4 也是为了玩大镖客才买的。买回来之后玩了一段时间后，公司项目一直很忙，没有大把的时间沉浸其中去玩，偶尔陆陆续续上去骑骑马、打打猎、钓钓鱼，主线基本没有动力再推进下去。再后来大约有一年的时间，连 PS4 都懒得打开了，几乎弃坑。</p><p>直到这次的肺炎疫情，让我有大把的时间在家，一口气玩了几天，白天儿子会干扰我，只能等他睡着了玩，在他睡午觉的时候玩一会儿，晚上睡着后熬夜玩，大概每天到三四点。终于完成了 2019 年未能完成的目标：通关「荒野大镖客」。</p><p>在第六章的末尾，和艾比盖尔辞别之后，<em>That's The Way It Is</em> 的 BGM 缓缓响起，饱受肺结核折磨的亚瑟强撑着跨上马鞍，拿出马鞍袋里的赌徒帽戴在头上，骑着马去完成他最后的救赎。画面中不断浮现出亚瑟摩根这一生中各种人对他的评价，各种角色的声音再次浮现。瞬间鼻子一酸，眼泪都快流下来。我不想跳过这一段的任何一秒，不停地按着 X 键，我想让他骑着马尽情驰骋，因为我知道，这是亚瑟摩根最后一场旅程，这是他最后一次可以这样尽情驰骋了。这也是我有史以来唯一一次，因为游戏中的某个场景，某个音乐而动情。</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/01.jpg"></img></figure><p>之前为了不剧透，很多攻略、直播我看到这里都没有再往下看。后来陪着我，带着我驰骋了整个地图的小黑，也跟我再见了。之前每次小黑不小心死亡又没有提前保存的时候，我甚至不惜回档到几个小时前重新玩，也不想失去小黑。我每次去商店，都会确认马用复苏剂是不是满的，而这次，我知道，即便我身上带满了马用复苏剂，也再救不回它。</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/02.jpg"></img></figure><p>之后便是英雄落幕，和人渣迈卡搏斗时，我疯狂按键，手都按酸了，还是无法改变剧情打败他。最后，亚瑟孤独地躺在悬崖边，在穿过清晨薄雾的阳光下，结束了人生的旅途，完成了他的救赎。</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/03.jpg"></img></figure><p>尾声的环节就略显枯燥，开始以约翰的身份做起了农场主。换了身份后，一下子游戏的动力就缺少了很多，只想赶快完成剧情通关。连我三岁的儿子看到我在玩的时候，都在说，爸爸，这个人不是我们，我们的马呢？这个不是我们的帽子……</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/04.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/05.jpg"></img></figure><p>通关后，短暂地做了几个未完成的支线任务，把 107 个任务都做完了、所有抢劫类型都完成了、所有桌上游戏都完成了、也夺取了 6 个帮派藏身处，再之后，就没有动力再玩了。</p><p>亚瑟摩根用他的死换来了约翰·马斯顿和艾比盖尔、杰克的幸福，最终实现了他想象中的未来。</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/06.jpg"></img></figure><p>回过头来纵观整个游戏，可以发现，沉浸感非常强，亚瑟摩根的形象已经深入我心。剧情非常精彩，虽然节奏很慢，支线很多，但如果花大把时间去认真玩的话，就会完全沉浸到这个角色中。或许开头你以为自己是一个无恶不作的帮派成员，也并不知道你最终会成为一个怎么样的人，但到最后，随着剧情的推进，你会越来越想做个好人。尽管你曾经做了很多坏事，也因为不小心骑马撞死了不少路人，也曾经抢劫银行和警察枪战，杀死了不少无辜的好人。但那都是过去，在亚瑟生命的最后时刻，你会不忍心让他做一个坏人。</p><p>一开始的我，以为这是一个纯粹的西部帮派的故事，打打杀杀，自由度很高。到最后我才发现，原来这部游戏是这么的精彩，前面各种任务，各种支线，都是在铺垫，给最后亚瑟摩根制造了这样一个结局，实在是精彩。游戏中经常会在亚瑟的梦中、昏迷的错觉中出现一只公鹿的画面，正如在知乎上看到的一个观点，这大约是亚瑟的自我象征吧。在族群中，公鹿起着保护族群的作用，它拥有者硕大的鹿角，在面对敌人的时候，成为了一个杀戮者，而亚瑟在帮派中也是扮演这样的角色。但鹿的本性是善良温和的，这也隐喻着亚瑟本质上依旧是一个好人，正如修女临别时和亚瑟说的那样，你是个善良的人，只是你并不了解你自己。</p><figure><img src="https://hadb.me/static/posts/2020/20200216.best-game-red-dead-redemption-2/07.jpg"></img></figure><p>如果要我给「荒野大镖客」打分，我也会打出满分。这是我玩过的最好玩的游戏，没有之一。</p><p>有生之年，不知道还能不能再遇到一个像这样好玩的游戏。</p><blockquote><p>PS：B 站上面亚瑟救赎之路的那段视频：<a href="https://www.bilibili.com/video/BV13t411U7bL" rel="nofollow">「荒野大镖客 2」最感人泪目的 BGM 我哭了你那？亚瑟的救赎之路(鹿) 收藏纪念把，结局前(剧透)请完成主线后再来重温音乐</a></p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[iTerm2login]]></title>
        <id>/posts/2020/iterm2login</id>
        <link href="https://hadb.me/posts/2020/iterm2login"/>
        <updated>2020-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[iterm2login.sh 文件：]]></summary>
        <content type="html"><![CDATA[<p>iterm2login.sh 文件：</p><pre><code><span class="line" line="1"><span class="sutJx">#!/usr/bin/expect
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sptTA">set</span><span class="s_sjI"> timeout</span><span class="srdBf"> 30
</span></span><span class="line" line="4"><span class="sbgvK">spawn</span><span class="s_sjI"> ssh</span><span class="stzsN"> -p</span><span class="su5hD"> [lindex $argv </span><span class="s_sjI">0]</span><span class="su5hD"> [lindex $argv </span><span class="s_sjI">1]@[lindex</span><span class="su5hD"> $argv </span><span class="s_sjI">2]
</span></span><span class="line" line="5"><span class="sbgvK">expect</span><span class="s_sjI"> {
</span></span><span class="line" line="6"><span class="sbgvK">        "(yes/no)?"
</span></span><span class="line" line="7"><span class="su5hD">        {</span><span class="sbgvK">send</span><span class="sjJ54"> "</span><span class="s_sjI">yes\n</span><span class="sjJ54">"</span><span class="sP7_E">;</span><span class="sbgvK">exp_continue}
</span></span><span class="line" line="8"><span class="sbgvK">        "password:"
</span></span><span class="line" line="9"><span class="su5hD">        {</span><span class="sbgvK">send</span><span class="sjJ54"> "</span><span class="s_sjI">[lindex </span><span class="su5hD">$argv</span><span class="s_sjI"> 3]\n</span><span class="sjJ54">"</span><span class="s_sjI">}
</span></span><span class="line" line="10"><span class="su5hD">}
</span></span><span class="line" line="11"><span class="sbgvK">interact
</span></span></code></pre><p>复制配置到其他 Profile：Other Actions -> Bulk Copy from Selected Profile</p><p>Session -> Status Bar</p><p>iTerm2 -> Install Shell Integration</p><p>参考：<a href="https://www.iterm2.com/documentation-shell-integration.html" rel="nofollow">Shell Integration</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[k8s 上利用 cert-manager 自动签发 TLS 证书]]></title>
        <id>/posts/2020/k8s-cert-manager-tls</id>
        <link href="https://hadb.me/posts/2020/k8s-cert-manager-tls"/>
        <updated>2020-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[很多博主的 https 证书经常容易忘记更新，虽说证书过期前都会有邮件提醒，但是万一确实忙得没时间去处理，忘记了，就会出现证书过期的情况了。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200227.k8s-cert-manager-tls/cover.png" alt="封面" /><p>很多博主的 <code>https</code> 证书经常容易忘记更新，虽说证书过期前都会有邮件提醒，但是万一确实忙得没时间去处理，忘记了，就会出现证书过期的情况了。</p><p>之前在服务器上自己搭博客服务的时候，用 <code>Let's Encrypt</code> 来自动创建并续签证书，确实省了不少事。</p><p>在我的博客部署到 <code>k8s</code> 之后，就一直用的一年一签的免费证书，每年更新一次，也不算特别麻烦，但是总归不够高端，我又怀念起了 <code>Let's Encrypt</code>。</p><p><code>Let's Encrypt</code> 是个好东西，<code>k8s</code> 也是个好东西，两个好东西怎么结合呢？搜寻了一番确实有方案，经过几天的尝试，终于弄好了。花了几天是因为第一天因为有个粗心导致的问题，导致搞了好久没成功，休息了几天再次尝试，才找到问题。</p><p>有关 <code>k8s</code> 的基础知识，这里不做赘述，网上教程很多，这里假设大家对 <code>k8s</code> 都有一定了解。</p><h4 id="安装-cert-manager">安装 cert-manager</h4><p>安装 <code>helm</code> 到本地</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> brew</span><span class="s_sjI"> install</span><span class="s_sjI"> helm
</span></span></code></pre><p>添加仓库和命名空间</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> create</span><span class="s_sjI"> namespace</span><span class="s_sjI"> cert-manager</span><span class="sutJx"> # 创建 cert-manager 命名空间
</span></span><span class="line" line="2"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> label</span><span class="s_sjI"> namespace</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> certmanager.io/disable-validation=</span><span class="s39Yj">true</span><span class="sutJx"> # 标记 cert-manager 命名空间以禁用资源验证
</span></span><span class="line" line="3"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> apply</span><span class="stzsN"> --validate=false</span><span class="stzsN"> -f</span><span class="s_sjI"> https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager-legacy.crds.yaml</span><span class="sutJx"> # 安装 CustomResourceDefinition 资源，注意 k8s 版本低于 1.15 需要用 legacy 版本
</span></span><span class="line" line="4"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> repo</span><span class="s_sjI"> add</span><span class="s_sjI"> jetstack</span><span class="s_sjI"> https://charts.jetstack.io</span><span class="sutJx"> # 添加 Jetstack Helm repository
</span></span><span class="line" line="5"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> repo</span><span class="s_sjI"> update</span><span class="sutJx"> # 更新本地 Helm chart repository
</span></span></code></pre><p>安装 <code>cert-manager</code></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> install</span><span class="s_sjI"> cert-manager</span><span class="stzsN"> --namespace</span><span class="s_sjI"> cert-manager</span><span class="stzsN"> --version</span><span class="s_sjI"> v0.14.1</span><span class="s_sjI"> jetstack/cert-manager
</span></span></code></pre><p>查看 <code>cert-manager</code> 安装情况</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> get</span><span class="s_sjI"> pods</span><span class="stzsN"> --namespace</span><span class="s_sjI"> cert-manager
</span></span><span class="line" line="2"><span class="sbgvK">NAME</span><span class="s_sjI">                                       READY</span><span class="s_sjI">   STATUS</span><span class="s_sjI">    RESTARTS</span><span class="s_sjI">   AGE
</span></span><span class="line" line="3"><span class="sbgvK">cert-manager-6cff8dc7b9-8vxws</span><span class="s_sjI">              1/1</span><span class="s_sjI">     Running</span><span class="srdBf">   0</span><span class="s_sjI">          4d10h
</span></span><span class="line" line="4"><span class="sbgvK">cert-manager-cainjector-795c46858f-txczb</span><span class="s_sjI">   1/1</span><span class="s_sjI">     Running</span><span class="srdBf">   0</span><span class="s_sjI">          4d10h
</span></span><span class="line" line="5"><span class="sbgvK">cert-manager-webhook-5dfc77cd74-skgsv</span><span class="s_sjI">      1/1</span><span class="s_sjI">     Running</span><span class="srdBf">   0</span><span class="s_sjI">          4d10h
</span></span></code></pre><h4 id="更新-cert-manager">更新 cert-manager</h4><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> delete</span><span class="stzsN"> -n</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> deployment</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> cert-manager-cainjector</span><span class="s_sjI"> cert-manager-webhook
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> apply</span><span class="stzsN"> --validate=false</span><span class="stzsN"> -f</span><span class="s_sjI"> https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager-legacy.crds.yaml
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> repo</span><span class="s_sjI"> update
</span></span><span class="line" line="6"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> upgrade</span><span class="stzsN"> --version</span><span class="s_sjI"> v0.14.1</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> jetstack/cert-manager</span><span class="stzsN"> -n</span><span class="s_sjI"> cert-manager
</span></span></code></pre><h4 id="创建-clusterissuer">创建 ClusterIssuer</h4><p>我们需要创建一个签发机构，<code>cert-manager</code> 提供了<code>Issuer</code> 和 <code>ClusterIssuer</code> 两种类型的签发机构，<code>Issuer</code> 只能用来签发自己所在命名空间下的证书，ClusterIssuer 可以签发任意命名空间下的证书，我这里用 <code>ClusterIssuer</code> 为例，创建 <code>letsencrypt-prod.yaml</code> 文件：</p><pre><code><span class="line" line="1"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> cert-manager.io/v1alpha2
</span></span><span class="line" line="2"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> ClusterIssuer
</span></span><span class="line" line="3"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  labels</span><span class="sP7_E">:
</span></span><span class="line" line="5"><span class="sQzsp">    name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod
</span></span><span class="line" line="6"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod</span><span class="sutJx"> # 自定义的签发机构名称，后面会引用
</span></span><span class="line" line="7"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="8"><span class="sQzsp">  acme</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sQzsp">    email</span><span class="sP7_E">:</span><span class="s_sjI"> yourname@youremail.com</span><span class="sutJx"> # 你的邮箱，证书快过期的时候会邮件提醒，不过我们可以设置自动续期
</span></span><span class="line" line="10"><span class="sQzsp">    solvers</span><span class="sP7_E">:
</span></span><span class="line" line="11"><span class="sP7_E">      -</span><span class="sQzsp"> http01</span><span class="sP7_E">:
</span></span><span class="line" line="12"><span class="sQzsp">          ingress</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sQzsp">            class</span><span class="sP7_E">:</span><span class="s_sjI"> nginx
</span></span><span class="line" line="14"><span class="sQzsp">    privateKeySecretRef</span><span class="sP7_E">:
</span></span><span class="line" line="15"><span class="sQzsp">      name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod</span><span class="sutJx"> # 指示此签发机构的私钥将要存储到哪个 Secret 对象中
</span></span><span class="line" line="16"><span class="sQzsp">    server</span><span class="sP7_E">:</span><span class="s_sjI"> https://acme-v02.api.letsencrypt.org/directory</span><span class="sutJx"> # acme 协议的服务端，我们用 Let's Encrypt
</span></span></code></pre><p>应用 <code>yaml</code></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> create</span><span class="stzsN"> -f</span><span class="s_sjI"> letsencrypt-prod.yaml
</span></span></code></pre><p>查看状态</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> get</span><span class="s_sjI"> clusterissuer
</span></span><span class="line" line="2"><span class="sbgvK">NAME</span><span class="s_sjI">               READY</span><span class="s_sjI">   AGE
</span></span><span class="line" line="3"><span class="sbgvK">letsencrypt-prod</span><span class="s_sjI">   True</span><span class="s_sjI">    51s
</span></span></code></pre><h4 id="手动签发证书">手动签发证书</h4><p>手动签发证书，创建 <code>test-monkeyrun-net-cert.yaml</code> 文件</p><pre><code><span class="line" line="1"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> cert-manager.io/v1alpha2
</span></span><span class="line" line="2"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> Certificate
</span></span><span class="line" line="3"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> test-monkeyrun-net-cert
</span></span><span class="line" line="5"><span class="sQzsp">  namespace</span><span class="sP7_E">:</span><span class="s_sjI"> test
</span></span><span class="line" line="6"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="7"><span class="sQzsp">  secretName</span><span class="sP7_E">:</span><span class="s_sjI"> tls-test-monkeyrun-net</span><span class="sutJx"> # 证书保存的 secret 名
</span></span><span class="line" line="8"><span class="sQzsp">  duration</span><span class="sP7_E">:</span><span class="s_sjI"> 2160h</span><span class="sutJx"> # 90d
</span></span><span class="line" line="9"><span class="sQzsp">  renewBefore</span><span class="sP7_E">:</span><span class="s_sjI"> 720h</span><span class="sutJx"> # 30d
</span></span><span class="line" line="10"><span class="sQzsp">  dnsNames</span><span class="sP7_E">:
</span></span><span class="line" line="11"><span class="sP7_E">    -</span><span class="s_sjI"> test.monkeyrun.net
</span></span><span class="line" line="12"><span class="sQzsp">  issuerRef</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sQzsp">    name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod
</span></span><span class="line" line="14"><span class="sQzsp">    kind</span><span class="sP7_E">:</span><span class="s_sjI"> ClusterIssuer
</span></span><span class="line" line="15"><span class="sQzsp">    group</span><span class="sP7_E">:</span><span class="s_sjI"> cert-manager.io
</span></span></code></pre><p>应用 <code>yaml</code></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> apply</span><span class="stzsN"> -f</span><span class="s_sjI"> test-monkeyrun-net-cert.yaml
</span></span></code></pre><p>检查是否生成证书文件</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> get</span><span class="s_sjI"> certificate</span><span class="stzsN"> -n</span><span class="s_sjI"> test
</span></span><span class="line" line="2"><span class="sbgvK">NAME</span><span class="s_sjI">                      READY</span><span class="s_sjI">   SECRET</span><span class="s_sjI">                   AGE
</span></span><span class="line" line="3"><span class="sbgvK">test-monkeyrun-net-cert</span><span class="s_sjI">   True</span><span class="s_sjI">    test-monkeyrun-net-tls</span><span class="s_sjI">   99m
</span></span></code></pre><p>将该证书配置到 <code>test.monkeyrun.net</code> 的 <code>ingress</code> 上，测试 <code>https</code> 访问，成功。</p><h4 id="创建deployment时自动签发证书"><del>创建Deployment时自动签发证书</del></h4><p><del>创建 <code>test-nginx.yaml</code></del></p><pre><code><span class="line" line="1"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> extensions/v1beta1
</span></span><span class="line" line="2"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> Deployment
</span></span><span class="line" line="3"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="5"><span class="sQzsp">  namespace</span><span class="sP7_E">:</span><span class="s_sjI"> test
</span></span><span class="line" line="6"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="7"><span class="sQzsp">  replicas</span><span class="sP7_E">:</span><span class="srdBf"> 1
</span></span><span class="line" line="8"><span class="sQzsp">  template</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sQzsp">    metadata</span><span class="sP7_E">:
</span></span><span class="line" line="10"><span class="sQzsp">      labels</span><span class="sP7_E">:
</span></span><span class="line" line="11"><span class="sQzsp">        run</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="12"><span class="sQzsp">    spec</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sQzsp">      containers</span><span class="sP7_E">:
</span></span><span class="line" line="14"><span class="sP7_E">        -</span><span class="sQzsp"> name</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="15"><span class="sQzsp">          image</span><span class="sP7_E">:</span><span class="s_sjI"> nginx
</span></span><span class="line" line="16"><span class="sQzsp">          ports</span><span class="sP7_E">:
</span></span><span class="line" line="17"><span class="sP7_E">            -</span><span class="sQzsp"> containerPort</span><span class="sP7_E">:</span><span class="srdBf"> 80
</span></span><span class="line" line="18"><span class="sbgvK">---
</span></span><span class="line" line="19"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> v1
</span></span><span class="line" line="20"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> Service
</span></span><span class="line" line="21"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="22"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="23"><span class="sQzsp">  namespace</span><span class="sP7_E">:</span><span class="s_sjI"> test
</span></span><span class="line" line="24"><span class="sQzsp">  labels</span><span class="sP7_E">:
</span></span><span class="line" line="25"><span class="sQzsp">    app</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="26"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="27"><span class="sQzsp">  ports</span><span class="sP7_E">:
</span></span><span class="line" line="28"><span class="sP7_E">    -</span><span class="sQzsp"> port</span><span class="sP7_E">:</span><span class="srdBf"> 80
</span></span><span class="line" line="29"><span class="sQzsp">      protocol</span><span class="sP7_E">:</span><span class="s_sjI"> TCP
</span></span><span class="line" line="30"><span class="sQzsp">      name</span><span class="sP7_E">:</span><span class="s_sjI"> http
</span></span><span class="line" line="31"><span class="sQzsp">  selector</span><span class="sP7_E">:
</span></span><span class="line" line="32"><span class="sQzsp">    run</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="33"><span class="sbgvK">---
</span></span><span class="line" line="34"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> extensions/v1beta1
</span></span><span class="line" line="35"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> Ingress
</span></span><span class="line" line="36"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="37"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="38"><span class="sQzsp">  namespace</span><span class="sP7_E">:</span><span class="s_sjI"> test
</span></span><span class="line" line="39"><span class="sQzsp">  annotations</span><span class="sP7_E">:
</span></span><span class="line" line="40"><span class="sQzsp">    kubernetes.io/ingress.class</span><span class="sP7_E">:</span><span class="s_sjI"> nginx
</span></span><span class="line" line="41"><span class="sQzsp">    kubernetes.io/tls-acme</span><span class="sP7_E">:</span><span class="sjJ54"> '</span><span class="s_sjI">true</span><span class="sjJ54">'
</span></span><span class="line" line="42"><span class="sQzsp">    certmanager.io/cluster-issuer</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod
</span></span><span class="line" line="43"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="44"><span class="sQzsp">  rules</span><span class="sP7_E">:
</span></span><span class="line" line="45"><span class="sP7_E">    -</span><span class="sQzsp"> host</span><span class="sP7_E">:</span><span class="s_sjI"> test.monkeyrun.net
</span></span><span class="line" line="46"><span class="sQzsp">      http</span><span class="sP7_E">:
</span></span><span class="line" line="47"><span class="sQzsp">        paths</span><span class="sP7_E">:
</span></span><span class="line" line="48"><span class="sP7_E">          -</span><span class="sQzsp"> backend</span><span class="sP7_E">:
</span></span><span class="line" line="49"><span class="sQzsp">              serviceName</span><span class="sP7_E">:</span><span class="s_sjI"> test-nginx
</span></span><span class="line" line="50"><span class="sQzsp">              servicePort</span><span class="sP7_E">:</span><span class="srdBf"> 80
</span></span><span class="line" line="51"><span class="sQzsp">            path</span><span class="sP7_E">:</span><span class="s_sjI"> /
</span></span><span class="line" line="52"><span class="sQzsp">  tls</span><span class="sP7_E">:
</span></span><span class="line" line="53"><span class="sP7_E">    -</span><span class="sQzsp"> secretName</span><span class="sP7_E">:</span><span class="s_sjI"> tls-test-monkeyrun-net
</span></span><span class="line" line="54"><span class="sQzsp">      hosts</span><span class="sP7_E">:
</span></span><span class="line" line="55"><span class="sP7_E">        -</span><span class="s_sjI"> test.monkeyrun.net
</span></span></code></pre><p><del>删除之前手动创建的 <code>Deployment</code>、<code>Service</code> 、 <code>Ingress</code> 和 <code>Secret</code> 后， 应用 <code>yaml</code> 来自动创建</del></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> apply</span><span class="stzsN"> -f</span><span class="s_sjI"> test-nginx.yaml
</span></span></code></pre><p><del>打开 <code>https://test.monkeyrun.net</code> 测试，成功！</del></p><p>不知为何再次使用自动签发证书的时候会报错：</p><pre><code>E0330 07:46:30.070412       1 sync.go:57] cert-manager/controller/ingress-shim "msg"="failed to determine issuer to be used for ingress resource" "error"="failed to determine issuer name to be used for ingress resource" "resource_kind"="Ingress" "resource_name"="xxx" "resource_namespace"="xxx"
</code></pre><p>解决了半天都没能找到问题，所以还是用手动签发吧，反正也是一次性的操作。</p><h4 id="通过-dns-验证域名">通过 DNS 验证域名</h4><p>刚才通过 http01 的方式验证域名会有个问题，对于已经部署上线的项目，没办法去验证，所以可以通过 dns 的方式来验证。</p><p><del>经过搜寻，找到了几篇文章，都是利用 <a href="https://github.com/kevinniu666" rel="nofollow">kevinniu666</a> 这位仁兄基于  <a href="https://github.com/jetstack/cert-manager-webhook-example" rel="nofollow">jetstack/cert-manager-webhook-example</a> 改成 <code>alidns</code> 的版本来搞的，不过尝试了下，他这里面 <code>cert-manager</code> 版本太老已经跑不起来了，从 GitHub 的 forks 树里面找到了最新的一个 fork，<a href="https://github.com/colprog/cert-manager-webhook-alidns" rel="nofollow">colprog/cert0manager-webhooks-alidns</a>，尝试了下，也不行，他应该是改了镜像，但是不可用了。重新尝试了下上一代 fork <a href="https://github.com/pangzineng/cert-manager-webhook-alidns" rel="nofollow">pangzineng/cert-manager-webhook-alidns</a>，可用。</del></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> git</span><span class="s_sjI"> clone</span><span class="s_sjI"> https://github.com/pangzineng/cert-manager-webhook-alidns.git
</span></span><span class="line" line="2"><span class="sbgvK">$</span><span class="s_sjI"> cd</span><span class="s_sjI"> cert-manager-webhook-alidns
</span></span><span class="line" line="3"><span class="sbgvK">$</span><span class="s_sjI"> helm</span><span class="s_sjI"> install</span><span class="s_sjI"> cert-manager-webhook-alidns</span><span class="stzsN"> --namespace=cert-manager</span><span class="s_sjI"> ./deploy/webhook-alidns
</span></span></code></pre><p><del>创建 alidns AccessKey Id 和 Secret</del></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="stzsN"> -n</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> create</span><span class="s_sjI"> secret</span><span class="s_sjI"> generic</span><span class="s_sjI"> alidns-access-key-id</span><span class="stzsN"> --from-literal=accessKeyId=</span><span class="sjJ54">'</span><span class="s_sjI">xxxxxxx</span><span class="sjJ54">'
</span></span><span class="line" line="2"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="stzsN"> -n</span><span class="s_sjI"> cert-manager</span><span class="s_sjI"> create</span><span class="s_sjI"> secret</span><span class="s_sjI"> generic</span><span class="s_sjI"> alidns-access-key-secret</span><span class="stzsN"> --from-literal=accessKeySecret=</span><span class="sjJ54">'</span><span class="s_sjI">xxxxxxx</span><span class="sjJ54">'
</span></span></code></pre><p>更新：使用 <a href="https://github.com/pragkent/alidns-webhook/tree/master" rel="nofollow">pragkent/alidns-webhook</a></p><p>修改我们之前创建的 <code>letsencrypt-prod.yaml</code></p><pre><code><span class="line" line="1"><span class="sQzsp">apiVersion</span><span class="sP7_E">:</span><span class="s_sjI"> cert-manager.io/v1
</span></span><span class="line" line="2"><span class="sQzsp">kind</span><span class="sP7_E">:</span><span class="s_sjI"> ClusterIssuer
</span></span><span class="line" line="3"><span class="sQzsp">metadata</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  labels</span><span class="sP7_E">:
</span></span><span class="line" line="5"><span class="sQzsp">    name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod
</span></span><span class="line" line="6"><span class="sQzsp">  name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod</span><span class="sutJx"> # 自定义的签发机构名称，后面会引用
</span></span><span class="line" line="7"><span class="sQzsp">spec</span><span class="sP7_E">:
</span></span><span class="line" line="8"><span class="sQzsp">  acme</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sQzsp">    email</span><span class="sP7_E">:</span><span class="s_sjI"> yourname@youremail.com</span><span class="sutJx"> # 你的邮箱，证书快过期的时候会邮件提醒，不过我们可以设置自动续期
</span></span><span class="line" line="10"><span class="sQzsp">    solvers</span><span class="sP7_E">:
</span></span><span class="line" line="11"><span class="sP7_E">      -</span><span class="sQzsp"> dns01</span><span class="sP7_E">:
</span></span><span class="line" line="12"><span class="sQzsp">          webhook</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sQzsp">            groupName</span><span class="sP7_E">:</span><span class="s_sjI"> yourgroup.com
</span></span><span class="line" line="14"><span class="sQzsp">            solverName</span><span class="sP7_E">:</span><span class="s_sjI"> alidns
</span></span><span class="line" line="15"><span class="sQzsp">            config</span><span class="sP7_E">:
</span></span><span class="line" line="16"><span class="sQzsp">              region</span><span class="sP7_E">:</span><span class="sjJ54"> ''
</span></span><span class="line" line="17"><span class="sQzsp">              accessKeySecretRef</span><span class="sP7_E">:
</span></span><span class="line" line="18"><span class="sQzsp">                name</span><span class="sP7_E">:</span><span class="s_sjI"> alidns-secret
</span></span><span class="line" line="19"><span class="sQzsp">                key</span><span class="sP7_E">:</span><span class="s_sjI"> access-key
</span></span><span class="line" line="20"><span class="sQzsp">              secretKeySecretRef</span><span class="sP7_E">:
</span></span><span class="line" line="21"><span class="sQzsp">                name</span><span class="sP7_E">:</span><span class="s_sjI"> alidns-secret
</span></span><span class="line" line="22"><span class="sQzsp">                key</span><span class="sP7_E">:</span><span class="s_sjI"> secret-key
</span></span><span class="line" line="23"><span class="sQzsp">    privateKeySecretRef</span><span class="sP7_E">:
</span></span><span class="line" line="24"><span class="sQzsp">      name</span><span class="sP7_E">:</span><span class="s_sjI"> letsencrypt-prod-account-key</span><span class="sutJx"> # 指示此签发机构的私钥将要存储到哪个 Secret 对象中
</span></span><span class="line" line="25"><span class="sQzsp">    server</span><span class="sP7_E">:</span><span class="s_sjI"> https://acme-v02.api.letsencrypt.org/directory</span><span class="sutJx"> # acme 协议的服务端，我们用 Let's Encrypt
</span></span></code></pre><p>应用 <code>yaml</code></p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> create</span><span class="stzsN"> -f</span><span class="s_sjI"> letsencrypt-prod.yaml
</span></span></code></pre><p>查看状态</p><pre><code><span class="line" line="1"><span class="sbgvK">$</span><span class="s_sjI"> kubectl</span><span class="s_sjI"> get</span><span class="s_sjI"> clusterissuer
</span></span><span class="line" line="2"><span class="sbgvK">NAME</span><span class="s_sjI">               READY</span><span class="s_sjI">   AGE
</span></span><span class="line" line="3"><span class="sbgvK">letsencrypt-prod</span><span class="s_sjI">   True</span><span class="s_sjI">    51s
</span></span></code></pre><p>重新手动签发证书，验证，成功！</p><p>PS：需要注意的是，从 http01 认证修改到 dns01 认证后，有个坑，会一直失败，查看 cert-manager 的 Pod 日志，会发现如下错误：</p><pre><code><span class="line" line="1"><span class="su5hD">cert-manager/controller/orders </span><span class="s_sjI">"msg"</span><span class="su5hD">=</span><span class="s_sjI">"Failed to determine the list of Challenge resources needed for the Order"</span><span class="s_sjI"> "error"</span><span class="su5hD">=</span><span class="s_sjI">"no configured challenge solvers can be used for this challenge"</span><span class="s_sjI"> "resource_kind"</span><span class="su5hD">=</span><span class="s_sjI">"Order"</span><span class="s_sjI"> "resource_name"</span><span class="su5hD">=</span><span class="s_sjI">"xxx"
</span></span></code></pre><p>研究了半天都没成功，后来在 GitHub 上找到了这个 <a href="https://github.com/jetstack/cert-manager/issues/2494#issuecomment-585391545" rel="nofollow">Issue</a>，按照 <a href="https://github.com/demisx" rel="nofollow">demisx</a> 这位仁兄的建议，把所有和 <code>cert-manager</code> 相关的东西全部删除重新用 dns01 的方式部署一遍就 OK 了。</p><p>另外，cert-manager 的 API group 从 <code>certmanager.k8s.io</code> 改到 <code>certmanager.io</code> 了，不少老教程里面仍然是前者，需要改为后者才能正常执行。</p><blockquote><p>参考链接</p><ul><li><a href="https://docs.bitnami.com/kubernetes/how-to/secure-kubernetes-services-with-ingress-tls-letsencrypt/" rel="nofollow">Secure Kubernetes Services With Ingress, TLS And Let's Encrypt</a></li><li><a href="https://xuchao918.github.io/2019/03/14/%E2%95%A9%E2%95%A3%E2%95%99%E2%94%9Ccert-manager%E2%95%A9%E2%95%A1%E2%95%A7%E2%95%93Ingress-https/" rel="nofollow">使用 cert-manager 实现 Ingress https</a></li><li><a href="https://yq.aliyun.com/articles/718711" rel="nofollow">使用 cert-manager 给阿里云的 DNS 域名授权 SSL 证书</a></li><li><a href="https://cert-manager.io/docs/" rel="nofollow">cert-manager docs</a></li></ul></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[第一次胃肠镜记录]]></title>
        <id>/posts/2020/record-of-endoscopy-and-colonoscopy</id>
        <link href="https://hadb.me/posts/2020/record-of-endoscopy-and-colonoscopy"/>
        <updated>2020-04-27T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h4 id="_04-22">04-22</h4><p>开始发现左下腹有些隐隐作痛，按压会痛，大便不成型。</p><h4 id="_04-23">04-23</h4><p>大便依旧不成型，晚上发现痛感还在，去医院挂急诊，验血，拍 CT 平扫，当时临时报告说没问题，只看到脂肪肝，验血中白细胞和淋巴细胞数值偏高，有轻微炎症，医生开了双歧杆菌和得舒特匹维溴铵片。</p><h4 id="_04-24">04-24</h4><p>喝了牛奶，拉稀。下午 CT 正式报告出来，医生打电话告知正式报告出来，怀孕结肠有些问题，需要来拿正式报告，并做进一步检查。去医院，预约了下周一胃肠镜，医生开了泻药，左氧氟沙星和双歧杆菌。</p><h4 id="_04-25">04-25</h4><p>早上大便比之前好多了，下午吃了点西瓜，晚上吃了一个有点辣的羊肉串，晚上又有些拉肚子，糊状。</p><p>今天痛感几乎感觉不到，按压也没啥痛感了。</p><p>由于昨天中午玩了一会儿同事的健腹轮，今天整个腹部的肌肉一用力都是酸痛的。</p><h4 id="_04-27">04-27</h4><ul><li>06:00 起床先上了个厕所，由于昨天都喝的粥，也没啥便，一半硬一半糊。拉完开始找容器配置聚乙二醇溶液</li><li>06:15 开始喝聚乙二醇</li><li>06:25 终于喝完600ml，味道和口感倒还能接受，主要一下子600ml的量有点多，确实几次想吐，忍住了，200ml一杯分3次喝完了。喝完得走动，坐着容易想吐。不敢打嗝，生怕吐出来</li><li>06:30 开始有便意，小拉了一下</li><li>06:40 再次喝150ml，边走动边刷刷抖音</li><li>06:45 再次有便意，这次是几乎喷出来的感觉，放开闸门，就任由水流流出。不知为啥，甚至有点流鼻涕，鼻涕都吓出来了。此时大便还没有清澈，还有不少杂质</li><li>06:55 再次喝150ml</li><li>06:58 再次拉，杂质不多了，颜色还不对</li><li>07:10 拉</li><li>07:11 喝</li><li>07:14 发现刚才每次喝的150ml，说明书应该喝250ml，要开始加量提速追进度了</li><li>07:20 喝250ml</li><li>07:22 拉，颜色淡了很多</li><li>07:28 拉</li><li>07:30 喝了150ml，喝不下了</li><li>07:39 拉，这次感觉拉了好多</li><li>07:41 感觉胃里很满喝不下，准备跳一会再喝</li><li>07:44 打了个嗝，好多了</li><li>07:45 喝150ml</li><li>07:50 喝150ml</li><li>07:53 拉，已经没有任何杂质了，但是颜色还有些黄，不够淡</li><li>08:02 感觉喝不下，再歇歇，嘴里渗口水，感觉要吐。舔了点白糖，感觉好了一些。打了个嗝，更好些了</li><li>08:05 喝150ml，这次没能一口气喝完，中断了两次，本来想喝200，还剩50时来了便意</li><li>08:08 拉。颜色淡了很多了，已经比较淡了。还剩下300左右不想喝了。毕竟肚子里应该还有不少</li><li>08:15 刚才拉完菊花有点痛</li><li>08:18 拉</li><li>08:22 拉</li><li>08:52 又拉了一次，基本清澈了</li><li>10:05 主动去厕所再拉了一次，没多少量了。但是拉完菊花好痛，不能碰</li><li>14:00 出发去医院</li><li>14:26 喝了一瓶麻药，喉咙没知觉了，难受，吞咽也难受</li><li>14:30 吊水，乳酸钠林格，没开始滴</li><li>14:34 躺到临时的手术床上等待。根据回忆，后来让我躺好，腿蜷缩往前顶着，没几分钟麻醉师开始给我嘴巴含住一个东西，然后鼻子插上氧气管，麻醉师骗我说是氧气，让我大口吸，我闻那味道也不太像，猜到是麻醉剂，估计10秒左右就没记忆了</li><li>15:00 被喊醒了，有点迷迷糊糊
勉强能打字，站不稳，像睡着一样，打了几个嗝，思维基本恢复，小腹有点痛</li><li>15:03 老婆搀我出来了，走不稳，像喝醉了，没力，使不上劲，思维没问题，打字正常，喉咙恢复知觉，裤子护士帮忙穿好了，腹部有轻微疼痛，记忆停留在嘴巴含着呼吸装置，鼻子插上呼吸管，后面没记忆。这会儿打了好多错别字，估计思维还没完全恢复，像打瞌睡时的智力。坐到等候区，坐下去就爬不起来了。改了一些错别字</li><li>15:11 这会儿恢复了一点精神，看东西很清晰。还是没力气爬不起来，但是手机打字很迅速。肠道有气在蠕动，估计待会儿要放屁。思维还算清晰</li><li>15:20 结肠检查没问题，肠道都没问题。但是顺带的胃镜检查到有反流性食管炎（A级）和慢性浅表性胃炎伴糜烂。肠道有胀气，有轻微疼痛</li><li>15:26 放了一个很长的屁，肚子舒服了些。打车回家</li><li>15:35 刚才又放了一个很长的屁，肚子又舒坦了些</li><li>15:44 下车了，又放了个屁，肚子舒服些，还有一点不适</li><li>15:48 陆续放了几个屁，舒服多了</li><li>15:52 往床上一躺，躺下瞬间感觉重心不稳，没力气，麻醉还没完全恢复，我要躺下休息会</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[鹊桥仙·日料团建]]></title>
        <id>/posts/2020/japanese-cuisine-team-building</id>
        <link href="https://hadb.me/posts/2020/japanese-cuisine-team-building"/>
        <updated>2020-06-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[割烹料理，]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200630.japanese-cuisine-team-building/cover.jpg" alt="封面" /><p>割烹料理，</p><p>爱琴海轿，</p><p>今世奇才相聚。</p><p>一壶清酒解忧愁，</p><p>好似那乌云散去。</p><p>人生苦短，</p><p>及时行乐，</p><p>梦想无需顾虑。</p><p>十年之后论英雄，</p><p>翻山越岭约再叙。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如梦令·老友聚餐]]></title>
        <id>/posts/2020/old-friend-dinner</id>
        <link href="https://hadb.me/posts/2020/old-friend-dinner"/>
        <updated>2020-07-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昔日并肩老友，]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20200702.old-friend-dinner/cover.jpg" alt="封面" /><p>昔日并肩老友，</p><p>聚会阔别良久。</p><p>此事屡推迟，</p><p>今夜终约喝酒。</p><p>嗨否，</p><p>嗨否？</p><p>但愿友情不朽。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何成为一名全栈开发工程师]]></title>
        <id>/posts/2020/how-to-become-a-fullstack-developer</id>
        <link href="https://hadb.me/posts/2020/how-to-become-a-fullstack-developer"/>
        <updated>2020-11-20T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/cover.jpg" alt="封面" /><h3 id="什么是全栈开发工程师">什么是全栈开发工程师</h3><p>“全栈”这个概念最早来源于 Facebook 工程师 Carlos Bueno 在 2010 年底写的一篇文章：<a href="http://carlos.bueno.org/2010/11/full-stack.html" rel="nofollow"><em>The Full Stack</em></a>。</p><p>作者认为全栈是一个通才，能够自己创建不平凡的应用程序。</p><p>他也指出，没人能够熟悉所有方方面面，但作为一个全栈，能够看清每个栈的上下之间是如何运作的。</p><p>我们再看看百度百科的定义：全栈工程师是指掌握多种技能，胜任前端与后端，能利用多种技能独立完成产品的人。</p><p>百度百科的定义稍微有些狭义，全栈的技能远远不止前端与后端，但在大多数情况下，熟练掌握前后端就可以成为一名别人眼中的全栈。</p><p>在我的理解中，全栈开发工程师除了传统大家理解的前端、后端、移动端之外，还必须具备设计与运维的技能。若不具备这两项技能，还是没办法独立完成一个产品的，在设计与运维阶段，还是得依赖于其他人，或者说无法更独立、更高效地完成一个产品。</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/01.png"></img></figure><h3 id="全栈开发工程师存在的意义">全栈开发工程师存在的意义</h3><p>我们了解了什么是全栈开发工程师，那么，为什么会存在全栈开发工程师呢？或者说为什么需要全栈开发工程师呢？</p><p>我认为全栈具有下面几个至关重要的作用：</p><ol><li>极大减少沟通成本，全栈开发工程师独立完成一个项目的时候，沟通成本为零，全部自己干</li><li>恐怖的开发效率，全栈开发工程师可以从他的若干技能中找到最高效完成任务的方式</li><li>资源紧张时的万金油，在公司资源紧张的时候，可以充当万金油，哪里缺人顶哪里</li><li>救火灭火的能力，全栈开发工程师在面对线上问题的时候，能够更快地定位问题所在</li></ol><h3 id="全栈与专家的区别">全栈与专家的区别</h3><p>相信大家都听说过“木桶理论”，专家强调的是术业有专攻，衡量一个专家主要看专攻领域的能力；而全栈强调综合能力，主要取决于技术的短板，也就是木桶理论中最短的那块板子。</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/02.png"></img></figure><p>另外，“二八定律”也可以用来解释全栈与专家。在很多情况下，一个有成为全栈或专家能力的人，花 20% 的时间可以将一项技术从零开始学习达到 80 分，而从 80 分提高到 100 分，则需要花 80% 的时间。于是，在某一项领域成为一名专家所花的时间与成为一名在多个领域都达到 80 分的全栈是差不多的。</p><p>因此，想成为一名全栈，也必然需要做一些取舍，需要更加注重技术的广度，而不能陷进技术的深度中无法自拔，毕竟人的精力是有限的。</p><h3 id="全栈开发工程师的进阶之路">全栈开发工程师的进阶之路</h3><p>想要成为一名全栈，我认为需要具备以下几点能力：</p><ol><li>超强的学习能力：全栈需要快速掌握很多技能，所以必须具有超强的学习能力</li><li>发现并解决问题的能力：遇到未知领域的问题，不要退缩，去发现问题，并寻找解决办法</li><li>良好的沟通能力：需要在团队中与各种人员进行沟通，起到团队不同成员的桥梁作用</li><li>全局思维的能力：全栈最大的价值就是全局思维，不局限于某一个面，而是从全局去考虑问题</li><li>技能迁移能力：不局限于某一个技能领域，可以根据需要快速迁移到其他领域</li><li>勇于探索的能力：对新技术的渴望与追求，不断勇于探索新技能</li></ol><p>在我个人的成长上面，我有以下几点经验：</p><ol><li>实践大于理论：与其花大量的时间看文档，停留在理论上，不如直接开始实践，会更快上手一门新技术</li><li>善于利用搜素引擎：翻墙！翻墙！翻墙！Google！Google！Google！</li><li>独自完成一个产品：从零开始打造一个产品，独自完成其中的每个环节</li><li>决心、耐心、虚心、细心：成为一名全栈需要下决心、有耐心，学习要虚心、做事情要细心，才能更上一层楼</li><li>良好的人际关系：与各个领域的技术专家打好关系，你可以从他们身上获得很多干货</li><li>时间积累：任何一个领域都需要花时间去积累，没有速成之法</li></ol><p>下面这个图是我推荐的技术上的进阶路线：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/03.png"></img></figure><h3 id="全栈开发的一个实际案例">全栈开发的一个实际案例</h3><p>下面是我的一个黑客马拉松比赛作品的实际案例，一个人完成该作品的全部生命周期。</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/04.jpg"></img></figure><p>产品设计：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/05.png"></img></figure><p>UI 设计：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/06.png"></img></figure><p>小程序注册：</p><ol><li>注册小程序账号</li><li>公司实名认证，申请营业执照扫描件、授权书盖章等</li><li>配置服务器合法域名</li><li>本地打了一个含有域名验证的 nginx 镜像并发布到 k8s</li><li>配置扫普通链接二维码打开小程序</li></ol><p>Coding：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/07.png"></img></figure><p>硬件调试：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/08.jpg"></img></figure><p>测试部署&发布上线：</p><figure><img src="https://hadb.me/static/posts/2020/20201120.how-to-become-a-fullstack-developer/09.png"></img></figure><p>之后有时间我整理下，详细讲下这个项目的细节，将各个环节的文件都开源出来。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[白香词谱]]></title>
        <id>/posts/2020/baixiang-poems</id>
        <link href="https://hadb.me/posts/2020/baixiang-poems"/>
        <updated>2020-12-17T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20201217.baixiang-poems/cover.jpg" alt="封面" /><blockquote><p>《白香词谱》是清朝嘉庆年间靖安人舒梦兰编选。词谱选录了由唐朝到清朝的词作品共一百篇，凡一百调。这些调式都是较为通用的，小令、中调、长调均有。为便于初学者，每调还详细列注平仄韵读，成为真正的词谱。</p><p>《白香词谱》同时又是一本简明词选。所选的词都是比较著名的或者艺术性较高的，好些是历久传诵不衰的名作。它兼收并蓄，不主一家，既收婉约，也收豪放，是一本不可多得的好选本，也是一本较佳的词学入门读物。</p><p>整理于 2008 年 8 月</p></blockquote><p>【○平声；●仄声；⊙可平可仄；△平韵；▲仄韵】</p><h3 id="一菩萨蛮闺情">一、菩萨蛮·闺情</h3><p>李白</p><p>平林漠漠烟如织，寒山一带伤心碧。暝色入高楼，有人楼上愁。</p><p>⊙○⊙●○○▲，⊙○⊙●○○▲。⊙●⊙○△，⊙○○●△。</p><p>玉阶空伫立，宿鸟归飞急。何处是归程，长亭更短亭。</p><p>⊙○○●▲，⊙●⊙○▲。⊙●●○△，⊙○⊙●△。</p><h3 id="二忆秦娥思秋">二、忆秦娥·思秋</h3><p>李白</p><p>箫声咽，秦娥梦断秦楼月。秦楼月，年年柳色，灞陵伤别。</p><p>○⊙▲，○○⊙●○○▲。○○▲，⊙○⊙●，●○○▲。</p><p>乐游原上清秋节，咸阳古道音尘绝。音尘绝，西风残照，汉家陵阙。</p><p>⊙○⊙●○○▲，⊙○⊙●○○▲。○○▲，⊙○⊙●，●○○▲。</p><p>（“秦楼月”、“音尘绝”为“叠三字”，即前句后三字）</p><h3 id="三调笑令宫词">三、调笑令·宫词</h3><p>王建</p><p>团扇，团扇，美人并来遮面。</p><p>○▲，○▲，●⊙⊙○○▲。</p><p>玉颜憔悴三年，谁复商量管弦。</p><p>⊙○⊙●○△，⊙●○○●△。</p><p>弦管，弦管，春草昭阳路断。</p><p>○▲，○▲。⊙●⊙○⊙▲。</p><p>（“团扇”“弦管”均为“叠句”，“弦管”为“上句末二字颠倒”）</p><h3 id="四长相思别情">四、长相思·别情</h3><p>白居易</p><p>汴水流，泗水流，流到瓜州古渡头，吴山点点愁。</p><p>●⊙△，●⊙△，⊙●○○⊙●△，⊙○⊙●△。</p><p>思悠悠，恨悠悠，恨到归时方始休，月明人倚楼。</p><p>●○△，●○△，⊙●○○⊙●△，●○○●△。</p><h3 id="五更漏子本意">五、更漏子·本意</h3><p>温庭筠</p><p>柳丝长，春雨细，花外漏声迢递。</p><p>●○○，○●▲，⊙●⊙○⊙▲。</p><p>惊塞雁，起城乌，画屏金鹧鸪。</p><p>○●●，●○△，●○○●△。</p><p>香雾薄，透重幕，惆怅谢家池阁。</p><p>○⊙▲，⊙○▲，⊙●⊙○⊙▲。</p><p>红烛背，绣帘垂，梦君君不知。</p><p>○●●，●○△，●○○●△。</p><h3 id="六摊破浣溪沙秋恨">六、摊破浣溪沙·秋恨</h3><p>李璟</p><p>菡萏香销翠叶残，西风愁起绿波间。还与韶光共憔悴，不堪看。</p><p>⊙●○○●●△，⊙○⊙●●○△。⊙●⊙○○●●，●○△。</p><p>细雨梦回鸡塞远，小楼吹彻玉笙寒。多少泪珠何限恨，倚阑干。</p><p>⊙●⊙○○●●，⊙○⊙●●○△。⊙●⊙○○●●，●○△。</p><h3 id="七忆江南怀旧">七、忆江南·怀旧</h3><p>李煜</p><p>多少恨，昨夜梦魂中。还似旧时游上苑，车如流水马如龙，花月正春风。</p><p>○⊙●，⊙●●○△。⊙●⊙○○●●，⊙○⊙●●○△，⊙●●○△。</p><h3 id="八捣练子秋闺">八、捣练子·秋闺</h3><p>李煜</p><p>深院静，小庭空，断续寒砧断续风。</p><p>○●●，●○△，⊙●○○⊙●△。</p><p>无奈夜长人不寐，数声和月到帘栊。</p><p>⊙●⊙○○●●，●○⊙●●○△。</p><h3 id="九相见欢秋闺">九、相见欢·秋闺</h3><p>李煜</p><p>无言独上西楼，月如钩，寂寞梧桐、深院锁清秋。</p><p>⊙○⊙●○△，●○△，⊙●⊙○、○●●○△。</p><p>剪不断，理还乱，是离愁。别是一番、滋味在心头。</p><p>●⊙▲，⊙○▲，●○△。⊙●⊙○、○●●○△。</p><h3 id="十浪淘沙怀旧">十、浪淘沙·怀旧</h3><p>李煜</p><p>帘外雨潺潺，春意阑珊。罗衾不耐五更寒。梦里不知身是客，一晌贪欢。</p><p>⊙●●○△，⊙●○△。⊙○⊙●●○△。⊙●⊙○○●●，⊙●○△。</p><p>独自莫凭栏，无限江山。别时容易见时难。流水落花春去也，天上人间。</p><p>⊙●●○△，⊙●○△。⊙○⊙●●○△，⊙●⊙○○●●，⊙●○△。</p><h3 id="十一虞美人感旧">十一、虞美人·感旧</h3><p>李煜</p><p>春花秋月何时了，往事知多少。小楼昨夜又东风，故国不堪回首月明中。</p><p>⊙○⊙●○○▲，⊙●○○▲。⊙○⊙●●○△，⊙●●○⊙●●○△。</p><p>雕栏玉砌应犹在，只是朱颜改。问君能有几多愁？恰似一江春水向东流。</p><p>⊙○⊙●○○▲，⊙●○○▲。⊙○⊙●●○△？⊙●●○⊙●●○△。</p><h3 id="十二一斛珠香口">十二、一斛珠·香口</h3><p>李煜</p><p>晚妆初过，沉檀轻注些儿个。向人微露丁香颗，一曲清歌，暂引樱桃破。</p><p>⊙○⊙▲，⊙○⊙●○○▲。⊙○⊙●○○▲，⊙●○○，⊙●○○▲。</p><p>罗袖□残殷色可，杯深旋被香醪□。绣床斜凭娇无那，烂嚼红绒，笑向檀郎唾。</p><p>⊙●⊙○○●▲，⊙○⊙●○○▲。⊙○⊙●○○▲，⊙●○○，⊙●○○▲。</p><h3 id="十三谒金门春闺">十三、谒金门·春闺</h3><p>冯延巳</p><p>风乍起，吹皱一池春水。闲引鸳鸯芳径里，手□红杏蕊。</p><p>○⊙▲，⊙●⊙○○▲。⊙●⊙○○●▲，⊙○○●▲。</p><p>斗鸭阑干独倚，碧玉搔头斜坠。终日望君君不至，举头闻鹊喜。</p><p>⊙●⊙○⊙▲，⊙●⊙○○▲。⊙●⊙○○●▲，⊙○○●▲。</p><h3 id="十四踏莎行春暮">十四、踏莎行·春暮</h3><p>寇准</p><p>春色将阑，莺声渐老，红英落尽青梅小。画堂人静雨蒙蒙，屏山半掩余香袅。</p><p>⊙●○○，⊙○●▲，⊙○⊙●○○▲。⊙○⊙●●○○，⊙○⊙●○○▲。</p><p>密约沉沉，离情杳杳，菱花尘满慵将照。倚楼无语欲销魂，长空黯淡连芳草。</p><p>⊙●○○，⊙○●▲，⊙○⊙●○○▲。⊙○⊙●●○○，⊙○⊙●○○▲。</p><h3 id="十五贺圣朝留别">十五、贺圣朝·留别</h3><p>叶清臣</p><p>满斟绿醑留君住，莫匆匆归去。三分春色二分愁，更一分风雨。</p><p>⊙○⊙●○○▲，●○○○▲。⊙○⊙●●○○，●⊙○○▲。</p><p>花开花谢，都来几许。且高歌休诉。不知来岁牡丹时，再相逢何处。</p><p>⊙○⊙●，⊙○⊙▲。●○○○●。⊙○⊙●●○○，●⊙○○▲。</p><h3 id="十六御街行离怀">十六、御街行·离怀</h3><p>范仲淹</p><p>纷纷坠叶飘香砌，夜寂静、寒声碎。真珠帘卷玉楼空，天淡银河垂地。年年今夜，月华如练，长是人千里。</p><p>⊙○⊙●○○▲，●●●、○○▲。⊙○⊙●●○○，⊙●⊙○○▲。⊙○⊙●，⊙○⊙●，⊙●○○▲。</p><p>愁肠已断无由醉，酒未到、先成泪。残灯明灭枕头欹，谙尽孤眠滋味。都来此事，眉间心上，无计相回避。</p><p>⊙○⊙●○○▲，●●●、○○▲。⊙○⊙●●○○，⊙●⊙○○▲。⊙○⊙●，⊙○⊙●，⊙●○○▲。</p><h3 id="十七渔家傲秋思">十七、渔家傲·秋思</h3><p>范仲淹</p><p>塞下秋来风景异，衡阳雁去无留意。四面边声连角起，千障里，长烟落日孤城闭。</p><p>●●⊙○○●▲，⊙○⊙●○○▲。⊙●⊙○○●▲，○⊙▲，⊙○⊙●○○▲。</p><p>浊酒一杯家万里，燕然未勒归无计。羌管悠悠霜满地，人不寐，将军白发征夫泪。</p><p>●●⊙○○●▲，⊙○⊙●○○▲。⊙●⊙○○●▲，○⊙▲，⊙○⊙●○○▲。</p><h3 id="十八苏幕遮怀旧">十八、苏幕遮·怀旧</h3><p>范仲淹</p><p>碧云天，黄叶地，秋色连波、波上寒烟翠。山映斜阳天接水，芳草无情、更在斜阳外。</p><p>●○○，○●▲，⊙●○○、⊙●○○▲。⊙●○○○●▲，⊙●○○、⊙●○○▲。</p><p>黯乡魂，追旅思，夜夜除非、好梦留人睡。明月楼高休独倚，酒入愁肠、化作相思泪。</p><p>●○○，○●▲，⊙●○○、⊙●○○▲。⊙●○○○●▲，⊙●○○、⊙●○○▲。</p><h3 id="十九锦缠道春游">十九、锦缠道·春游</h3><p>宋祁</p><p>燕子呢喃，景色乍长春昼。睹园林、万花如绣，海棠经雨胭脂透。柳展宫眉，翠拂行人首。</p><p>●●○○，⊙●●○○▲。●○○、⊙○○▲，⊙○⊙●○○▲。⊙●○○，⊙●○○▲。</p><p>向郊原踏青，恣歌携手。醉醺醺、尚寻芳酒。问牧童、遥指孤村道，杏花深处，那里人家有。</p><p>●○○●○，⊙○○▲。●○○、⊙○○▲。●⊙○、⊙●○○⊙，●○○●，⊙●○○▲。</p><h3 id="二十离亭燕怀古">二十、离亭燕·怀古</h3><p>张升</p><p>一带江山如画，风物向秋潇洒。水浸碧天何处断，霁色冷光相射。蓼屿荻花洲，掩映竹篱茅舍。</p><p>⊙●⊙○○▲，⊙●⊙○○▲。⊙●⊙○○●●，●●⊙○○▲。⊙●●○○，⊙●⊙○○▲。</p><p>云际客帆高挂，烟外酒旗低亚。多少六朝兴废事，尽入渔樵闲话。怅望倚层楼，寒日无言西下。</p><p>⊙●⊙○○▲，⊙●⊙○○▲。⊙●⊙○○●●，●●⊙○○▲。⊙●●○○，⊙●⊙○○▲。</p><h3 id="二一诉衷情眉意">二一、诉衷情·眉意</h3><p>欧阳修</p><p>清晨帘幕卷轻霜，呵手试梅妆。都缘自有离恨，故画作、远山长。</p><p>⊙○⊙●●○△，⊙●●○△。⊙○⊙●○⊙，⊙●●、●○△。</p><p>思往事，惜流光，易成伤。未歌先敛，欲笑还颦，最断人肠。</p><p>○●●，●○△，●○△。●○○●，●●○○，⊙●○△。</p><h3 id="二二阮郎归踏青">二二、阮郎归·踏青</h3><p>欧阳修</p><p>南园春半踏青时，风和闻马嘶。清梅如豆柳如眉，日长蝴蝶飞。</p><p>⊙○⊙●●○△，⊙○○●△。⊙○⊙●●○△，●○○●△。</p><p>花露重，草烟低，人家帘幕垂。秋千慵困解罗衣，画堂双燕归。</p><p>○●●，●○○，⊙○○●△。⊙○⊙●●○△，●○○●△。</p><h3 id="二三南歌子闺情">二三、南歌子·闺情</h3><p>欧阳修</p><p>凤髻金泥带，龙纹玉掌梳。去来窗下笑相扶，爱道画眉深浅入时无。</p><p>⊙●○○●，○○●●△。⊙○⊙●●○△，⊙●⊙○⊙●●○△。</p><p>弄笔偎人久，描花试手初。等闲妨了绣功夫，笑问鸳鸯两字怎生书。</p><p>⊙●○○●，○○●●△。⊙○⊙●●○△，⊙●⊙○⊙●●○△。</p><h3 id="二四临江仙妓席">二四、临江仙·妓席</h3><p>欧阳修</p><p>柳外轻雷池上雨，雨声滴碎荷声。小楼西角断虹明。阑干私倚处，遥见月华生。</p><p>⊙●⊙○○●●，⊙○⊙●○△。⊙○⊙●●○△。⊙○○●●，⊙●●○△。</p><p>燕子飞来窥画栋，玉钩垂下帘旌。凉波不动簟纹平。水晶双枕畔，犹有堕钗横。</p><p>⊙●⊙○○●●，⊙○⊙●○△。⊙○⊙●●○△。⊙○○●●，⊙●●○△。</p><h3 id="二五西江月佳人">二五、西江月·佳人</h3><p>司马光</p><p>宝髻松松挽就，铅华淡淡妆成。红烟翠雾罩轻盈，飞絮游丝无定。</p><p>⊙●⊙○⊙●，⊙○⊙●○△。⊙○⊙●●○△，⊙●⊙○⊙▲。</p><p>相见争如不见，有情还似无情。笙歌散后酒微醒，深院月明人静。</p><p>⊙●⊙○⊙●，⊙○⊙●○△。⊙○⊙●●○△，⊙●⊙○⊙▲。</p><h3 id="二六桂枝香金陵怀古">二六、桂枝香·金陵怀古</h3><p>王安石</p><p>登临纵目，正故国晚秋，天气初肃。千里澄江似练，翠峰如簇。征帆去棹残阳里，背西风，酒旗斜矗。彩舟云淡，星河鹭起，画图难足。</p><p>○○●▲，●●●⊙○，⊙⊙○▲。⊙●○○⊙●，●○○▲。⊙○⊙●○○●，●○○，⊙○○▲。●○○○●，⊙○⊙●，●○○▲。</p><p>念往昔豪华竞逐，叹门外楼头，悲恨相续。千古凭高对此，漫嗟荣辱。六朝旧事随流水，但寒烟、衰草凝绿。至今商女，时时犹唱，后庭遗曲。</p><p>●⊙●○○●▲，●⊙●○○，⊙⊙○▲。⊙●○○⊙●，●○○▲。⊙○⊙●○○●，●○○、⊙⊙○▲。●○○●，⊙○⊙●，●○○▲。</p><h3 id="二七天仙子送春">二七、天仙子·送春</h3><p>张先</p><p>水调数声持酒听，午醉醒来愁未醒。送春春去几时回，临晚镜，伤流景，往事后期空记省。</p><p>⊙●⊙○○●▲，⊙●⊙○○●▲。⊙○⊙●●○○，○⊙▲，○⊙▲，⊙●⊙○○●▲。</p><p>沙上并禽池上暝，云破月来花弄影。重重帘幕密遮灯，风不定，人初静，明日落红应满径。</p><p>⊙●⊙○○●▲，⊙●⊙○○●▲。⊙○⊙●●○○，○⊙▲，○⊙▲，⊙●⊙○○●▲。</p><h3 id="二八昼夜乐忆别">二八、昼夜乐·忆别</h3><p>柳永</p><p>洞房记得初相遇，便只合、长相聚。何期小会幽欢，变作别离情绪。况值阑珊春色暮，对满目、乱花狂絮。直恐好风光，尽随伊归去。</p><p>⊙○⊙●○○▲，●⊙⊙、⊙○▲。○○●●○○，●●⊙○⊙▲。⊙●○○○●▲，●⊙⊙、●○○▲。⊙●●○○，●○○○▲。</p><p>一场寂寞凭谁诉，算前言、总轻负。早知恁地难拼，悔不当初留住。其奈风流端正外，更别有、系人心处。一日不思量，也攒眉千度。</p><p>⊙○⊙●○○▲，●⊙⊙、⊙○▲。⊙○●●○○，⊙●○○⊙▲。⊙●○○○●●，●⊙⊙、●○○▲。⊙●●○○，●○○○▲。</p><h3 id="二九雨霖铃秋别">二九、雨霖铃·秋别</h3><p>柳永</p><p>寒蝉凄切，对长亭晚，骤雨初歇。都门帐饮无绪，方留恋处，兰舟催发。执手相看，泪眼竟无语凝噎。念去去、千里烟波，暮霭沉沉楚天阔。</p><p>○○○▲，●○○●，●⊙○▲。○○●●○●，○○●●，○○○▲。●●○○，●●●○●○▲。●●●、○●○○，●●○○●○▲。</p><p>多情自古伤离别，更那堪、冷落清秋节。今宵酒醒何处，杨柳岸、晓风残月。此去经年，应是良辰好景虚设。便纵有、千种风流，更与何人说。</p><p>○○●●○○▲，●○○、●●○○▲。○○●●○●，○●●、●○○▲。●●○○，○●○○●●○▲。●●●、○●○○，●●○○▲。</p><h3 id="三十卜算子别意">三十、卜算子·别意</h3><p>王观</p><p>水是眼波横，山是眉峰聚。欲问行人去那边，眉眼盈盈处。</p><p>⊙●●○○，⊙●○○▲。⊙●○○●●○，⊙●○○▲。</p><p>才始送春归，又送君归去。若到江南赶上春，千万和春住。</p><p>⊙●●○○，⊙●○○▲。⊙●○○●●○，⊙●○○▲。</p><h3 id="三一洞仙歌夏夜">三一、洞仙歌·夏夜</h3><p>苏轼</p><p>冰肌玉骨，自清凉无汗。水殿风来暗香满。绣帘开，一点明月窥人，人未寝、欹枕钗横鬓乱。</p><p>⊙○⊙●，●○○⊙▲。⊙●○○●○▲。●○○，●●⊙●○○，○●●、⊙●⊙○⊙▲。</p><p>起来携素手，庭户无声，时见疏星渡河汉。试问夜如何，夜已三更，金波淡，玉绳低转。但屈指、西风几时来，又不道流年，暗中偷换。</p><p>⊙○○●●，⊙●○○，⊙●○○●○▲。⊙●●○○，⊙●○○，○⊙●，⊙○⊙▲。●●●、○○●○○，●⊙●○○，●○○▲。</p><h3 id="三二蝶恋花春景">三二、蝶恋花·春景</h3><p>苏轼</p><p>花褪残红青杏小，燕子飞时，绿水人家绕。枝上柳绵吹又少，天涯何处无芳草。</p><p>⊙●⊙○○●▲，⊙●○○，⊙●○○▲。⊙●⊙○○●▲，⊙○⊙●○○▲。</p><p>墙里秋千墙外道，墙外行人，墙里佳人笑。笑渐不闻声渐悄，多情却被无情恼。</p><p>⊙●⊙○○●▲，⊙●○○，⊙●○○▲。⊙●⊙○○●▲，⊙○⊙●○○▲。</p><h3 id="三三水调歌头中秋">三三、水调歌头·中秋</h3><p>苏轼</p><p>明月几时有，把酒问青天。不知天上宫阙，今夕是何年。我欲乘风归去，又恐琼楼玉宇，高处不胜寒。起舞弄清影，何似在人间。</p><p>⊙●●○●，⊙●●○△。⊙○⊙●○●，⊙●●○△。⊙●○○⊙●，⊙●○○⊙●，⊙●●○△。⊙●●○●，⊙●●○△。</p><p>转朱阁，低绮户，照无眠。不应有恨，何事偏向别时圆。人有悲欢离合，月有阴晴圆缺，此事古难全。但愿人长久，千里共婵娟。</p><p>●⊙●，⊙●●，●○△。⊙○⊙●，○●○●●○△。⊙●○○⊙●，⊙●○○⊙●，⊙●●○△。⊙●⊙○●，⊙●●○△。</p><h3 id="三四清平乐晚春">三四、清平乐·晚春</h3><p>黄庭坚</p><p>春归何处，寂寞无行路。若有人知春去处，唤取归来同住。</p><p>○○⊙▲，⊙●○○▲。⊙●⊙○○⊙▲，⊙●⊙○⊙▲。</p><p>春无踪迹谁知，除非问取黄鹂。百啭无人能解，因风飞过蔷薇。</p><p>⊙○⊙●○△，⊙○⊙●○△。⊙●⊙○⊙●，⊙○⊙●○△。</p><h3 id="三五画堂春本意">三五、画堂春·本意</h3><p>黄庭坚</p><p>东风吹柳日初长，雨余芳草斜阳。杏花零落燕泥香，睡损红妆。</p><p>⊙○⊙●●○△，⊙○⊙●○△。⊙○⊙●●○△，⊙●○△。</p><p>宝篆烟销龙凤，画屏云锁潇湘。夜寒微透薄罗裳，无限思量。</p><p>⊙●⊙○⊙●，⊙○⊙●○△。⊙○⊙●●○△，⊙●○△。</p><h3 id="三六蓦山溪别意">三六、蓦山溪·别意</h3><p>黄庭坚</p><p>鸳鸯翡翠，小小思珍偶。眉黛敛秋波，尽湖南，山明水秀。娉娉袅袅，恰近十三余，春未透，花枝瘦，正是愁时候。</p><p>⊙○⊙●，⊙●○○▲。⊙●●○○，●⊙⊙，○○⊙▲。⊙○⊙●，⊙●●○○，○⊙▲，○⊙▲，⊙●○○▲。</p><p>寻芳载酒，肯落他人后。只恐远归来，绿成阴，青梅如豆。心期得处，每自不由人，长亭柳，君知否，千里犹回首。</p><p>⊙○⊙●，⊙●○○▲。⊙●●○○，●⊙⊙，○○⊙▲。⊙○⊙●，⊙●●○○，○⊙▲，○⊙▲，⊙●○○▲。</p><h3 id="三七忆王孙春闺">三七、忆王孙·春闺</h3><p>秦观</p><p>萋萋芳草忆王孙，柳外楼高空断魂。杜宇声声不忍闻。欲黄昏，雨打梨花深闭门。</p><p>⊙○⊙●●○△，⊙●○○⊙●△。⊙●○○⊙●△。●○△，⊙●○○⊙●△。</p><h3 id="三八如梦令春景">三八、如梦令·春景</h3><p>秦观</p><p>莺嘴啄花红溜，燕尾剪波绿皱。指冷玉笙寒，吹彻小梅春透。依旧，依旧，人与绿杨俱瘦。</p><p>⊙●⊙○⊙▲，⊙●⊙○⊙▲。⊙●●○○，⊙●⊙○⊙▲。○▲，○▲，⊙●⊙○⊙▲。</p><p>（“依旧，依旧”为叠句）</p><h3 id="三九桃源忆故人冬景">三九、桃源忆故人·冬景</h3><p>秦观</p><p>玉楼深锁多情种，清夜悠悠谁共。羞见枕衾鸳凤，闷则和衣拥。</p><p>⊙○⊙●○○▲，⊙●⊙○○▲。⊙●⊙○○▲，⊙●○○▲。</p><p>无端画角严城动，惊破一番新梦。窗外月华霜重，听彻梅花弄。</p><p>⊙○⊙●○○▲，⊙●⊙○○▲。⊙●⊙○○▲，⊙●○○▲。</p><h3 id="四十鹊桥仙七夕">四十、鹊桥仙·七夕</h3><p>秦观</p><p>纤云弄巧，飞星传恨，银汉迢迢暗度。金风玉露一相逢，便胜却、人间无数。</p><p>⊙○⊙●，⊙○⊙●，⊙●⊙○⊙▲。⊙○⊙●●○○，●⊙●、○○⊙▲。</p><p>柔情似水，佳期如梦，忍顾鹊桥归路。两情若是久长时，又岂在、朝朝暮暮。</p><p>⊙○⊙●，⊙○⊙●，⊙●⊙○⊙▲。⊙○⊙●●○○，●⊙●、○○⊙▲。</p><h3 id="四一河传赠妓">四一、河传·赠妓</h3><p>秦观</p><p>恨眉醉眼，甚轻轻觑着、神魂迷乱。常记那回，小曲栏干西畔。鬓云松、罗袜刬。</p><p>⊙○⊙▲，●○○●▲、⊙○⊙●。⊙●●●○，⊙●⊙○○▲。●⊙○、○●▲。</p><p>丁香笑吐娇无限，语软声低、道我何曾惯。云雨未谐，早被东风吹散。瘦杀人、天不管。</p><p>⊙○⊙●○○▲，⊙●○○、⊙●○○▲。⊙●●○，⊙●⊙○○▲。●⊙○、○●▲。</p><h3 id="四二满庭芳春游">四二、满庭芳·春游</h3><p>秦观</p><p>晓色云开，春随人意，骤雨才过还晴。古台芳榭，飞燕蹴红英。舞困榆钱自落，秋千外，绿水桥平。东风里、朱门映柳，低按小秦筝。</p><p>⊙●○○，⊙○⊙●，●⊙⊙●⊙△。⊙○○●，⊙●●○△。⊙●⊙○⊙●，○⊙●，⊙●○△。○○●、○○○●，⊙●●○△。</p><p>多情。行乐处，珠钿翠盖，玉辔红缨。渐酒空金□，花困蓬瀛。豆蔻梢头旧恨，十年梦，屈指堪惊。凭栏久，疏烟淡日，寂寞下芜城。</p><p>○△。⊙●●，⊙○⊙●，⊙●○△。●⊙○⊙●，⊙●○△。⊙●⊙○⊙●，⊙⊙●，⊙●○△。⊙○●，⊙○⊙●，⊙●●○△。</p><h3 id="四三清玉案春暮">四三、清玉案·春暮</h3><p>贺铸</p><p>凌波不过横塘路，但目送芳尘去。锦瑟年华谁与度，月楼花院，绮窗朱户，惟有春知处。</p><p>⊙○⊙●○○▲，●⊙●○○▲。⊙●⊙○○●▲，●○○●，●○○▲，⊙●○○▲。</p><p>碧云冉冉蘅皋暮，彩笔空题断肠句。试问闲愁知几许，一川烟草，满城风絮，梅子黄时雨。</p><p>⊙○⊙●○○▲，⊙●○○●○▲。⊙●⊙○○●▲，●○○●，●○○▲，⊙●○○▲。</p><h3 id="四四薄倖春情">四四、薄倖·春情</h3><p>贺铸</p><p>淡妆多态，更滴滴、频回盼睐。便认得、琴心先许，欲绾合欢双带。记画堂、风月逢迎，轻颦浅笑娇无奈。向睡鸭炉边，翔鸳屏里，羞把香罗暗解。</p><p>⊙○○▲，●⊙●、○○●▲。●⊙●、○○○●，⊙●⊙○○▲。●⊙○、○●○○，○○●●○○▲。●●●○○，⊙○⊙●，⊙●○○⊙▲。</p><p>自过了、烧灯后，都不见、踏青挑菜。几回凭双燕，叮咛深意，往来却恨重帘碍。约何时再。正春浓酒困，人闲昼永无聊赖。恹恹睡起，犹有花梢日在。</p><p>⊙●●、○○●，○●●、●○⊙▲。⊙○⊙⊙●，○○⊙●，⊙○⊙●○○▲。●○○▲。●○○⊙●，○○●●○○▲。○○●●，⊙●○○⊙▲。</p><h3 id="四五惜分飞本意">四五、惜分飞·本意</h3><p>毛滂</p><p>泪湿栏干花着露，愁到眉峰碧聚。此恨平分取，更无言语空相觑。</p><p>⊙●⊙○○●▲，⊙●○○●▲。⊙●○○▲，⊙○⊙●○○▲。</p><p>断雨残云无意絮，寂寞朝朝暮暮。今夜山深处，断魂分付潮回去。</p><p>⊙●⊙○○⊙▲，⊙●○○●▲。⊙●○○▲，⊙○⊙●○○▲。</p><h3 id="四六河满子秋怨">四六、河满子·秋怨</h3><p>孙洙</p><p>怅望浮生急景，凄凉宝瑟余音。楚客多情偏怨别，碧山远水登临。目送连天衰草，夜阑几处疏砧。</p><p>⊙●⊙○⊙●，⊙○⊙●○△。⊙●⊙○○●●，⊙○⊙●○△。⊙●⊙○⊙●，⊙○⊙●○△。</p><p>黄叶无风自落，秋云不雨长阴。天若有情天亦老，摇摇幽恨难禁。惆怅旧欢如梦，觉来无处追寻。</p><p>⊙●⊙○⊙●，⊙○⊙●○△。⊙●⊙○○●●，⊙○⊙●○△。⊙●⊙○⊙●，⊙○⊙●○△。</p><h3 id="四七烛影摇红惜春">四七、烛影摇红·惜春</h3><p>王诜</p><p>香脸轻匀，黛眉巧画宫妆浅。风流天付与精神，全在娇波转。早是萦心可惯，更那堪、频频顾盼。几回得见，见了还休，争如不见。</p><p>⊙●○○，⊙○⊙●○○▲。⊙○⊙●●○○，⊙●○○▲。⊙●⊙○⊙▲，●○○、○○●▲。⊙○⊙●，⊙●○○，○○○▲。</p><p>烛影摇红，夜阑饮散春宵短。当时谁解唱阳关，离恨天涯远。无奈云收雨散，凭栏干、东风泪眼。海棠开后，燕子来时，黄昏庭院。</p><p>⊙●○○，⊙○⊙●○○▲。⊙○⊙●●○○，⊙●○○▲。⊙●⊙○⊙▲，●○○、○○●▲。⊙○⊙●，⊙●○○，○○○▲。</p><h3 id="四八减字木兰花春情">四八、减字木兰花·春情</h3><p>王安国</p><p>画桥流水，雨湿落红飞不起。月破黄昏，帘里余香马上闻。</p><p>⊙○⊙▲，⊙●⊙○○●▲。⊙●○△，⊙●○○⊙●△。</p><p>徘徊不语，今夜梦魂何处去。不似垂杨，犹解飞花入洞房。</p><p>⊙○⊙▲，⊙●⊙○○●▲。⊙●○△，⊙●○○⊙●△。</p><h3 id="四九千秋岁夏景">四九、千秋岁·夏景</h3><p>谢逸</p><p>楝花飘砌。蔌蔌清香细。梅雨过，苹风起。情随湘水远，梦绕吴峰翠。琴书倦，鹧鸪唤起南窗睡。</p><p>⊙○⊙▲。⊙●○○▲。⊙●●，○○▲。⊙○○●●，⊙●○○▲。○⊙●，⊙○⊙●○○▲。</p><p>密意无人寄。幽恨凭谁洗。修竹畔，疏帘里。歌余尘拂扇，舞罢风掀袂。人散后，一钩新月天如水。</p><p>⊙●○○▲。⊙●○○▲，⊙●●，○○▲。⊙○○●●，⊙●○○▲。○⊙●，⊙○⊙●○○▲。</p><h3 id="五十琐窗寒寒食">五十、琐窗寒·寒食</h3><p>周邦彦</p><p>暗柳啼鸦，单衣伫立，小帘朱户。桐花半亩，静锁一庭愁雨。洒空阶、更阑未休，故人翦烛西窗语。似楚江暝宿，风灯零乱，少年羁旅。</p><p>●●○○，○○●●，●○○▲。○○●●，●●⊙○○▲。●○○、○⊙●⊙，⊙○⊙●○○▲。●⊙○⊙●，○○⊙●，●○○▲。</p><p>迟暮。嬉游处。正店舍无烟，禁城百五。旗亭唤酒，付与高阳俦侣。想东园、桃李自春，小唇秀靥今在否。到归时、定有残英，待客携樽俎。</p><p>○▲。○○▲。●●●○○，●○⊙▲。○○●●，●●⊙○○▲。●○○、⊙●⊙○，⊙○●●○●▲。●○○、⊙●○○，●●○○▲。</p><h3 id="五一解语花元宵">五一、解语花·元宵</h3><p>周邦彦</p><p>风销焰蜡，露□烘炉，花市光相射。桂华流瓦。纤云散，耿耿素娥欲下。衣裳淡雅。看楚女、纤腰一把。箫鼓喧，人影参差，满路飘香麝。</p><p>○○●●，●●○○，○●○○▲。●○○▲。○○●，⊙●⊙○⊙▲。○○⊙▲。⊙●●、⊙○⊙▲。⊙●○，⊙●○○，⊙●○○▲。</p><p>因念帝城放夜，望千门如昼，嬉笑游冶。钿车罗帕。相逢处、自有暗尘随马。年光是也。惟只见、旧情衰谢。清漏移，飞盖归来，任舞休歌罢。</p><p>⊙●⊙○●▲，●○○○●，○●○▲。⊙○○▲。○○●、⊙●●○○▲。○○●▲。⊙●●、⊙○⊙▲。⊙●○，⊙●○○，●●○○▲。</p><h3 id="五二过秦楼秋夜">五二、过秦楼·秋夜</h3><p>周邦彦</p><p>水浴清蟾，叶喧凉吹，巷陌马声初断。闲依露井，笑扑流萤，惹破画罗轻扇。人静夜久凭栏，愁不归眠，立残更箭。叹年华一瞬，人今千里，梦沉书远。</p><p>●●○○，⊙○○●，●●●○○▲。○○●●，⊙●○○，●●●○○▲。○●●●○○，○●○○，⊙○○▲。●○○●●，○○○●，●○○●。</p><p>空见说、鬓怯琼梳，容销金镜，渐懒趁时匀染。梅风地溽，虹雨苔滋，一架舞红都变。谁信无聊，为伊才减江淹，情伤荀倩。但明河影下，还看疏星几点。</p><p>○●●、●●○○，⊙○○●，●●●○○▲。○○●●，⊙●○○，●●●○○▲。○●○○，●○○●○○，⊙○○▲。●○○●●，⊙●○○●▲。</p><h3 id="五三昭君怨春怨">五三、昭君怨·春怨</h3><p>万俟雅言</p><p>春到南楼雪尽，惊动灯期花信。小雨一番寒，倚栏干。</p><p>⊙●⊙○⊙▲，⊙●⊙○⊙▲。⊙●●○△，⊙○△。</p><p>莫把栏干频倚，一望几重烟水。何处是京华，暮云遮。</p><p>⊙●⊙○⊙▲，⊙●⊙○⊙▲。⊙●●○△，⊙○△。</p><h3 id="五四感皇恩入京">五四、感皇恩·入京</h3><p>赵企</p><p>骑马踏红尘，长安重到。人面依然似花好。旧欢才展，又被新愁分了。未成云雨梦，巫山晓。</p><p>⊙●●○○，⊙○○▲。⊙●○○●○▲。⊙○○●，⊙●⊙○○▲。⊙○○●●，○○▲。</p><p>千里断肠，关山古道。回首高城似天杳。满怀离恨，付与落花啼鸟。故人何处也，青春老。</p><p>⊙●●○，⊙○⊙▲。⊙●○○●○▲。⊙○○●，⊙●⊙○○▲。⊙○○●●，○○▲。</p><h3 id="五五好事近初夏">五五、好事近·初夏</h3><p>蒋子云</p><p>叶暗乳鸦啼，风定老红犹落。蝴蝶不随春去，入熏风池阁。</p><p>⊙●●○○，⊙●⊙○○▲。⊙●⊙○○●，●○○○▲。</p><p>休歌金缕劝金卮，酒病煞如昨。帘卷日长人静，任杨花飘泊。</p><p>⊙○⊙●●○○，⊙⊙●○▲。⊙●⊙○○●，●○○○▲。</p><h3 id="五六贺新郎春闺">五六、贺新郎·春闺</h3><p>李玉</p><p>篆缕销金鼎，醉沉沉、庭阴转午，画堂人静。芳草王孙知何处，惟有杨花糁径。渐玉枕、腾腾春醒。帘外残红春已透，镇无聊，□酒恹恹病。云鬓乱，未□整。</p><p>⊙●○○▲，●○○、⊙○●●，⊙○○▲。○●○○○⊙●⊙●○○●▲，●⊙●、⊙○○▲。⊙●⊙○○●●，●○○，⊙●○○▲。○●●，●○▲。</p><p>江南旧事休重省。遍天涯，寻消问息，断鸿难倩。月满西楼凭栏久，依旧归期未定。又只恐、瓶沉金井。嘶骑不来银烛暗，枉教人、立尽梧桐影。谁伴我，对鸾镜。</p><p>○○●●○○▲。●○○，○○●●，●○○▲。⊙●⊙○○⊙●，⊙●○○●▲。●⊙●、○○○▲。⊙●⊙○○⊙●，●○○、⊙●○○▲。○●●，●○▲。</p><h3 id="五七潇湘夜雨灯花">五七、潇湘夜雨·灯花</h3><p>赵长卿</p><p>斜点银缸，高擎莲炬，夜寒不耐微风。重重帘幕掩堂中。香渐远，长烟袅□，光不定，寒影摇红。偏奇处、当庭月暗，吐焰如虹。</p><p>⊙●○○，⊙○⊙●，⊙○⊙●○△。⊙○⊙●●○△。○●●，○○●●，○●●，○●○△。○○●、○○●●，●●○△。</p><p>红裳呈艳，丽蛾一见，无奈狂踪。试烦他纤手，卷上纱笼。开正好，银花照夜，堆不尽，金粟凝空。叮咛语，频将好事，来报主人公。</p><p>⊙○⊙●，⊙○⊙●，⊙●○△。●⊙○○●，⊙●○△。○●●，○○●●，○●●，○●○△。○○●，○○●●，⊙●●○△。</p><h3 id="五八祝英台近春晚">五八、祝英台近·春晚</h3><p>辛弃疾</p><p>宝钗分，桃叶渡，烟柳暗南浦。怕上层楼，十日九风雨。断肠点点飞红，都无人管，倩谁唤、流莺声住。</p><p>●○○，○●▲，⊙●●○▲。⊙●○○，⊙●●○▲。⊙○⊙●○○，⊙○○●，●⊙●、⊙○○▲。</p><p>鬓边觑，试把花卜归期，才簪又重数。罗帐灯昏，哽咽梦中语。是他春带愁来，春归何处，却不解、带将愁去。</p><p>●○▲，⊙⊙⊙●○○，⊙○●○▲。⊙●○○，⊙●●○▲。⊙○⊙●○○，⊙○○▲，●⊙●、⊙○○▲。</p><h3 id="五九南浦春暮">五九、南浦·春暮</h3><p>程垓</p><p>金鸭懒薰香，向晚来，春酲一枕无绪。浓绿涨瑶窗，东风外，吹尽乱红飞絮。无言伫立，断肠惟有流莺语。碧云欲暮，空惆怅韶华，一时虚度。</p><p>⊙●●○○，●●○，○○⊙●○▲。⊙●●○○，○○●，⊙●●○○▲。○○●●，⊙○⊙●○○▲。●○●▲，○⊙●○○，⊙○○▲。</p><p>追思旧日心情，记题叶西楼，吹花南浦。老去觉欢疏，伤春恨、都付断云残雨。黄昏院落，问谁犹在凭栏处。可堪杜宇，空只解声声，催他春去。</p><p>○○●●○○，●○●○○，⊙○○▲。⊙●●○○，○○●、⊙●●○○▲。○○●●，●○○●○○▲。⊙○●▲，⊙●●○○，⊙○○▲。</p><h3 id="六十齐天乐蟋蟀">六十、齐天乐·蟋蟀</h3><p>姜夔</p><p>庾郎先自吟愁赋，凄凄更闻私语。露湿铜铺，苔侵石井，都是曾听伊处。哀音似诉，正思妇无眠，起寻机杼。曲曲屏山，夜凉独自甚情绪。</p><p>●○⊙●○○▲，○○●○○▲。●●○○，○○●●，⊙●⊙○○▲。○○●▲，●⊙●○○，●○○▲。●●○○，●○⊙●●○▲。</p><p>西窗又吹暗雨。为谁频断续，相和砧杵。候馆吟秋，离宫吊月，别有伤心无数。豳诗漫与，笑篱落呼灯，世间儿女。写入琴丝，一声声更苦。</p><p>○○●○●▲。●○○●●，○●○▲。●●○○，○○●●，⊙●○○○▲。○○●▲，●⊙●○○，●○○▲。●●○○，●○○●▲。</p><h3 id="六一沁园春有感">六一、沁园春·有感</h3><p>陆游</p><p>孤鹤归来，再过辽天，换尽旧人。念累累枯冢，茫茫梦境，王侯蝼蚁，毕竟成尘。载酒园林，寻花巷陌，当日何曾轻负春。流年改，叹围腰带剩，点鬓霜新。</p><p>⊙●○○，●●○○，⊙●⊙△。●⊙○⊙●，⊙○⊙●，⊙○⊙●，⊙●○△。⊙●○○，⊙○⊙●，⊙●○○⊙●△。○○●，⊙○○●●，●●○△。</p><p>交亲散落如云。又岂料而今余此身。幸眼明身健，茶甘饭软，非惟我老，更有人贫。躲尽危机，消残壮志，短艇湖中闲采□。吾何恨，有渔翁共醉，溪友为邻。</p><p>○○●●○△。●⊙●○○⊙●△。●⊙○⊙●，⊙○⊙●，⊙○⊙●，⊙●○△。⊙●○○，⊙○⊙●，⊙●○○⊙●△。○○●，●○○●●，⊙●○△。</p><h3 id="六二醉太平闺情">六二、醉太平·闺情</h3><p>刘过</p><p>情高意真，眉长鬓青。小楼明月调筝，写春风数声。</p><p>○○●△，○○●△。⊙○⊙●○△，●○○●△。</p><p>思君忆君，魂牵梦萦。翠销香暖云屏，更那堪酒醒。</p><p>○○●△，○○●△。⊙○⊙●○△，●○○●△。</p><h3 id="六三喜迁莺闰元宵">六三、喜迁莺·闰元宵</h3><p>吴礼之</p><p>银蟾光彩，喜稔岁闰正，元宵还再。乐事难并，佳时罕遇，依旧试灯何碍。花市又移星汉，莲炬重芳人海。尽勾引，遍嬉游宝马，香车喧隘。</p><p>⊙○○▲，●⊙●⊙○，○○○▲。⊙●○○，⊙○⊙●，⊙●●○○▲。⊙●●○○●，○●○○○▲。●⊙●，●○○⊙●，⊙○○▲。</p><p>晴快，天意教、人月更圆，偿足风流债。媚柳烟浓，夭桃红小，景物迥然堪爱。巷陌笑声不断，襟袖余香仍在。待归也，便相期明日，踏青挑菜。</p><p>○▲，○●○、○●●○，⊙●○○▲。⊙●○○，⊙○⊙⊙，⊙●●○○▲。⊙●●○○●，⊙●⊙○○▲。●○●，●○○⊙●，⊙○○▲。</p><h3 id="六四双双燕本意">六四、双双燕·本意</h3><p>史达祖</p><p>过春社了，度帘幕中间，去年尘冷。差池欲住，试入旧巢相并。还相雕梁藻井，又软语、商量不定。飘然快拂花梢，翠尾分开红影。</p><p>⊙○●●，●⊙●○○，●○○▲。⊙○⊙●，⊙●●○○▲。⊙●○○●▲，●⊙●、○○⊙▲。○○●●○○，●●○○○▲。</p><p>芳径，芹泥雨润。爱贴地争飞，竞夸轻俊。红楼归晚，看足柳昏花暝。应是栖香正稳，便忘了、天涯芳信。愁损翠黛双蛾，日日画栏独凭。</p><p>○▲，○○●▲。●⊙●○○，●○○▲。⊙○○●，●●●○○▲。⊙●○○●▲，●⊙●、○○⊙▲。○⊙●●○○，●●⊙○⊙▲。</p><h3 id="六五换巢鸾凤春情">六五、换巢鸾凤·春情</h3><p>史达祖</p><p>人若梅娇，正愁横断坞，梦绕溪桥。倚风融汉粉，坐月怨秦箫。相思因什到纤腰。定知我今、无魂可销。佳期晚，谩几度、泪痕相照。</p><p>⊙●○△，●○○●●，⊙●○△。⊙○○●●，●●●○△。⊙○⊙●●○△。●○●○、○○●△。○○●，●⊙●、●○○▲。</p><p>人悄，天渺渺。花外语香，时透郎怀抱。暗握荑苗，乍尝樱颗，犹恨侵阶芳草。天念王昌忒多情，换巢鸾凤教偕老。温柔乡，醉芙蓉、一帐春晓。</p><p>○▲，○●▲。○●⊙○，⊙●○○▲。⊙●○○，⊙○○●，⊙●⊙○○▲。○●○○●○○，⊙○⊙●○○▲。○○○，●○○、⊙●○▲。</p><h3 id="六六瑞鹤仙风怀">六六、瑞鹤仙·风怀</h3><p>史达祖</p><p>杏烟娇湿鬓，过杜若汀洲，楚衣香润。回头翠楼近，指鸳鸯沙上，暗藏春恨。归鞭隐隐，便不念、芳痕未稳。自箫声、吹落云东，再数故园花信。</p><p>⊙○○●▲，●⊙●○○，⊙○○▲。○○●○▲，●○○○●，⊙○○▲。○○●▲，●⊙●、○○●▲。●○○、⊙●○○，⊙●●○○▲。</p><p>谁问，听歌窗罅，倚月钩栏，旧家轻俊。芳心一寸，相思后，总灰尽。奈春风多事，吹花摇柳，也把幽情唤醒。对南溪、桃萼翻红，又成瘦损。</p><p>○▲，⊙○○●，●●○○，●○○▲。⊙○⊙▲，○○●，●○▲。●○○○●，⊙○○●，⊙●○○●▲。●○○、⊙●○○，●○●▲。</p><h3 id="六七风入松春园">六七、风入松·春园</h3><p>吴文英</p><p>听风听雨过清明，愁草瘗花铭。楼前绿暗分携路，一丝柳、一寸柔情。料峭春寒中酒，迷离晓梦啼莺。</p><p>⊙○⊙●●○△，⊙●●○△。⊙○●●○○●，●○⊙、⊙●○△。⊙●⊙○⊙●，⊙○⊙●○△。</p><p>西园日日扫林亭，依旧赏新晴。黄蜂频扑秋千索，有当时、纤手香凝。惆怅双鸳不到，幽阶一夜苔生。</p><p>⊙○⊙●●○△，⊙●●○△。⊙○⊙●○○●，●○⊙、⊙●○△。⊙●⊙○⊙●，⊙○⊙●○△。</p><h3 id="六八一翦梅春思">六八、一翦梅·春思</h3><p>蒋捷</p><p>一片春愁带酒浇。江上舟摇，楼上帘招，秋娘容与泰娘娇。风又飘飘，雨又潇潇。</p><p>⊙●○○●●△。⊙●○△，⊙●○△，⊙○⊙●●○△。⊙●○△，⊙●○△。</p><p>何日云帆卸浦桥。银字筝调，心字香烧，流光容易把人抛。红了樱桃，绿了芭蕉。</p><p>⊙●○○●●△。⊙●○△，⊙●○△，⊙○⊙●●○△。⊙●○△，⊙●○△。</p><h3 id="六九永遇乐绿阴">六九、永遇乐·绿阴</h3><p>蒋捷</p><p>清逼池亭，润侵山阁，云气凝聚。未有蝉前，已无蝶后，花事随流水。西园支径，今朝重到，半碍醉筇吟袂。除非是、莺身瘦小，暗中引雏穿去。</p><p>⊙●○○，⊙○⊙●，⊙●⊙▲。⊙●○○，⊙○⊙●，⊙●○○▲。⊙○○●，⊙○⊙●，⊙●●○○▲。⊙○●、○○●●，⊙○●○○▲。</p><p>梅檐滴溜，风来吹断，放得斜阳一缕。玉子敲枰，香绡落翦，声度深几许。层层离恨，凄迷如此，点破漫烦轻絮。应难认、争春旧馆，倚红杏处。</p><p>⊙○⊙●，⊙○○●，●●○○⊙▲。⊙●○○，⊙○⊙●，⊙●○⊙▲。⊙○○●，⊙○⊙●，⊙●●○○▲。⊙○●、○○●●，⊙○●▲。</p><h3 id="七十瑶台聚八仙寄兴">七十、瑶台聚八仙·寄兴</h3><p>张炎</p><p>秋月娟娟，人正远、鱼雁待拂吟笺。也知游事，多在第二桥边。花底鸳鸯深处睡，柳阴淡隔里湖船。路绵绵。梦吹旧曲，如此山川。</p><p>⊙●○△，○⊙●、○●●●○△。●○○●，○●●●○△。⊙●○○○●●，⊙○●●●○△。●○△。●○●●，○●○△。</p><p>平生几两谢屐，便放歌自得，直上风烟。峭壁谁家，长啸竟落松前。十年孤剑万里，又何似、畦分抱瓮泉。中山酒、且醉餐石髓，白眼青天。</p><p>○○⊙○●●，●⊙○●●，●●○△。●●○○，○●●●○△。⊙○⊙●●●，●⊙●、○○⊙●△。○○●、●●○○●，⊙●○△。</p><h3 id="七一水龙吟白莲">七一、水龙吟·白莲</h3><p>张炎</p><p>仙人掌上芙蓉，涓涓犹滴金盘露。轻妆照水，纤裳玉立，飘飘似舞。几度消凝，满湖烟月，一汀鸥鹭。记小舟夜悄，波明香远，浑不见、花开处。</p><p>○○⊙●○○，⊙○⊙●○○▲。⊙○●●，⊙○●●，⊙○●▲。⊙●○○，⊙○○●，⊙○○▲。●⊙○⊙●，⊙○⊙●，⊙●●、○○▲。</p><p>应是浣纱人妒，褪红衣、被谁轻误。闲情淡雅，冶姿清润，凭娇待语。隔浦相逢，偶然倾盖，似传心素。怕湘皋佩解，绿云十里，卷西风去。</p><p>⊙●⊙○○▲，●○○、⊙○○▲。⊙○●●，⊙○○●，⊙○●▲。⊙●○○，⊙○○●，⊙○○▲。●○○●●，⊙○⊙●，●○○▲。</p><h3 id="七二绮罗香红叶">七二、绮罗香·红叶</h3><p>张炎</p><p>万里飞霜，千山落木，寒艳不招春妒。枫冷吴江，独客又吟愁句。正船舣、流水孤村，似花绕、斜阳芳树。甚荒沟、一片凄凉，载情不去载愁去。</p><p>●●○○，○○●●，⊙●●○○▲。⊙●○○，⊙●●○○▲。●⊙⊙、⊙●○○，●⊙●、⊙○○▲。●○○、●●○○，⊙○⊙●●○▲。</p><p>长安谁问倦旅，羞见衰颜借酒，飘零如许。漫倚新妆，不入洛阳花谱。为回风、起舞樽前，尽化作、断霞千缕。记阴阴、绿遍江南，夜窗听暗雨。</p><p>○○○●●▲，○●○○⊙●，⊙○○▲。⊙●○○，⊙●●○○▲。●⊙⊙、⊙●○○，●⊙●、⊙○○▲。●○○、●●○○，●○○●▲。</p><h3 id="七三疏影梅影">七三、疏影·梅影</h3><p>张炎</p><p>黄昏片月，似碎阴满地，还更清绝。枝北枝南，疑有疑无，几度背灯难折。依稀倩女离魂处，缓步出、前村时节。看夜深、竹外横斜，应妒过云明灭。</p><p>○○●▲，●○○●●，⊙●○▲。⊙●○○，⊙●○○，⊙⊙●⊙○▲。○○●●○○●，●●●、⊙○○▲。●●⊙、●●○○，⊙●●○○▲。</p><p>窥镜蛾眉淡扫，为容不在貌，独抱孤洁。莫是花光，描取春痕，不怕丽谯吹彻。还惊海上燃犀去，照水底、珊瑚疑活。做弄得、酒醒天寒，空对一庭香雪。</p><p>⊙●○○●●，●○⊙●●，⊙●○▲。⊙●○○，⊙●○○，⊙●●○○▲。○○●●○○●，⊙●●、⊙○○▲。●●⊙、⊙●○○，⊙●●○○▲。</p><h3 id="七四采桑子春暮">七四、采桑子·春暮</h3><p>朱藻</p><p>幛泥油壁人归后，满院花阴，楼影沉沉，中有伤春一片心。</p><p>⊙○⊙●○○●，⊙●○△，⊙●○△，⊙●○○⊙●△。</p><p>闲穿绿树寻梅子，斜日笼明，团扇风轻，一径黄花不避人。</p><p>⊙○⊙●○○●，⊙●○△，⊙●○△，⊙●○○⊙●△。</p><h3 id="七五荆州亭题柱">七五、荆州亭·题柱</h3><p>吴城小龙女</p><p>帘卷曲栏独倚，江展暮云无际。泪眼不曾晴，家在吴头楚尾。</p><p>⊙●⊙○⊙▲，⊙●⊙○⊙▲。⊙●●○○，⊙●⊙○⊙▲。</p><p>数点雪花乱委，扑漉沙鸥惊起。诗句欲成时，没入苍烟丛里。</p><p>⊙●⊙○⊙▲，⊙●⊙○⊙▲。⊙●●○○，⊙●⊙○⊙▲。</p><h3 id="七六醉花阴重九">七六、醉花阴·重九</h3><p>李清照</p><p>薄雾浓云愁永昼，瑞脑销金兽。佳节又重阳，宝枕纱厨，昨夜凉初透。</p><p>⊙●⊙○○●▲，⊙●○○▲。⊙●●○○，⊙●○○，⊙●○○▲。</p><p>东篱把酒黄昏后，有暗香盈袖。莫道不销魂，帘卷西风，人比黄花瘦。</p><p>⊙○⊙●○○▲，⊙●○○▲。⊙●●○○，⊙●○○，⊙●○○▲。</p><h3 id="七七凤凰台上忆吹箫别情">七七、凤凰台上忆吹箫·别情</h3><p>李清照</p><p>香冷金猊，被翻红浪，起来慵自梳头。任宝奁尘满，日上帘钩。生怕离怀别苦，多少事、欲说还休。新来瘦，非干病酒，不是悲秋。</p><p>⊙●○○，⊙○⊙●，⊙○⊙●○△。●●○○●，⊙●○△。⊙●⊙○⊙●，○⊙●、⊙●○△。○○●，○○●●，⊙●○△。</p><p>休休。这回去也，千万遍阳关，也则难留。念武陵人远，烟锁秦楼。惟有楼前流水，应念我、终日凝眸。凝眸处，从今又添，一段新愁。</p><p>○△。⊙○●●，⊙●●○○，⊙●○△。●⊙○○●，⊙●○△。⊙●⊙○⊙●，○⊙●、⊙●○△。○○●，○○●⊙，⊙●○△。</p><h3 id="七八声声慢秋情">七八、声声慢·秋情</h3><p>李清照</p><p>寻寻觅觅，冷冷清清，凄凄惨惨戚戚。乍暖乍寒时候，最难将息。三杯两盏淡酒，怎敌他、晚来风急。雁过也，正伤心、却是旧时相识。</p><p>○○●●，●●○○，○○●●○▲。⊙●⊙○⊙●，⊙○○▲。○○●○⊙●，●●○、●○○▲。●●●，●○○、⊙●⊙○⊙▲。</p><p>满地黄花堆积。憔悴损，而今有谁堪摘。守着窗儿，独自怎生得黑。梧桐更兼细雨，到黄昏、点点滴滴。这次第，怎一个、愁字了得。</p><p>⊙●⊙○⊙▲。○●●，⊙⊙●○⊙▲。●●○○，⊙●⊙○⊙▲。○○●○⊙●，●○○、●○○▲。●●●，●●●、○●●▲。</p><h3 id="七九南乡子春闺">七九、南乡子·春闺</h3><p>孙道绚</p><p>晓日压重檐，斗帐春寒起未□。天气困人梳洗懒，眉尖，淡画春山不喜添。</p><p>⊙●●○△，⊙●○○●●△。⊙●⊙○○●●，○△，⊙●○○●●△。</p><p>闲把绣丝□，认得金针又倒拈。陌上游人归也未，恹恹，满院杨花不卷帘。</p><p>⊙●●○△，⊙●○○●●△。⊙●⊙○○●●，○△，⊙●○○●●△。</p><h3 id="八十生查子元夕">八十、生查子·元夕</h3><p>朱淑真</p><p>去年元夜时，花市灯如昼。月上柳梢头，人约黄昏后。</p><p>⊙○○●○，⊙●○○▲。⊙●●○○，⊙●○○▲。</p><p>今年元夜时，月与灯依旧。不见去年人，泪湿春衫袖。</p><p>⊙○○●○，⊙●○○▲。⊙●●○○，⊙●○○▲。</p><h3 id="八一鹧鸪天别情">八一、鹧鸪天·别情</h3><p>聂胜琼</p><p>玉惨花愁出凤城，莲花楼下柳青青。尊前一唱阳关曲，别个人人第五程。</p><p>⊙●○○●●△，⊙○⊙●●○△。⊙○⊙●○○●，⊙●○○●●△。</p><p>寻好梦，梦难成，有谁知我此时情。枕前泪共阶前雨，隔个窗儿滴到明。</p><p>○●●，●○△，⊙○⊙●●○△。⊙○⊙●○○●，⊙●○○●●△。</p><h3 id="八二人月圆有感">八二、人月圆·有感</h3><p>吴激</p><p>南朝千古伤心事，还唱后庭花。旧时王谢，堂前燕子，飞向谁家。</p><p>⊙○⊙●○○●，⊙●●○△。⊙○⊙●，○○●●，⊙●○△。</p><p>恍然一梦，天姿胜雪，宫鬓堆鸦。江州司马，青衫泪湿，同是天涯。</p><p>⊙○⊙●，○○⊙●，⊙●○△。⊙○⊙●，○○●●，⊙●○△。</p><h3 id="八三望海潮凯旋舟次">八三、望海潮·凯旋舟次</h3><p>折元礼</p><p>地雄河岳，疆分韩晋，潼关高压秦头。山倚断霞，江吞绝壁，野烟萦带沧洲。虎旆拥貔貅，看阵云截岸，霜气横秋。千雉严城，五更残角月如钩。</p><p>⊙○○●，○○⊙●，⊙○⊙●○△。⊙●●○，○○●●。⊙○⊙●○△。⊙●●○△，●⊙○⊙●，⊙●○△。⊙●○○，●○○●●○△。</p><p>西风晓入貂裘。恨儒冠误我，却羡兜牟。六郡少年，三关老将，贺兰烽火新收。天外岳莲楼，挂几行雁字，指引归舟。正好黄金换酒，羯鼓醉凉州。</p><p>○○●●○△。●○○⊙●，⊙●○△。⊙●●○，○○●●，⊙○⊙●○△。○●●○○，●⊙○⊙●，⊙●○△。⊙●○○●●，●●●○△。</p><h3 id="八四玉漏迟咏怀">八四、玉漏迟·咏怀</h3><p>元好问</p><p>浙江归路杳，西南却羡，投林高鸟。升斗微官，世累苦相萦绕。不似麒麟殿里，又不与、巢由同调。时自笑，虚名负我，半生吟啸。</p><p>●○○●▲，○○●●，⊙○○▲。⊙●○○，●⊙●○○▲。⊙●○○⊙●，●⊙●、○○○▲。○●●，⊙○●●，●○○▲。</p><p>扰扰马足车尘，被岁月无情，暗消年少。钟鼎山林，一事几时曾了。四壁秋虫夜雨，更一点、残灯斜照。清镜晓，白发又添多少。</p><p>⊙⊙●●○○，●⊙●○○，⊙○○▲。⊙●○○，⊙●●○○▲。⊙●○○●●，⊙●●、○○○▲。○●●，⊙●●○○▲。</p><h3 id="八五点绛唇闺情">八五、点绛唇·闺情</h3><p>曾允元</p><p>一夜东风，枕边吹散愁多少。数声啼鸟，梦转纱窗晓。</p><p>●●○○，⊙○⊙●○○▲。●○○▲，⊙●○○▲。</p><p>来是春初，去是春将老。长亭道，一般芳草，只有归时好。</p><p>⊙●○○，⊙●○○▲。○○▲，●○○▲，⊙●○○▲。</p><h3 id="八六满江红金陵怀古">八六、满江红·金陵怀古</h3><p>萨都拉</p><p>六代豪华，春去也、更无消息。空怅望，山川形胜，已非畴昔。王谢堂前双燕子，乌衣巷口曾相识。听夜深寂寞打孤城，春潮急。</p><p>⊙●○○，⊙⊙●、⊙○⊙▲。⊙⊙●，⊙○⊙●，●○○▲。⊙●⊙○○●●，⊙○⊙●○○▲。●⊙○⊙●●○○，○○▲。</p><p>思往事，愁如织。怀故国，空陈迹。但荒烟衰草，乱鸦斜日。玉树歌残秋露冷，胭脂井坏寒□泣。到如今只有蒋山青，秦淮碧。</p><p>○⊙●，○○▲。○⊙●，○○▲。●⊙○○●，●○○▲。⊙●⊙○○●●，⊙○⊙●○○▲。●⊙○⊙●●○○，○○▲。</p><h3 id="八七念奴娇石头城">八七、念奴娇·石头城</h3><p>萨都拉</p><p>石头城上，望天低、吴楚眼空无物。指点六朝形胜地，惟有青山如壁。蔽日旌旗，连云樯橹，白骨纷如雪。一江南北，消磨多少豪杰。</p><p>●○○●，●○○、⊙●●○○▲。⊙●⊙○○●●，⊙●⊙○○▲。⊙●○○，○○⊙●，⊙●○○▲。⊙○○●，⊙○⊙●○▲。</p><p>寂寞避暑离宫，东风辇路，芳草年年发。落日无人松径冷，鬼火高低明灭。歌舞尊前，繁华镜里，暗换青青发。伤心千古，秦淮一片明月。</p><p>⊙●⊙●○○，○○⊙●，⊙●○○▲。⊙●⊙○○●●，⊙●⊙○○▲。⊙●○○，⊙○⊙●，⊙●○○▲。⊙○○●，⊙○○●○▲。</p><h3 id="八八陌上花有怀">八八、陌上花·有怀</h3><p>张翥</p><p>关山梦里，归来还又，岁华催晚。马影鸡声，谙尽倦邮荒馆。绿笺密记多情事，一看一回肠断。待殷勤寄与，旧游莺燕，水流云散。</p><p>⊙○●●，⊙○⊙●，⊙○○▲。⊙●○○，○●●○○▲。⊙○⊙●○○●，⊙●●○○▲。●⊙○●●，⊙○○●，●○○▲。</p><p>满罗衫是酒，香痕凝处，唾碧啼红相半。只恐梅花，瘦倚夜寒谁暖。不成便没相逢日，重整钗鸾筝雁。但何郎纵有，春风词笔，病怀浑懒。</p><p>●○○●●，⊙○⊙●，⊙●⊙○○▲。⊙●○○，⊙●●○○▲。⊙○⊙●○○●，○●⊙○○▲。●⊙○●●，⊙○○●，●○○▲。</p><h3 id="八九东风第一枝忆梅">八九、东风第一枝·忆梅</h3><p>张翥</p><p>老树浑苔，横枝未叶，青春肯误芳约。背阴未返冰魂，阳梢已含红萼。佳人寒怯，谁惊起、晓来梳掠。是月斜、花外么禽，霜冷竹间幽鹤。</p><p>●●○○，○○●●，○○●●○▲。⊙○●●○○，⊙○●○⊙▲。○○⊙●，⊙⊙●、⊙○○▲。●●⊙、⊙●○○，⊙●●○○▲。</p><p>云淡淡，粉痕渐薄。风细细，冻香又落。叩门喜伴金樽，倚栏怕听画角。依稀梦里，记半面、浅窥珠箔。恁时得、重写鸾笺，去访旧游东阁。</p><p>○●●，●○●▲。○●●，●○●▲。●○●●○○，⊙○●○⊙▲。○○⊙●，⊙⊙●、⊙○○▲。●○⊙、⊙●○○，⊙●●○○▲。</p><h3 id="九十摸鱼儿送春">九十、摸鱼儿·送春</h3><p>张翥</p><p>涨西湖、半篙新雨，麴尘波外风软。兰舟同上鸳鸯浦，天气嫩寒轻暖。帘半卷，度一缕歌云、不碍桃花扇。莺娇燕婉，任狂客无肠，王孙有恨，莫放酒杯浅。</p><p>●○○、⊙○○●，⊙○○●○▲。○○⊙●○○●，⊙●⊙○○▲。○●▲，⊙●●○○、⊙●○○▲。○○●▲，●⊙●○○，⊙○⊙●，⊙●●○▲。</p><p>垂杨岸，何处红亭翠馆。如今游兴全懒。山容水态依然好，惟有绮罗云散。君不见，歌舞地、青芜满目成秋苑。斜阳又晚，正落絮飞花，将春欲去，目送水天远。</p><p>○○●，⊙●○○●▲。⊙○○●○▲。○○⊙●○○●，⊙●●○○▲。○●▲，⊙●●、○○⊙●○○▲。○○●▲，●⊙●○○，⊙○⊙●，⊙●●○▲。</p><h3 id="九一多丽西湖">九一、多丽·西湖</h3><p>张翥</p><p>晚山青，一川云树冥冥。正参差、烟凝紫翠，斜阳画出南屏。馆娃归、吴台游鹿，铜仙去、汉苑飞萤。怀古情多，凭高望极，且将樽酒慰飘零。自湖上、爱梅仙远，鹤梦几时醒。空留得、六桥疏柳，孤屿危亭。</p><p>●○△，●○⊙●○△。●○○、⊙○⊙●，⊙○⊙●○△。●○○、⊙○⊙●，⊙⊙●、⊙●○△。⊙●○○，⊙○⊙●，⊙○⊙●●○△。●⊙●、⊙○⊙●，⊙●●○△。○○●、⊙○⊙●，⊙●○△。</p><p>待苏堤、歌声散尽，更须携妓西泠。藕花深，雨凉翡翠，菰蒲软、风弄蜻蜓。澄碧生秋，闹红驻景，采菱新唱最堪听。见一片，水天无际，渔火两三星。多情月，为人留照，未过前汀。</p><p>●○○、⊙○⊙●，⊙○⊙●○△。●○○，⊙○⊙●，⊙⊙●、⊙●○△。⊙●○○，⊙○⊙●，⊙○⊙●●○△。●⊙●，⊙○⊙●，⊙●●○△。○○●，⊙○⊙●，⊙●○△。</p><h3 id="九二夺锦标七夕">九二、夺锦标·七夕</h3><p>张埜</p><p>凉月横舟，银潢浸练，万里秋容如拭。冉冉鸾骖鹤驭，桥倚高寒，鹊飞空碧。问欢情几许，早收拾、新愁重织。恨人间、会少离多，万古千秋今夕。</p><p>⊙●○○，○○●●，●●○○○▲。●●○○⊙●，○●○○，●○○▲。●○○●●，●○⊙、○○○▲。●○○、●●○○，●●○○○▲。</p><p>谁念文园病客。夜色沉沉，独抱一天岑寂。忍记穿针亭榭，金鸭香寒，玉徽尘积。凭新凉半枕，又依稀、行云消息。听窗前、泪雨浪浪，梦里檐声犹滴。</p><p>⊙●○○●▲。●●○○，●●○○○▲。●●○○⊙●，○●○○，●○○▲。●○○●●，●○⊙、○○○▲。●○○、●●○○，●●○○○▲。</p><h3 id="九三眼儿媚秋闺">九三、眼儿媚·秋闺</h3><p>刘基</p><p>萋萋烟草小楼西，云压雁声低。两行疏柳，一丝残照，万点鸦栖。</p><p>⊙○⊙●●○△，⊙●●○△。⊙○⊙●，⊙○⊙●，⊙●○△。</p><p>春山碧树秋重绿，人在武陵溪。无情明月，有情归梦，同到幽闺。</p><p>⊙○⊙●○○●，⊙●●○△。⊙○⊙●，⊙○⊙●，⊙●○△。</p><h3 id="九四误佳期闺怨">九四、误佳期·闺怨</h3><p>汪懋麟</p><p>寒气暗侵帘幕，孤负芳春小约。庭梅开遍不归来，直恁心情恶。</p><p>⊙●⊙○○▲，⊙●⊙○●▲。⊙○⊙●●○○，●●○○▲。</p><p>独抱影儿眠，背看灯花落。待他重与画眉时，细数郎轻薄。</p><p>⊙●●○○，⊙●○○▲。⊙○⊙●●○○，●●○○▲。</p><h3 id="九五柳梢青纪游">九五、柳梢青·纪游</h3><p>朱彝尊</p><p>障羞罗扇，花时犹记，这边曾见。曲录栏干，玲珑窗户，也都寻遍。</p><p>●○○▲，⊙○○●，⊙○○▲。⊙●○○，⊙○○●，⊙○○▲。</p><p>两峰依旧青青，但不比、眉梢平远。第一难忘，重来崔护，去年人面。</p><p>⊙○⊙●○○，●⊙●、⊙○○▲。⊙●○○，⊙○○●，⊙○○▲。</p><h3 id="九六解佩令自题词集">九六、解佩令·自题词集</h3><p>朱彝尊</p><p>十年磨剑，五陵结客，把平生、涕泪都飘尽。老去填词，一半是、空中传恨。几曾围，燕钗蝉鬓。</p><p>⊙○⊙●，⊙○⊙●，●○○、⊙●○○▲。⊙●○○，●⊙⊙、⊙○○▲。⊙○○，⊙○⊙▲。</p><p>不师秦七，不师黄九，倚新声、玉田差近。落拓江湖，且分付、歌筵红粉。料封侯、白头无分。</p><p>⊙○⊙●，⊙○⊙●，●○○、●○○▲。⊙●○○，●⊙●、⊙○○▲。●○○、●○⊙▲。</p><h3 id="九七暗香咏红豆">九七、暗香·咏红豆</h3><p>朱彝尊</p><p>凝珠吹黍，似早梅乍萼，新桐初乳。莫是珊瑚，零乱敲残石家树。记得南中旧事，金齿屐、小鬟蛮女。向两岸、树底盈盈，素手摘新雨。</p><p>⊙○○▲，●⊙○●●，⊙○○▲。●●⊙○，○●○○●○▲。⊙●○○●●，⊙●●、⊙○○▲。●●●、⊙●○○，⊙●●○▲。</p><p>延伫。碧云暮。休逗入茜裙，欲寻无处。唱歌归去，先向绿窗饲鹦鹉。惆怅檀郎终远，待寄与、相思犹阻。烛影下，开玉合，背人偷数。</p><p>○▲。●○▲。⊙●●⊙○，●○○▲。●○⊙▲，○●⊙○●○▲。⊙●○○⊙●，⊙●●、⊙○○▲。●●●，○●●，●○○▲。</p><h3 id="九八庆春泽纪恨">九八、庆春泽·纪恨</h3><p>朱彝尊</p><p>桥影流虹，湖光映雪，翠帘不卷春深。一寸横波，断肠人在楼阴。游丝不系羊车住，倩何人、传语青禽。最难禁，倚遍雕栏，梦遍罗衾。</p><p>⊙●○○，○○●●，⊙○⊙●○△。⊙●○○，⊙○⊙●○△。⊙○⊙●○○●，●○○、⊙●○△。●○△，⊙●○○，⊙●○△。</p><p>重来已是朝云散，怅明珠佩冷，紫玉烟沉。前度桃花，依然开满江浔。钟情怕到相思路，盼长堤、草尽红心。动愁吟，碧落黄泉，两处谁寻。</p><p>○○●●○○●，●○○⊙●，⊙●○△。⊙●○○，⊙○⊙●○△。⊙○⊙●○○●，●○○、⊙●○△。●○△，⊙●○○，⊙●○△。</p><h3 id="九九春风袅娜游丝">九九、春风袅娜·游丝</h3><p>朱彝尊</p><p>倩东君着力，系住韶华。穿小径，漾晴沙。正阴云笼日，难寻野马，轻□染草，细绾秋蛇。燕蹴还低，莺衔忽溜，惹却黄须无数花。纵许悠扬度朱户，终愁人影隔窗纱。</p><p>●○○⊙●，●●○△。○●●，●○△。●○○⊙●，⊙○⊙●，○○●●，●●○△。●●○○，○○⊙●，●●○○○●△。●●○○●○●，○○○●●○△。</p><p>惆怅谢娘池阁，湘帘乍卷，凝斜盼、近拂檐牙。疏篱□，短垣遮。微风别院，好景谁家。红袖招时，偏随罗扇，玉鞭堕处，又逐香车。休憎轻薄，笑多情似我，春心不定，飞梦天涯。</p><p>⊙●⊙○⊙●，○○●●，⊙○●、●●○△。○○●，●○△。○○●●，⊙●○△。⊙●○○，⊙○⊙●，⊙○●●，●●○△。○○⊙●，●○○⊙●，○○●●，⊙●○△。</p><h3 id="一百翠楼吟魂">一百、翠楼吟·魂</h3><p>黄之隽</p><p>月魄荒唐，花灵仿佛，相携最无人处。栏干芳草外，忽惊转、几声啼宇。飘零何许，似一缕游丝，因风吹去。浑无据，想应凄断，路旁酸雨。</p><p>●●○○，○○●●，○○●○○▲。⊙○○●●，●○●、⊙○○▲。○○⊙▲，●●●○○，○○⊙▲。○○▲，●○○●，●○○▲。</p><p>日暮，渺渺愁予，觉黯然销却，别情离绪。春阴楼外远，入烟柳、和莺私语。连江暝树，欲打点幽香，随郎黏住。能留否，只愁轻绝，化为飞絮。</p><p>●▲，○●○○，●●○○●，●○○▲。⊙○○●●，●○●、⊙○○▲。○○⊙▲，●●●○○，○○⊙▲。○○▲，●○○●，●○○▲。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[安卓 WebView 图片离线缓存方案]]></title>
        <id>/posts/2020/android-webview-picture-cache</id>
        <link href="https://hadb.me/posts/2020/android-webview-picture-cache"/>
        <updated>2020-12-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[有这样一个项目，UI 渲染全部由 WebView 来完成，套个安卓的壳，壳子里面做一些和硬件交互的功能，例如摄像头、麦克风等。WebView 加载的页面走的本地打包的文件。不过 WebView 中的图片等资源走的是网络访问。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20201218.android-webview-picture-cache/cover.png" alt="封面" /><p>有这样一个项目，UI 渲染全部由 WebView 来完成，套个安卓的壳，壳子里面做一些和硬件交互的功能，例如摄像头、麦克风等。WebView 加载的页面走的本地打包的文件。不过 WebView 中的图片等资源走的是网络访问。</p><p>为了减少网络访问的流量，以及提升在弱网络或无网络情况下的体验，需要对网络访问的图片进行本地缓存。</p><p>原先采用的是 WebView 自带的缓存机制来实现，但并不可靠，于是需要通过拦截网络请求，通过本地缓存干预的方式来实现。具体原理如下：</p><ol><li>通过 <code>shouldInterceptRequest</code> 拦截请求，判断是否是访问网络图片，如果是则进行干预</li><li>取请求地址的 <code>md5</code> 值加图片文件扩展名组成的文件名，拼接 <code>cache</code> 目录获得一个本地资源地址，判断该资源是否存在，若存在则直接返回该资源</li><li>若该资源不存在，说明是首次访问，则将该网络图片下载到该地址下，并返回该资源</li></ol><p>具体代码如下：</p><pre><code><span class="line" line="1"><span class="smGrS">import</span><span class="sbgvK"> android.content.Context
</span></span><span class="line" line="2"><span class="smGrS">import</span><span class="sbgvK"> android.net.http.SslError
</span></span><span class="line" line="3"><span class="smGrS">import</span><span class="sbgvK"> android.webkit.</span><span class="s_hVV">*
</span></span><span class="line" line="4"><span class="smGrS">import</span><span class="sbgvK"> androidx.webkit.WebViewAssetLoader
</span></span><span class="line" line="5"><span class="smGrS">import</span><span class="sbgvK"> java.io.File
</span></span><span class="line" line="6"><span class="smGrS">import</span><span class="sbgvK"> java.io.FileOutputStream
</span></span><span class="line" line="7"><span class="smGrS">import</span><span class="sbgvK"> java.math.BigInteger
</span></span><span class="line" line="8"><span class="smGrS">import</span><span class="sbgvK"> java.net.HttpURLConnection
</span></span><span class="line" line="9"><span class="smGrS">import</span><span class="sbgvK"> java.net.URL
</span></span><span class="line" line="10"><span class="smGrS">import</span><span class="sbgvK"> java.security.MessageDigest
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="smGrS">class</span><span class="sbgvK"> CommonWebClient</span><span class="su5hD">(context: </span><span class="sbgvK">Context</span><span class="su5hD">) : </span><span class="sbgvK">WebViewClient</span><span class="su5hD">() {
</span></span><span class="line" line="13"><span class="sbsja">    private</span><span class="smGrS"> var</span><span class="su5hD"> assetLoader: </span><span class="sbgvK">WebViewAssetLoader</span><span class="smGrS"> =</span><span class="su5hD"> WebViewAssetLoader.</span><span class="sGLFI">Builder</span><span class="su5hD">()
</span></span><span class="line" line="14"><span class="su5hD">            .</span><span class="sGLFI">addPathHandler</span><span class="su5hD">(</span><span class="s_sjI">"/assets/"</span><span class="su5hD">, WebViewAssetLoader.</span><span class="sGLFI">AssetsPathHandler</span><span class="su5hD">(context))
</span></span><span class="line" line="15"><span class="su5hD">            .</span><span class="sGLFI">build</span><span class="su5hD">()
</span></span><span class="line" line="16"><span emptyLinePlaceholder>
</span></span><span class="line" line="17"><span class="sbsja">    private</span><span class="smGrS"> fun</span><span class="sGLFI"> md5</span><span class="su5hD">(input: </span><span class="sbgvK">String</span><span class="su5hD">): </span><span class="sbgvK">String</span><span class="su5hD"> {
</span></span><span class="line" line="18"><span class="sVHd0">        return</span><span class="sGLFI"> BigInteger</span><span class="su5hD">(</span><span class="srdBf">1</span><span class="su5hD">, MessageDigest.</span><span class="sGLFI">getInstance</span><span class="su5hD">(</span><span class="s_sjI">"MD5"</span><span class="su5hD">).</span><span class="sGLFI">digest</span><span class="su5hD">(input.</span><span class="sGLFI">toByteArray</span><span class="su5hD">())).</span><span class="sGLFI">toString</span><span class="su5hD">(</span><span class="srdBf">16</span><span class="su5hD">).</span><span class="sGLFI">padStart</span><span class="su5hD">(</span><span class="srdBf">32</span><span class="su5hD">, </span><span class="s_sjI">'0'</span><span class="su5hD">)
</span></span><span class="line" line="19"><span class="su5hD">    }
</span></span><span class="line" line="20"><span emptyLinePlaceholder>
</span></span><span class="line" line="21"><span class="sbsja">    override</span><span class="smGrS"> fun</span><span class="sGLFI"> shouldOverrideUrlLoading</span><span class="su5hD">(view: </span><span class="sbgvK">WebView</span><span class="su5hD">, url: </span><span class="sbgvK">String</span><span class="su5hD">): </span><span class="sbgvK">Boolean</span><span class="su5hD"> {
</span></span><span class="line" line="22"><span class="sVHd0">        return</span><span class="syTEX"> true
</span></span><span class="line" line="23"><span class="su5hD">    }
</span></span><span class="line" line="24"><span emptyLinePlaceholder>
</span></span><span class="line" line="25"><span class="sbsja">    override</span><span class="smGrS"> fun</span><span class="sGLFI"> shouldInterceptRequest</span><span class="su5hD">(view: </span><span class="sbgvK">WebView</span><span class="su5hD">?, request: </span><span class="sbgvK">WebResourceRequest</span><span class="su5hD">?): </span><span class="sbgvK">WebResourceResponse</span><span class="su5hD">? {
</span></span><span class="line" line="26"><span class="sVHd0">        return</span><span class="su5hD"> request?.url?.</span><span class="sGLFI">let</span><span class="su5hD"> { url </span><span class="sbsja">->
</span></span><span class="line" line="27"><span class="smGrS">            val</span><span class="su5hD"> urlString </span><span class="smGrS">=</span><span class="su5hD"> url.</span><span class="sGLFI">toString</span><span class="su5hD">()
</span></span><span class="line" line="28"><span class="sVHd0">            if</span><span class="su5hD"> (</span><span class="smGrS">!</span><span class="su5hD">urlString.</span><span class="sGLFI">contains</span><span class="su5hD">(</span><span class="s_sjI">"appassets.androidplatform.net"</span><span class="su5hD">) </span><span class="smGrS">&&</span><span class="su5hD"> urlString.</span><span class="sGLFI">contains</span><span class="su5hD">(</span><span class="s_sjI">"aliyuncs.com"</span><span class="su5hD">)) {
</span></span><span class="line" line="29"><span class="sVHd0">                try</span><span class="su5hD"> {
</span></span><span class="line" line="30"><span class="smGrS">                    var</span><span class="su5hD"> extension </span><span class="smGrS">=</span><span class="su5hD"> urlString.</span><span class="sGLFI">substring</span><span class="su5hD">(urlString.</span><span class="sGLFI">lastIndexOf</span><span class="su5hD">(</span><span class="s_sjI">"."</span><span class="su5hD">))
</span></span><span class="line" line="31"><span class="sVHd0">                    if</span><span class="su5hD"> (extension.</span><span class="sGLFI">lastIndexOf</span><span class="su5hD">(</span><span class="s_sjI">"?"</span><span class="su5hD">) </span><span class="smGrS">></span><span class="smGrS"> -</span><span class="srdBf">1</span><span class="su5hD">) {
</span></span><span class="line" line="32"><span class="su5hD">                        extension </span><span class="smGrS">=</span><span class="su5hD"> extension.</span><span class="sGLFI">substring</span><span class="su5hD">(</span><span class="srdBf">0</span><span class="su5hD">, extension.</span><span class="sGLFI">lastIndexOf</span><span class="su5hD">(</span><span class="s_sjI">"?"</span><span class="su5hD">))
</span></span><span class="line" line="33"><span class="su5hD">                    }
</span></span><span class="line" line="34"><span class="smGrS">                    val</span><span class="su5hD"> fileName </span><span class="smGrS">=</span><span class="s_sjI"> "</span><span class="sjJ54">${</span><span class="sGLFI">md5</span><span class="sfo-9">(urlString)</span><span class="sjJ54">}${</span><span class="sfo-9">extension</span><span class="sjJ54">}</span><span class="s_sjI">"
</span></span><span class="line" line="35"><span class="smGrS">                    val</span><span class="su5hD"> file </span><span class="smGrS">=</span><span class="sGLFI"> File</span><span class="su5hD">(view?.context?.externalCacheDir, fileName)
</span></span><span class="line" line="36"><span class="sVHd0">                    if</span><span class="su5hD"> (</span><span class="smGrS">!</span><span class="su5hD">file.</span><span class="sGLFI">exists</span><span class="su5hD">()) {
</span></span><span class="line" line="37"><span class="smGrS">                        val</span><span class="su5hD"> conn </span><span class="smGrS">=</span><span class="sGLFI"> URL</span><span class="su5hD">(urlString).</span><span class="sGLFI">openConnection</span><span class="su5hD">() </span><span class="smGrS">as</span><span class="su5hD"> HttpURLConnection
</span></span><span class="line" line="38"><span class="su5hD">                        conn.connectTimeout </span><span class="smGrS">=</span><span class="srdBf"> 5000
</span></span><span class="line" line="39"><span class="su5hD">                        conn.requestMethod </span><span class="smGrS">=</span><span class="s_sjI"> "GET"
</span></span><span class="line" line="40"><span class="su5hD">                        conn.doInput </span><span class="smGrS">=</span><span class="syTEX"> true
</span></span><span class="line" line="41"><span class="sVHd0">                        if</span><span class="su5hD"> (conn.responseCode </span><span class="smGrS">==</span><span class="srdBf"> 200</span><span class="su5hD">) {
</span></span><span class="line" line="42"><span class="smGrS">                            val</span><span class="su5hD"> fos </span><span class="smGrS">=</span><span class="sGLFI"> FileOutputStream</span><span class="su5hD">(file)
</span></span><span class="line" line="43"><span class="smGrS">                            val</span><span class="su5hD"> buffer </span><span class="smGrS">=</span><span class="sGLFI"> ByteArray</span><span class="su5hD">(</span><span class="srdBf">1024</span><span class="su5hD">)
</span></span><span class="line" line="44"><span class="smGrS">                            var</span><span class="su5hD"> len </span><span class="smGrS">=</span><span class="srdBf"> 0
</span></span><span class="line" line="45"><span class="sVHd0">                            while</span><span class="su5hD"> (conn.inputStream.</span><span class="sGLFI">read</span><span class="su5hD">(buffer).</span><span class="sGLFI">also</span><span class="su5hD"> { len </span><span class="smGrS">=</span><span class="su5hD"> it } </span><span class="smGrS">!=</span><span class="smGrS"> -</span><span class="srdBf">1</span><span class="su5hD">) {
</span></span><span class="line" line="46"><span class="su5hD">                                fos.</span><span class="sGLFI">write</span><span class="su5hD">(buffer, </span><span class="srdBf">0</span><span class="su5hD">, len)
</span></span><span class="line" line="47"><span class="su5hD">                            }
</span></span><span class="line" line="48"><span class="su5hD">                            conn.inputStream.</span><span class="sGLFI">close</span><span class="su5hD">()
</span></span><span class="line" line="49"><span class="su5hD">                            fos.</span><span class="sGLFI">close</span><span class="su5hD">()
</span></span><span class="line" line="50"><span class="su5hD">                        }
</span></span><span class="line" line="51"><span class="su5hD">                    }
</span></span><span class="line" line="52"><span class="sGLFI">                    WebResourceResponse</span><span class="su5hD">(MimeTypeMap.</span><span class="sGLFI">getSingleton</span><span class="su5hD">().</span><span class="sGLFI">getMimeTypeFromExtension</span><span class="su5hD">(extension), </span><span class="s_sjI">"UTF-8"</span><span class="su5hD">, file.</span><span class="sGLFI">inputStream</span><span class="su5hD">())
</span></span><span class="line" line="53"><span class="su5hD">                } </span><span class="smGrS">catch</span><span class="su5hD"> (ex: </span><span class="sbgvK">Exception</span><span class="su5hD">) {
</span></span><span class="line" line="54"><span class="su5hD">                    assetLoader.</span><span class="sGLFI">shouldInterceptRequest</span><span class="su5hD">(url)
</span></span><span class="line" line="55"><span class="su5hD">                }
</span></span><span class="line" line="56"><span class="su5hD">            } </span><span class="sVHd0">else</span><span class="su5hD"> {
</span></span><span class="line" line="57"><span class="smGrS">                val</span><span class="su5hD"> response </span><span class="smGrS">=</span><span class="su5hD"> assetLoader.</span><span class="sGLFI">shouldInterceptRequest</span><span class="su5hD">(url)
</span></span><span class="line" line="58"><span class="sVHd0">                if</span><span class="su5hD"> (url.path?.</span><span class="sGLFI">endsWith</span><span class="su5hD">(</span><span class="s_sjI">".js"</span><span class="su5hD">) </span><span class="smGrS">==</span><span class="syTEX"> true</span><span class="smGrS"> &&</span><span class="su5hD"> response </span><span class="smGrS">!=</span><span class="s39Yj"> null</span><span class="su5hD">) {
</span></span><span class="line" line="59"><span class="su5hD">                    response.mimeType </span><span class="smGrS">=</span><span class="s_sjI"> "text/javascript"
</span></span><span class="line" line="60"><span class="su5hD">                }
</span></span><span class="line" line="61"><span class="su5hD">                response
</span></span><span class="line" line="62"><span class="su5hD">            }
</span></span><span class="line" line="63"><span class="su5hD">        }
</span></span><span class="line" line="64"><span class="su5hD">    }
</span></span><span class="line" line="65"><span emptyLinePlaceholder>
</span></span><span class="line" line="66"><span class="sbsja">    override</span><span class="smGrS"> fun</span><span class="sGLFI"> onReceivedSslError</span><span class="su5hD">(view: </span><span class="sbgvK">WebView</span><span class="su5hD">?, handler: </span><span class="sbgvK">SslErrorHandler</span><span class="su5hD">?, error: </span><span class="sbgvK">SslError</span><span class="su5hD">?) {
</span></span><span class="line" line="67"><span class="su5hD">        handler?.</span><span class="sGLFI">proceed</span><span class="su5hD">()
</span></span><span class="line" line="68"><span class="s39Yj">        super</span><span class="su5hD">.</span><span class="sGLFI">onReceivedSslError</span><span class="su5hD">(view, handler, error)
</span></span><span class="line" line="69"><span class="su5hD">    }
</span></span><span class="line" line="70"><span class="su5hD">}
</span></span></code></pre>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[基于 GitLab CI 和阿里云 k8s 的持续交付解决方案]]></title>
        <id>/posts/2020/devops-gitlab-ci-aliyun-k8s</id>
        <link href="https://hadb.me/posts/2020/devops-gitlab-ci-aliyun-k8s"/>
        <updated>2020-12-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今年对于我个人而言，在 DevOps 上的最大收获，莫过于摸索了这套基于 GitLab CI 和 k8s 的持续交付解决方案，其实原理都很简单，在我去年的方案里又做了改进，实现了基于 git tag 的触发方式，并且把原先的本地打包推镜像改为在 GitLab Runner 上打包推镜像。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2020/20201227.devops-gitlab-ci-aliyun-k8s/cover.png" alt="封面" /><p>今年对于我个人而言，在 DevOps 上的最大收获，莫过于摸索了这套基于 GitLab CI 和 k8s 的持续交付解决方案，其实原理都很简单，在我去年的方案里又做了改进，实现了基于 <code>git tag</code> 的触发方式，并且把原先的本地打包推镜像改为在 GitLab Runner 上打包推镜像。</p><p>这套解决方案大致流程是这样的：</p><ol><li>推送代码，在代码中配置 <code>gitlab-ci.yml</code></li><li>推送 tag，触发 GitLab Runner 编译 docker 镜像，并推送至阿里云镜像仓库</li><li>在阿里云 k8s 上基于镜像仓库创建应用，并创建重新部署的触发器，在镜像更新时触发该触发器</li></ol><p>这样，以后每次推送新的 tag 上去，就可以实现自动打包&部署了。</p><p>下面，我来详细讲解下所有步骤。</p><h2 id="配置-gitlab-runner">配置 GitLab Runner</h2><h5 id="configtoml">config.toml</h5><pre><code><span class="line" line="1"><span class="su5hD">concurrent </span><span class="sP7_E">=</span><span class="srdBf"> 1
</span></span><span class="line" line="2"><span class="su5hD">check_interval </span><span class="sP7_E">=</span><span class="srdBf"> 0
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="sP7_E">[</span><span class="sbgvK">session_server</span><span class="sP7_E">]
</span></span><span class="line" line="5"><span class="su5hD">session_timeout </span><span class="sP7_E">=</span><span class="srdBf"> 1800
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sP7_E">[[</span><span class="sbgvK">runners</span><span class="sP7_E">]]
</span></span><span class="line" line="8"><span class="su5hD">name </span><span class="sP7_E">=</span><span class="sjJ54"> "</span><span class="s_sjI">common-runner</span><span class="sjJ54">"
</span></span><span class="line" line="9"><span class="su5hD">url </span><span class="sP7_E">=</span><span class="sjJ54"> "</span><span class="s_sjI">https://git.xxx.xxx</span><span class="sjJ54">"
</span></span><span class="line" line="10"><span class="su5hD">token </span><span class="sP7_E">=</span><span class="sjJ54"> "</span><span class="s_sjI">TOKEN</span><span class="sjJ54">"
</span></span><span class="line" line="11"><span class="su5hD">executor </span><span class="sP7_E">=</span><span class="sjJ54"> "</span><span class="s_sjI">docker</span><span class="sjJ54">"
</span></span><span class="line" line="12"><span emptyLinePlaceholder>
</span></span><span class="line" line="13"><span class="sP7_E">[</span><span class="sbgvK">runners</span><span class="su5hD">.</span><span class="sbgvK">custom_build_dir</span><span class="sP7_E">]
</span></span><span class="line" line="14"><span emptyLinePlaceholder>
</span></span><span class="line" line="15"><span class="sP7_E">[</span><span class="sbgvK">runners</span><span class="su5hD">.</span><span class="sbgvK">cache</span><span class="sP7_E">]
</span></span><span class="line" line="16"><span emptyLinePlaceholder>
</span></span><span class="line" line="17"><span class="sP7_E">[</span><span class="sbgvK">runners</span><span class="su5hD">.</span><span class="sbgvK">cache</span><span class="su5hD">.</span><span class="sbgvK">s3</span><span class="sP7_E">]
</span></span><span class="line" line="18"><span emptyLinePlaceholder>
</span></span><span class="line" line="19"><span class="sP7_E">[</span><span class="sbgvK">runners</span><span class="su5hD">.</span><span class="sbgvK">cache</span><span class="su5hD">.</span><span class="sbgvK">gcs</span><span class="sP7_E">]
</span></span><span class="line" line="20"><span emptyLinePlaceholder>
</span></span><span class="line" line="21"><span class="sP7_E">[</span><span class="sbgvK">runners</span><span class="su5hD">.</span><span class="sbgvK">docker</span><span class="sP7_E">]
</span></span><span class="line" line="22"><span class="su5hD">tls_verify </span><span class="sP7_E">=</span><span class="syTEX"> false
</span></span><span class="line" line="23"><span class="su5hD">image </span><span class="sP7_E">=</span><span class="sjJ54"> "</span><span class="s_sjI">docker:latest</span><span class="sjJ54">"
</span></span><span class="line" line="24"><span class="su5hD">privileged </span><span class="sP7_E">=</span><span class="syTEX"> false
</span></span><span class="line" line="25"><span class="su5hD">disable_entrypoint_overwrite </span><span class="sP7_E">=</span><span class="syTEX"> false
</span></span><span class="line" line="26"><span class="su5hD">oom_kill_disable </span><span class="sP7_E">=</span><span class="syTEX"> false
</span></span><span class="line" line="27"><span class="su5hD">disable_cache </span><span class="sP7_E">=</span><span class="syTEX"> false
</span></span><span class="line" line="28"><span class="su5hD">volumes </span><span class="sP7_E">=</span><span class="sP7_E"> [
</span></span><span class="line" line="29"><span class="sjJ54">  "</span><span class="s_sjI">/var/run/docker.sock:/var/run/docker.sock</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="30"><span class="sjJ54">  "</span><span class="s_sjI">/xxx/gitlab-runner/cache:/cache</span><span class="sjJ54">"
</span></span><span class="line" line="31"><span class="sP7_E">]
</span></span><span class="line" line="32"><span class="su5hD">shm_size </span><span class="sP7_E">=</span><span class="srdBf"> 0
</span></span></code></pre><p>其中 <code>token</code> 从 <code>GitLab Admin Area / Overview / Runners</code> 中可以找到，或者也可以从 <code>Project / Settings / CI/CD</code> 中找到项目专用的 Runner token。</p><h2 id="代码配置">代码配置</h2><h4 id="前端node">前端（node）</h4><h5 id="dockerfile">Dockerfile</h5><pre><code><span class="line" line="1"><span class="sw1J6">FROM</span><span class="su5hD"> node:10-alpine
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sw1J6">WORKDIR</span><span class="su5hD"> /app
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sw1J6">COPY</span><span class="su5hD"> package.json /app
</span></span><span class="line" line="6"><span class="sw1J6">COPY</span><span class="su5hD"> yarn.lock /app
</span></span><span class="line" line="7"><span class="sw1J6">RUN</span><span class="su5hD"> yarn install
</span></span><span class="line" line="8"><span class="sw1J6">COPY</span><span class="su5hD"> . /app
</span></span><span class="line" line="9"><span class="sw1J6">RUN</span><span class="su5hD"> yarn build
</span></span><span class="line" line="10"><span emptyLinePlaceholder>
</span></span><span class="line" line="11"><span class="sw1J6">EXPOSE</span><span class="su5hD"> 8888
</span></span><span class="line" line="12"><span class="sw1J6">ENV</span><span class="su5hD"> APP_ENV $APP_ENV
</span></span><span class="line" line="13"><span class="sw1J6">CMD</span><span class="su5hD"> [</span><span class="s_sjI">"yarn"</span><span class="su5hD">, </span><span class="s_sjI">"start"</span><span class="su5hD">]
</span></span></code></pre><h5 id="gitlab-ciyml">.gitlab-ci.yml</h5><pre><code><span class="line" line="1"><span class="sQzsp">image</span><span class="sP7_E">:</span><span class="s_sjI"> docker:latest
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sQzsp">variables</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  REGISTRY</span><span class="sP7_E">:</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com
</span></span><span class="line" line="5"><span class="sQzsp">  USERNAME</span><span class="sP7_E">:</span><span class="s_sjI"> your username
</span></span><span class="line" line="6"><span class="sQzsp">  PASSWORD</span><span class="sP7_E">:</span><span class="s_sjI"> your password
</span></span><span class="line" line="7"><span class="sQzsp">  NAMESPACE</span><span class="sP7_E">:</span><span class="s_sjI"> your namespace
</span></span><span class="line" line="8"><span class="sQzsp">  PROJECT_NAME</span><span class="sP7_E">:</span><span class="s_sjI"> your project name
</span></span><span class="line" line="9"><span emptyLinePlaceholder>
</span></span><span class="line" line="10"><span class="sQzsp">stages</span><span class="sP7_E">:
</span></span><span class="line" line="11"><span class="sP7_E">  -</span><span class="s_sjI"> build
</span></span><span class="line" line="12"><span emptyLinePlaceholder>
</span></span><span class="line" line="13"><span class="sQzsp">docker-build</span><span class="sP7_E">:
</span></span><span class="line" line="14"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> build
</span></span><span class="line" line="15"><span class="sQzsp">  image</span><span class="sP7_E">:</span><span class="s_sjI"> docker:latest
</span></span><span class="line" line="16"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="17"><span class="sP7_E">    -</span><span class="s_sjI"> docker login --username=$USERNAME $REGISTRY -p $PASSWORD
</span></span><span class="line" line="18"><span class="sP7_E">    -</span><span class="s_sjI"> docker build -t $REGISTRY/$NAMESPACE/$PROJECT_NAME:$CI_COMMIT_REF_NAME -t $REGISTRY/$NAMESPACE/$PROJECT_NAME:latest .
</span></span><span class="line" line="19"><span class="sP7_E">    -</span><span class="s_sjI"> docker push $REGISTRY/$NAMESPACE/$PROJECT_NAME:$CI_COMMIT_REF_NAME
</span></span><span class="line" line="20"><span class="sP7_E">    -</span><span class="s_sjI"> docker push $REGISTRY/$NAMESPACE/$PROJECT_NAME:latest
</span></span><span class="line" line="21"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="22"><span class="sP7_E">    -</span><span class="s_sjI"> tags
</span></span></code></pre><h4 id="后端spring-boot">后端（spring boot）</h4><h5 id="dockerfile-1">Dockerfile</h5><pre><code><span class="line" line="1"><span class="sw1J6">FROM</span><span class="su5hD"> openjdk:11-jre-slim
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sw1J6">RUN</span><span class="su5hD"> ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sw1J6">VOLUME</span><span class="su5hD"> /tmp
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sw1J6">COPY</span><span class="su5hD"> target/xxx-api.jar app.jar
</span></span><span class="line" line="8"><span class="sw1J6">ENV</span><span class="su5hD"> SPRING_PROFILES_ACTIVE=</span><span class="s_sjI">"prd"
</span></span><span class="line" line="9"><span class="sw1J6">ENV</span><span class="su5hD"> JAVA_OPTS=</span><span class="s_sjI">"-Xmx256m"
</span></span><span class="line" line="10"><span class="sw1J6">ENTRYPOINT</span><span class="su5hD"> [ </span><span class="s_sjI">"java"</span><span class="su5hD">, </span><span class="s_sjI">"-Djava.security.egd=file:/dev/./urandom"</span><span class="su5hD">, </span><span class="s_sjI">"-jar"</span><span class="su5hD">, </span><span class="s_sjI">"/app.jar"</span><span class="su5hD">]
</span></span></code></pre><h5 id="gitlab-ciyml-1">.gitlab-ci.yml</h5><pre><code><span class="line" line="1"><span class="sQzsp">image</span><span class="sP7_E">:</span><span class="s_sjI"> docker:latest
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sQzsp">variables</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">  MAVEN_OPTS</span><span class="sP7_E">:</span><span class="s_sjI"> -Dmaven.repo.local=/cache/.m2/repository
</span></span><span class="line" line="5"><span class="sQzsp">  REGISTRY</span><span class="sP7_E">:</span><span class="s_sjI"> registry.cn-hangzhou.aliyuncs.com
</span></span><span class="line" line="6"><span class="sQzsp">  USERNAME</span><span class="sP7_E">:</span><span class="s_sjI"> your username
</span></span><span class="line" line="7"><span class="sQzsp">  PASSWORD</span><span class="sP7_E">:</span><span class="s_sjI"> your password
</span></span><span class="line" line="8"><span class="sQzsp">  NAMESPACE</span><span class="sP7_E">:</span><span class="s_sjI"> your namespace
</span></span><span class="line" line="9"><span class="sQzsp">  PROJECT_NAME</span><span class="sP7_E">:</span><span class="s_sjI"> your project name
</span></span><span class="line" line="10"><span emptyLinePlaceholder>
</span></span><span class="line" line="11"><span class="sQzsp">stages</span><span class="sP7_E">:
</span></span><span class="line" line="12"><span class="sP7_E">  -</span><span class="s_sjI"> package
</span></span><span class="line" line="13"><span class="sP7_E">  -</span><span class="s_sjI"> build
</span></span><span class="line" line="14"><span emptyLinePlaceholder>
</span></span><span class="line" line="15"><span class="sQzsp">maven-package</span><span class="sP7_E">:
</span></span><span class="line" line="16"><span class="sQzsp">  image</span><span class="sP7_E">:</span><span class="s_sjI"> maven:3.6-jdk-11-slim
</span></span><span class="line" line="17"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> package
</span></span><span class="line" line="18"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="19"><span class="sP7_E">    -</span><span class="s_sjI"> mvn $MAVEN_OPTS clean package -Dmaven.test.skip=true
</span></span><span class="line" line="20"><span class="sP7_E">    -</span><span class="s_sjI"> cp target/$PROJECT_NAME.jar /cache/jars/
</span></span><span class="line" line="21"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="22"><span class="sP7_E">    -</span><span class="s_sjI"> tags
</span></span><span class="line" line="23"><span emptyLinePlaceholder>
</span></span><span class="line" line="24"><span class="sQzsp">docker-build</span><span class="sP7_E">:
</span></span><span class="line" line="25"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> build
</span></span><span class="line" line="26"><span class="sQzsp">  image</span><span class="sP7_E">:</span><span class="s_sjI"> docker:latest
</span></span><span class="line" line="27"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="28"><span class="sP7_E">    -</span><span class="s_sjI"> docker login --username=$USERNAME $REGISTRY -p $PASSWORD
</span></span><span class="line" line="29"><span class="sP7_E">    -</span><span class="s_sjI"> mkdir target
</span></span><span class="line" line="30"><span class="sP7_E">    -</span><span class="s_sjI"> cp /cache/jars/$PROJECT_NAME.jar target
</span></span><span class="line" line="31"><span class="sP7_E">    -</span><span class="s_sjI"> docker build -t $REGISTRY/$NAMESPACE/$PROJECT_NAME:$CI_COMMIT_REF_NAME -t $REGISTRY/$NAMESPACE/$PROJECT_NAME:latest .
</span></span><span class="line" line="32"><span class="sP7_E">    -</span><span class="s_sjI"> docker push $REGISTRY/$NAMESPACE/$PROJECT_NAME:$CI_COMMIT_REF_NAME
</span></span><span class="line" line="33"><span class="sP7_E">    -</span><span class="s_sjI"> docker push $REGISTRY/$NAMESPACE/$PROJECT_NAME:latest
</span></span><span class="line" line="34"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="35"><span class="sP7_E">    -</span><span class="s_sjI"> tags
</span></span></code></pre><h2 id="阿里云-k8s-配置">阿里云 k8s 配置</h2><p>应用创建触发器：</p><figure><img src="https://hadb.me/static/posts/2020/20201227.devops-gitlab-ci-aliyun-k8s/01.png"></img></figure><p>复制触发器 URL 到镜像仓库中创建推送触发器：</p><figure><img src="https://hadb.me/static/posts/2020/20201227.devops-gitlab-ci-aliyun-k8s/02.png"></img></figure><p>完成。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2021 年伊始]]></title>
        <id>/posts/2021/beginning-of-2021</id>
        <link href="https://hadb.me/posts/2021/beginning-of-2021"/>
        <updated>2021-01-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[多事之秋的 2020 年终于结束了，这一年最大的关键词可能就是“新冠疫情”了，全民抗疫果真成为了常态，戴口罩也成了大家出门的一种习惯。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2021/20210103.beginning-of-2021/cover.jpg" alt="封面" /><p>多事之秋的 2020 年终于结束了，这一年最大的关键词可能就是“新冠疫情”了，全民抗疫果真成为了常态，戴口罩也成了大家出门的一种习惯。</p><p>回顾一下年初的目标：</p><ul className="contains-task-list"><li className="task-list-item"><input disabled type="checkbox"></input> 去 15 个不同的城市</li><li className="task-list-item"><input disabled type="checkbox"></input> 去健身房 100 次</li><li className="task-list-item"><input disabled type="checkbox"></input> 减脂至 70kg 以下</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 完成 10 篇文章 / Vlog</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 收入 > 支出</li><li className="task-list-item"><input disabled type="checkbox"></input> 平均睡眠时间 7 小时</li></ul><p>年初定下的目标，只完成了两项，其他的都没能完成，倒是年初疫情在家办公的时候，有大把的时间，把前年的目标「荒野大镖客」通关了。去年完成的这两项，疫情也有功劳，没完成的目标或多或少也可以甩锅给疫情。不管怎么样，今年的疫情，给了我们很多的借口，工作中我也看到很多人拿疫情当借口：“今年因为疫情的影响，业绩没能达到目标”；“因为疫情的影响，这个项目延期了”等等。这个借口似乎无法反驳，但终究只能用一次。</p><p>经过前几年定的目标，以及完成情况来看，对于减脂这件事情，我可能真的是不想去做，每年都完成不了。还有睡眠时间，也一直没能提升上来，2020 年平均睡眠时间只有 5 小时 50 分钟。总结原因，还是浪费了太多时间在抖音、知乎这类的软件上面。前段时间把抖音又卸载了，卸载后睡觉时间确实提前了。不过有时候碎片时间不知道该干啥，很无聊。这个今年需要好好思考下，怎么把碎片时间利用好。</p><p>今年我要好好思考下目标，为了提高完成率，目标都完成给自己设置一些奖励。一来让目标制定更为科学，不是为了制定而制定，二是在执行过程中，保持持续的动力。</p><p>今年的目标：</p><ul className="contains-task-list"><li className="task-list-item"><input checked disabled type="checkbox"></input> 一年不安装抖音、知乎 App</li><li className="task-list-item"><input disabled type="checkbox"></input> 发表 12 篇博客</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 小工具集扩充 5 个小工具</li><li className="task-list-item"><input disabled type="checkbox"></input> 椭圆机累计健身 50 小时</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 平均睡眠时间达到 6 小时</li></ul><p>这几个目标，只要第 1 项能完成，估计剩下的就都有时间完成，所以我把它放在第一位。</p><p>完成奖励：戴尔 U2720Q，Apple Watch S6。</p><p>2020 年，我 30 岁了。</p><p>尽管还没完全接受这个事实，但我真的 30 岁了。</p><p>早上看了池老师一篇文章，看来大家对于年龄的增长，都是敏感的。</p><p>回想自己毕业后这么多年，虽然努力程度以及目前所获得的成绩，比起一些同龄人，已经都远远超过他们。但我自己明白，我依旧是一个普通人，一个在历史长河中不会留下一点痕迹的平凡的人。但我不甘这样，我还需要更努力。这些年，虽然我已经比较努力，但我知道，我还是荒废了很多的青春。在我沉迷游戏中的时候、沉浸在抖音里的时候，更努力的人，可能正在为了自己的梦想而奋斗呢。不得不说，最近这一两年，工作确实占据了我太多的时间，以至于仅剩的一些闲暇时光，我的大脑不得不自我调节，用来挥霍了。但这其实也是借口，我可以做得更好。</p><p>2021 年，我需要更好地平衡工作、生活与梦想。安排好工作内容，协调好生活，闲暇时间不忘记为了梦想而努力。</p><p>加油！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[fastboot FAILED (remote: Operation not permitted) 的问题]]></title>
        <id>/posts/2021/fastboot-failed-remote-operation-not-permitted</id>
        <link href="https://hadb.me/posts/2021/fastboot-failed-remote-operation-not-permitted"/>
        <updated>2021-01-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这两天买了台安卓测试设备，由于我们的项目需要系统签名，所以不得不重新刷系统。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2021/20210105.fastboot-failed-remote-operation-not-permitted/cover.png" alt="封面" /><p>这两天买了台安卓测试设备，由于我们的项目需要系统签名，所以不得不重新刷系统。</p><p>在 <code>fastboot flash system</code> 的时候，每次都在最后要完成的时候报一个错误：</p><pre><code><span class="line" line="1"><span class="su5hD">target reported max download size of </span><span class="s39Yj">536870912</span><span class="su5hD"> bytes
</span></span><span class="line" line="2"><span class="su5hD">Sending sparse </span><span class="s_sjI">'system'</span><span class="s39Yj"> 1</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD"> (</span><span class="s39Yj">524284</span><span class="su5hD"> KB)...
</span></span><span class="line" line="3"><span class="su5hD">OKAY [ </span><span class="s39Yj">15</span><span class="su5hD">.543s]
</span></span><span class="line" line="4"><span class="su5hD">Writing </span><span class="s_sjI">'system'</span><span class="s39Yj"> 1</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD">...
</span></span><span class="line" line="5"><span class="su5hD">OKAY [  </span><span class="s39Yj">3</span><span class="su5hD">.548s]
</span></span><span class="line" line="6"><span class="su5hD">Sending sparse </span><span class="s_sjI">'system'</span><span class="s39Yj"> 2</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD"> (</span><span class="s39Yj">524284</span><span class="su5hD"> KB)...
</span></span><span class="line" line="7"><span class="su5hD">OKAY [ </span><span class="s39Yj">15</span><span class="su5hD">.483s]
</span></span><span class="line" line="8"><span class="su5hD">Writing </span><span class="s_sjI">'system'</span><span class="s39Yj"> 2</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD">...
</span></span><span class="line" line="9"><span class="su5hD">OKAY [  </span><span class="s39Yj">3</span><span class="su5hD">.644s]
</span></span><span class="line" line="10"><span class="su5hD">Sending sparse </span><span class="s_sjI">'system'</span><span class="s39Yj"> 3</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD"> (</span><span class="s39Yj">524284</span><span class="su5hD"> KB)...
</span></span><span class="line" line="11"><span class="su5hD">OKAY [ </span><span class="s39Yj">15</span><span class="su5hD">.103s]
</span></span><span class="line" line="12"><span class="su5hD">Writing </span><span class="s_sjI">'system'</span><span class="s39Yj"> 3</span><span class="su5hD">/</span><span class="s39Yj">4</span><span class="su5hD">...
</span></span><span class="line" line="13"><span class="su5hD">FAILED (remote: Operation not permitted)
</span></span><span class="line" line="14"><span class="su5hD">Finished. Total time: </span><span class="s39Yj">58</span><span class="su5hD">.650s
</span></span></code></pre><p>不管怎么 <code>-S</code> 给多少，最后总在 70% 左右的时候报这个错，网上的资料也是查不到。</p><p>后来想，会不会和 system 分区大小有关，尝试了半天又没结果。</p><p>最终下了个最新版的 platform-tools，解决了。问题确实是 system 分区大小的问题，最新版的 fastboot 在烧录前会自动调整 system 分区大小。</p><p>附个最新版 platform-tools 的下载地址：</p><p><a href="https://developer.android.com/studio/releases/platform-tools" rel="nofollow">https://developer.android.com/studio/releases/platform-tools</a></p><p>记录一下，方便后人。如果帮助到你的话，留个言再走吧。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[群晖 Let's Encrypt 配置多个泛域名 SSL 证书自动更新]]></title>
        <id>/posts/2021/synology-letsencrypt-multiple-domain-cert-configuration</id>
        <link href="https://hadb.me/posts/2021/synology-letsencrypt-multiple-domain-cert-configuration"/>
        <updated>2021-01-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前一直用的 syno-acme 配合群晖的计划任务实现泛域名 SSL 证书的更新，但是最近想切换域名，但是又要保持原有域名一段时间可用。syno-acme 的方案只支持默认证书的配置，群晖上多个证书的配置确实比较麻烦，几年前也折腾过。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2021/20210108.synology-letsencrypt-multiple-domain-cert-configuration/cover.jpg" alt="封面" /><p>之前一直用的 <a href="https://github.com/andyzhshg/syno-acme" rel="nofollow">syno-acme</a> 配合群晖的计划任务实现泛域名 SSL 证书的更新，但是最近想切换域名，但是又要保持原有域名一段时间可用。<code>syno-acme</code> 的方案只支持默认证书的配置，群晖上多个证书的配置确实比较麻烦，几年前也折腾过。</p><p>不过调研了下发现，Let's Encrypt 支持将多个域名绑定到同一个证书里，于是找了下解决方案，果然有位兄弟基于 <code>syno-acme</code> 做了些<a href="https://10001blog.xslinc.com/?p=89" rel="nofollow">修改</a>，支持多个域名。不过这位兄弟是 Hard Code 的，不够通用化，于是对 <code>syno-acme</code> 做了些改进，并提交了 <a href="https://github.com/andyzhshg/syno-acme/pull/58" rel="nofollow">Pull request</a>，希望对大家有帮助，<a href="https://github.com/HADB/syno-acme" rel="nofollow">Fork 仓库</a>。</p><p>主要修改内容：</p><p>配置时可通过逗号分隔多个域名，<code>config</code> 如下：</p><pre><code><span class="line" line="1"><span class="sutJx"># 你域名，如 baidu.com sina.com.cn 等，多个域名之间逗号分隔，支持泛域名
</span></span><span class="line" line="2"><span class="sbsja">export</span><span class="su5hD"> DOMAIN</span><span class="smGrS">=</span><span class="su5hD">your_domain1,</span><span class="smGrS">*</span><span class="su5hD">.your_domain1,your_domain2,</span><span class="smGrS">*</span><span class="su5hD">.your_domain2
</span></span></code></pre><p><code>cert-up.sh</code> 主要修改了如下的地方：</p><pre><code><span class="line" line="1"><span class="sVHd0">for</span><span class="su5hD"> d </span><span class="sVHd0">in</span><span class="sP7_E"> ${</span><span class="su5hD">DOMAIN</span><span class="smGrS">//</span><span class="su5hD">,</span><span class="smGrS">/</span><span class="sP7_E"> }
</span></span><span class="line" line="2"><span class="sVHd0">do
</span></span><span class="line" line="3"><span class="su5hD">  domain_params</span><span class="smGrS">=</span><span class="sjJ54">"${</span><span class="su5hD">domain_params</span><span class="sjJ54">}</span><span class="s_sjI"> -d </span><span class="sjJ54">${</span><span class="su5hD">d</span><span class="sjJ54">}"
</span></span><span class="line" line="4"><span class="sVHd0">done
</span></span><span class="line" line="5"><span class="sP7_E">${</span><span class="su5hD">ACME_BIN_PATH</span><span class="sP7_E">}</span><span class="su5hD">/acme.sh --force --log --issue --dns </span><span class="sP7_E">${</span><span class="su5hD">DNS</span><span class="sP7_E">}</span><span class="su5hD"> --dnssleep </span><span class="sP7_E">${</span><span class="su5hD">DNS_SLEEP</span><span class="sP7_E">}</span><span class="sP7_E"> ${</span><span class="su5hD">domain_params</span><span class="sP7_E">}
</span></span><span class="line" line="6"><span class="sP7_E">${</span><span class="su5hD">ACME_BIN_PATH</span><span class="sP7_E">}</span><span class="su5hD">/acme.sh --force --installcert </span><span class="sP7_E">${</span><span class="su5hD">domain_params</span><span class="sP7_E">}</span><span class="s_hVV"> \
</span></span><span class="line" line="7"><span class="sbgvK">  --certpath</span><span class="sP7_E"> ${</span><span class="su5hD">CRT_PATH</span><span class="sP7_E">}</span><span class="s_sjI">/cert.pem</span><span class="s_hVV"> \
</span></span><span class="line" line="8"><span class="stzsN">  --key-file</span><span class="sP7_E"> ${</span><span class="su5hD">CRT_PATH</span><span class="sP7_E">}</span><span class="s_sjI">/privkey.pem</span><span class="s_hVV"> \
</span></span><span class="line" line="9"><span class="stzsN">  --fullchain-file</span><span class="sP7_E"> ${</span><span class="su5hD">CRT_PATH</span><span class="sP7_E">}</span><span class="s_sjI">/fullchain.pem
</span></span></code></pre><p>通过逗号分隔 <code>DOMAIN</code> 中的多个域名，并循环拼接多个 <code>-d</code> 参数即可。</p><p>这么修改后，群晖就可以愉快的支持多个主域名的 SSL 证书啦，爽！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[回顾 2021，展望 2022]]></title>
        <id>/posts/2022/review-2021-and-look-forward-to-2022</id>
        <link href="https://hadb.me/posts/2022/review-2021-and-look-forward-to-2022"/>
        <updated>2022-01-19T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2022/20220119.review-2021-and-look-forward-to-2022/cover.jpg" alt="封面" /><blockquote><p>2022 年 01 月 07 日 ~ 2022 年 01 月 19 日</p></blockquote><p>又到一年回顾时，慵懒的周五下午，从柜子中翻出已经落灰的 QC35-II，打开 QQ 音乐，听着「重温 50 首不朽的经典英文老歌」，换了下桌面背景以便清晰显示 QQ 音乐的歌词，打开 Typora，完成各项准备工作。</p><p>回顾 2021 年，大致有这些大事：</p><ul><li>换了工作，从红星来到喜马，回到互联网公司，进入 WLB 的生活</li><li>提前还完了房贷的商业贷款部分，公积金贷款部分每月公积金还完之后还有盈余</li><li>上线了「<a href="https://starcraft.zone" rel="nofollow">星际战区</a>」，作为自己的独立项目</li><li>虚拟货币回了一些本，并彻底退出了</li><li>猿奋的 20 万高新补贴到账</li></ul><p>2021 年初定下的目标完成情况：</p><ul className="contains-task-list"><li className="task-list-item"><input checked disabled type="checkbox"></input> 一年不安装抖音、知乎 APP</li><li className="task-list-item"><input disabled type="checkbox"></input> 发表 12 篇博客</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 小工具集扩充 5 个小工具</li><li className="task-list-item"><input disabled type="checkbox"></input> 椭圆机累计健身 50 小时</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 平均睡眠时间达到 6 小时</li></ul><p>坚持一年没安装抖音和知乎 APP，这个目标原先的目的是为了减少在这些 APP 上所花的时间，这个虽然达成了，但是微信视频号成了抖音的替代品，知乎通过微信小程序也能继续使用。只能说确实有效减少了些使用时间。2021 年确实没怎么写博客，这点确实需要反思一下，这一年在技术积累上确实也比较少。椭圆机健身不说了，健身的 flag 每年都完成不了。睡眠时间达到 6 小时了，但这块后面也不再需要定目标了，现在睡眠还算规律，很少熬到两三点之后了。</p><p>2022 年，需要定一些有挑战性并且更有意义的目标。一个是提前还了房贷后，开始逐渐有一些资金可以用来理财了，需要对理财定一个目标，通过收益率来定可能更合理一些，考虑到目前分散在多个平台的理财统计起来会比较麻烦，所以对比了几个平台之后，决定之后新的理财都统一通过蚂蚁财富来操作，其他平台的理财也会逐渐迁移到蚂蚁财富里。最后通过蚂蚁财富 APP 统计的年收益率来计算。第一年先定一个 6% 收益率的小目标。还是以稳健为主。算了一下，每月定投 2 万，假设收益率达到 7%，20 年后总资产可以达到 1000 万。朝这个长期目标努力。达到 1000 万退休。为此最近专门写了个小工具：<a href="https://tools.yuanfen.net/financial-freedom" rel="nofollow">财富自由计算器</a></p><p>星际战区上线后也没有怎么运营，一些之前想做的功能也一直拖更没有完成，2022 年计划要把录像分析、小程序发电、会员体系做好。定个小目标，小程序累计用户数超过 1000。</p><p>动视暴雪要被微软收购了，对星际 2 的未来又多了一丝希望。一直想上宗师，一直没有达成这个目标。2022 年，争取打 200 场 1v1 天梯排位赛。</p><p>总结一下，2022 年的目标：</p><ul className="contains-task-list"><li className="task-list-item"><input disabled type="checkbox"></input> 蚂蚁财富年度收益率 > 6%</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 星际战区小程序累计用户数 > 1000</li><li className="task-list-item"><input checked disabled type="checkbox"></input> 星际 2 天梯 1v1 排位比赛场次 > 200</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 GitLab CI 自动部署 Ghost 主题]]></title>
        <id>/posts/2022/use-gitlab-to-deploy-ghost-theme-automatically</id>
        <link href="https://hadb.me/posts/2022/use-gitlab-to-deploy-ghost-theme-automatically"/>
        <updated>2022-05-24T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2022/20220524.use-gitlab-to-deploy-ghost-theme-automatically/cover.jpg" alt="封面" /><blockquote><p>居家隔离的第 N 天</p></blockquote><p>今天收到了 Ghost 5.0 发布的邮件，第一时间更新了下，发现主题里有些功能已经不兼容了，于是准备对主题做下更新。在看 Ghost Integrations 的时候发现有个 GitHub 的插件特别好用，支持通过 GitHub Actions 自动部署你的主题。但是我自己的项目用的都是 GitLab，找了一圈，没有官方的插件。于是尝试自己通过 GitLab CI 来实现。</p><p>大致看了下基于 GitHub Actions 自动部署的实现方式，通过官方提供的一个 <a href="https://github.com/TryGhost/action-deploy-theme/blob/main/index.js" rel="nofollow">TryGhost/action-deploy-theme</a> 的步骤，代码很简单，总共 40 行，我们来看下它做了什么：</p><pre><code><span class="line" line="1"><span class="sbsja">const</span><span class="s_hVV"> path</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">node:path</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="2"><span class="sbsja">const</span><span class="s_hVV"> core</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">@actions/core</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="3"><span class="sbsja">const</span><span class="s_hVV"> exec</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">@actions/exec</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="4"><span class="sbsja">const</span><span class="s_hVV"> GhostAdminApi</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">@tryghost/admin-api</span><span class="sjJ54">'</span><span class="su5hD">)</span><span class="sP7_E">;
</span></span><span class="line" line="5"><span emptyLinePlaceholder>
</span></span><span class="line" line="6"><span class="su5hD">(</span><span class="sbsja">async</span><span class="sbsja"> function</span><span class="sGLFI"> main</span><span class="sP7_E">()</span><span class="sP7_E"> {
</span></span><span class="line" line="7"><span class="sVHd0">  try</span><span class="sP7_E"> {
</span></span><span class="line" line="8"><span class="sbsja">    const</span><span class="s_hVV"> url</span><span class="smGrS"> =</span><span class="su5hD"> core</span><span class="sP7_E">.</span><span class="sGLFI">getInput</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">api-url</span><span class="sjJ54">'</span><span class="skxfh">)
</span></span><span class="line" line="9"><span class="sbsja">    const</span><span class="s_hVV"> api</span><span class="smGrS"> =</span><span class="smGrS"> new</span><span class="sGLFI"> GhostAdminApi</span><span class="skxfh">(</span><span class="sP7_E">{
</span></span><span class="line" line="10"><span class="su5hD">      url</span><span class="sP7_E">,
</span></span><span class="line" line="11"><span class="skxfh">      key</span><span class="sP7_E">:</span><span class="su5hD"> core</span><span class="sP7_E">.</span><span class="sGLFI">getInput</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">api-key</span><span class="sjJ54">'</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="12"><span class="skxfh">      version</span><span class="sP7_E">:</span><span class="sjJ54"> '</span><span class="s_sjI">canary</span><span class="sjJ54">'
</span></span><span class="line" line="13"><span class="sP7_E">    }</span><span class="skxfh">)
</span></span><span class="line" line="14"><span emptyLinePlaceholder>
</span></span><span class="line" line="15"><span class="sbsja">    const</span><span class="s_hVV"> basePath</span><span class="smGrS"> =</span><span class="su5hD"> process</span><span class="sP7_E">.</span><span class="su5hD">env</span><span class="sP7_E">.</span><span class="s_hVV">GITHUB_WORKSPACE
</span></span><span class="line" line="16"><span class="sbsja">    const</span><span class="s_hVV"> pkgPath</span><span class="smGrS"> =</span><span class="su5hD"> path</span><span class="sP7_E">.</span><span class="sGLFI">join</span><span class="skxfh">(</span><span class="su5hD">process</span><span class="sP7_E">.</span><span class="su5hD">env</span><span class="sP7_E">.</span><span class="s_hVV">GITHUB_WORKSPACE</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">package.json</span><span class="sjJ54">'</span><span class="skxfh">)
</span></span><span class="line" line="17"><span emptyLinePlaceholder>
</span></span><span class="line" line="18"><span class="sbsja">    let</span><span class="su5hD"> zipPath</span><span class="smGrS"> =</span><span class="su5hD"> core</span><span class="sP7_E">.</span><span class="sGLFI">getInput</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">file</span><span class="sjJ54">'</span><span class="skxfh">)
</span></span><span class="line" line="19"><span emptyLinePlaceholder>
</span></span><span class="line" line="20"><span class="sutJx">    // Zip file was not provided - zip everything up!
</span></span><span class="line" line="21"><span class="sVHd0">    if</span><span class="skxfh"> (</span><span class="smGrS">!</span><span class="su5hD">zipPath</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="22"><span class="sbsja">      const</span><span class="s_hVV"> themeName</span><span class="smGrS"> =</span><span class="su5hD"> core</span><span class="sP7_E">.</span><span class="sGLFI">getInput</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">theme-name</span><span class="sjJ54">'</span><span class="skxfh">) </span><span class="smGrS">||</span><span class="sGLFI"> require</span><span class="skxfh">(</span><span class="su5hD">pkgPath</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="su5hD">name
</span></span><span class="line" line="23"><span class="sbsja">      const</span><span class="s_hVV"> themeZip</span><span class="smGrS"> =</span><span class="sjJ54"> `${</span><span class="su5hD">themeName</span><span class="sjJ54">}</span><span class="s_sjI">.zip</span><span class="sjJ54">`
</span></span><span class="line" line="24"><span class="sbsja">      const</span><span class="s_hVV"> exclude</span><span class="smGrS"> =</span><span class="su5hD"> core</span><span class="sP7_E">.</span><span class="sGLFI">getInput</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">exclude</span><span class="sjJ54">'</span><span class="skxfh">) </span><span class="smGrS">||</span><span class="sjJ54"> ''
</span></span><span class="line" line="25"><span class="su5hD">      zipPath</span><span class="smGrS"> =</span><span class="su5hD"> themeZip
</span></span><span class="line" line="26"><span emptyLinePlaceholder>
</span></span><span class="line" line="27"><span class="sutJx">      // Create a zip
</span></span><span class="line" line="28"><span class="sVHd0">      await</span><span class="su5hD"> exec</span><span class="sP7_E">.</span><span class="sGLFI">exec</span><span class="skxfh">(</span><span class="sjJ54">`</span><span class="s_sjI">zip -r </span><span class="sjJ54">${</span><span class="su5hD">themeZip</span><span class="sjJ54">}</span><span class="sjJ54"> ${</span><span class="su5hD">core</span><span class="sjJ54">.</span><span class="sGLFI">getInput</span><span class="sfo-9">(</span><span class="sjJ54">'</span><span class="s_sjI">working-directory</span><span class="sjJ54">'</span><span class="sfo-9">) </span><span class="smGrS">||</span><span class="sjJ54"> '</span><span class="s_sjI">.</span><span class="sjJ54">'}</span><span class="s_sjI"> -x *.git* *.zip yarn* npm* node_modules* *routes.yaml *redirects.yaml *redirects.json </span><span class="sjJ54">${</span><span class="su5hD">exclude</span><span class="sjJ54">}`</span><span class="sP7_E">,</span><span class="skxfh"> []</span><span class="sP7_E">,</span><span class="sP7_E"> {</span><span class="skxfh"> cwd</span><span class="sP7_E">:</span><span class="su5hD"> basePath</span><span class="sP7_E"> }</span><span class="skxfh">)
</span></span><span class="line" line="29"><span class="sP7_E">    }
</span></span><span class="line" line="30"><span emptyLinePlaceholder>
</span></span><span class="line" line="31"><span class="su5hD">    zipPath</span><span class="smGrS"> =</span><span class="su5hD"> path</span><span class="sP7_E">.</span><span class="sGLFI">join</span><span class="skxfh">(</span><span class="su5hD">basePath</span><span class="sP7_E">,</span><span class="su5hD"> zipPath</span><span class="skxfh">)
</span></span><span class="line" line="32"><span emptyLinePlaceholder>
</span></span><span class="line" line="33"><span class="sutJx">    // Deploy it to the configured site
</span></span><span class="line" line="34"><span class="sVHd0">    await</span><span class="su5hD"> api</span><span class="sP7_E">.</span><span class="su5hD">themes</span><span class="sP7_E">.</span><span class="sGLFI">upload</span><span class="skxfh">(</span><span class="sP7_E">{</span><span class="skxfh"> file</span><span class="sP7_E">:</span><span class="su5hD"> zipPath</span><span class="sP7_E"> }</span><span class="skxfh">)
</span></span><span class="line" line="35"><span class="su5hD">    console</span><span class="sP7_E">.</span><span class="sGLFI">log</span><span class="skxfh">(</span><span class="sjJ54">`${</span><span class="su5hD">zipPath</span><span class="sjJ54">}</span><span class="s_sjI"> successfully uploaded.</span><span class="sjJ54">`</span><span class="skxfh">)
</span></span><span class="line" line="36"><span class="sP7_E">  }
</span></span><span class="line" line="37"><span class="sVHd0">  catch</span><span class="skxfh"> (</span><span class="su5hD">err</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="38"><span class="su5hD">    console</span><span class="sP7_E">.</span><span class="sGLFI">error</span><span class="skxfh">(</span><span class="su5hD">err</span><span class="skxfh">)
</span></span><span class="line" line="39"><span class="su5hD">    process</span><span class="sP7_E">.</span><span class="sGLFI">exit</span><span class="skxfh">(</span><span class="srdBf">1</span><span class="skxfh">)
</span></span><span class="line" line="40"><span class="sP7_E">  }
</span></span><span class="line" line="41"><span class="sP7_E">}</span><span class="su5hD">())
</span></span></code></pre><p>把主题打包成 zip 包，然后提供 Ghost 上创建的 <code>Admin API Key</code> 和 <code>API URL</code>，通过 API 去上传，那么我们应该也可以自己去实现。</p><p>首先，我们也需要去 Ghost 后台创建一个自定义的 Integration，比如取名叫 GitLab CI，目的是为了获得 <code>Admin API key</code> 和 <code>API URL</code>，后面在 GitLab CI 中需要用到。</p><figure><img src="https://hadb.me/static/posts/2022/20220524.use-gitlab-to-deploy-ghost-theme-automatically/01.png"></img></figure><p>下一步，去 GitLab CI 中，把这两个内容配置成变量，取名 <code>GHOST_ADMIN_API_KEY</code> 和 <code>GHOST_API_URL</code> 以便在 CI 脚本中使用。</p><figure><img src="https://hadb.me/static/posts/2022/20220524.use-gitlab-to-deploy-ghost-theme-automatically/02.png"></img></figure><p>在项目中添加 Ghost Admin API 库：</p><pre><code><span class="line" line="1"><span class="sbgvK">yarn</span><span class="s_sjI"> add</span><span class="s_sjI"> @tryghost/admin-api</span><span class="stzsN"> --dev
</span></span></code></pre><p>在 <code>gulpfile.js</code> 中插入部署的任务：</p><pre><code><span class="line" line="1"><span class="sbsja">const</span><span class="s_hVV"> GhostAdminApi</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">@tryghost/admin-api</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="2"><span class="sbsja">const</span><span class="sP7_E"> {</span><span class="s_hVV"> series</span><span class="sP7_E">,</span><span class="s_hVV"> src</span><span class="sP7_E">,</span><span class="s_hVV"> dest</span><span class="sP7_E"> }</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">gulp</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="3"><span class="sbsja">const</span><span class="s_hVV"> less</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">gulp-less</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="4"><span class="sbsja">const</span><span class="s_hVV"> zip</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">gulp-zip</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="5"><span class="sbsja">const</span><span class="s_hVV"> pump</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="su5hD">(</span><span class="sjJ54">'</span><span class="s_sjI">pump</span><span class="sjJ54">'</span><span class="su5hD">)
</span></span><span class="line" line="6"><span emptyLinePlaceholder>
</span></span><span class="line" line="7"><span class="sbsja">const</span><span class="sfCm-"> handleError</span><span class="smGrS"> =</span><span class="sP7_E"> (</span><span class="s99_P">done</span><span class="sP7_E">)</span><span class="sbsja"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="8"><span class="sVHd0">  return</span><span class="sbsja"> function</span><span class="sP7_E"> (</span><span class="s99_P">err</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="9"><span class="sVHd0">    if</span><span class="skxfh"> (</span><span class="su5hD">err</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="10"><span class="su5hD">      console</span><span class="sP7_E">.</span><span class="sGLFI">error</span><span class="skxfh">(</span><span class="su5hD">err</span><span class="skxfh">)
</span></span><span class="line" line="11"><span class="sP7_E">    }
</span></span><span class="line" line="12"><span class="sVHd0">    return</span><span class="sGLFI"> done</span><span class="skxfh">(</span><span class="su5hD">err</span><span class="skxfh">)
</span></span><span class="line" line="13"><span class="sP7_E">  }
</span></span><span class="line" line="14"><span class="sP7_E">}
</span></span><span class="line" line="15"><span emptyLinePlaceholder>
</span></span><span class="line" line="16"><span class="sbsja">function</span><span class="sGLFI"> css</span><span class="sP7_E">(</span><span class="s99_P">done</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="17"><span class="sGLFI">  pump</span><span class="skxfh">(
</span></span><span class="line" line="18"><span class="skxfh">    [
</span></span><span class="line" line="19"><span class="sGLFI">      src</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">./assets/css/*.less</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sP7_E"> {</span><span class="skxfh"> sourcemaps</span><span class="sP7_E">:</span><span class="syTEX"> true</span><span class="sP7_E"> }</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="20"><span class="sGLFI">      less</span><span class="skxfh">(</span><span class="sP7_E">{}</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="21"><span class="sGLFI">      dest</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">assets/css</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sP7_E"> {</span><span class="skxfh"> sourcemaps</span><span class="sP7_E">:</span><span class="sjJ54"> '</span><span class="s_sjI">./</span><span class="sjJ54">'</span><span class="sP7_E"> }</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="22"><span class="skxfh">    ]</span><span class="sP7_E">,
</span></span><span class="line" line="23"><span class="sGLFI">    handleError</span><span class="skxfh">(</span><span class="su5hD">done</span><span class="skxfh">)
</span></span><span class="line" line="24"><span class="skxfh">  )
</span></span><span class="line" line="25"><span class="sP7_E">}
</span></span><span class="line" line="26"><span emptyLinePlaceholder>
</span></span><span class="line" line="27"><span class="sbsja">function</span><span class="sGLFI"> zipper</span><span class="sP7_E">(</span><span class="s99_P">done</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="28"><span class="sbsja">  const</span><span class="s_hVV"> targetDir</span><span class="smGrS"> =</span><span class="sjJ54"> '</span><span class="s_sjI">dist/</span><span class="sjJ54">'
</span></span><span class="line" line="29"><span class="sbsja">  const</span><span class="s_hVV"> themeName</span><span class="smGrS"> =</span><span class="sGLFI"> require</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">./package.json</span><span class="sjJ54">'</span><span class="skxfh">)</span><span class="sP7_E">.</span><span class="su5hD">name
</span></span><span class="line" line="30"><span class="sbsja">  const</span><span class="s_hVV"> filename</span><span class="smGrS"> =</span><span class="sjJ54"> `${</span><span class="su5hD">themeName</span><span class="sjJ54">}</span><span class="s_sjI">.zip</span><span class="sjJ54">`
</span></span><span class="line" line="31"><span emptyLinePlaceholder>
</span></span><span class="line" line="32"><span class="sGLFI">  pump</span><span class="skxfh">(
</span></span><span class="line" line="33"><span class="skxfh">    [
</span></span><span class="line" line="34"><span class="sGLFI">      src</span><span class="skxfh">([</span><span class="sjJ54">'</span><span class="s_sjI">**</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">!node_modules</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">!node_modules/**</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">!dist</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="sjJ54"> '</span><span class="s_sjI">!dist/**</span><span class="sjJ54">'</span><span class="skxfh">])</span><span class="sP7_E">,
</span></span><span class="line" line="35"><span class="sGLFI">      zip</span><span class="skxfh">(</span><span class="su5hD">filename</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="36"><span class="sGLFI">      dest</span><span class="skxfh">(</span><span class="su5hD">targetDir</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="37"><span class="skxfh">    ]</span><span class="sP7_E">,
</span></span><span class="line" line="38"><span class="sGLFI">    handleError</span><span class="skxfh">(</span><span class="su5hD">done</span><span class="skxfh">)
</span></span><span class="line" line="39"><span class="skxfh">  )
</span></span><span class="line" line="40"><span class="sP7_E">}
</span></span><span class="line" line="41"><span emptyLinePlaceholder>
</span></span><span class="line" line="42"><span class="sbsja">async</span><span class="sbsja"> function</span><span class="sGLFI"> deploy</span><span class="sP7_E">(</span><span class="s99_P">done</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="43"><span class="sVHd0">  try</span><span class="sP7_E"> {
</span></span><span class="line" line="44"><span class="sbsja">    const</span><span class="s_hVV"> zipFile</span><span class="smGrS"> =</span><span class="sjJ54"> `</span><span class="s_sjI">dist/</span><span class="sjJ54">${</span><span class="sGLFI">require</span><span class="sfo-9">(</span><span class="sjJ54">'</span><span class="s_sjI">./package.json</span><span class="sjJ54">'</span><span class="sfo-9">)</span><span class="sjJ54">.</span><span class="su5hD">name</span><span class="sjJ54">}</span><span class="s_sjI">.zip</span><span class="sjJ54">`
</span></span><span class="line" line="45"><span class="sbsja">    const</span><span class="s_hVV"> api</span><span class="smGrS"> =</span><span class="smGrS"> new</span><span class="sGLFI"> GhostAdminApi</span><span class="skxfh">(</span><span class="sP7_E">{
</span></span><span class="line" line="46"><span class="skxfh">      url</span><span class="sP7_E">:</span><span class="su5hD"> process</span><span class="sP7_E">.</span><span class="su5hD">env</span><span class="sP7_E">.</span><span class="s_hVV">GHOST_API_URL</span><span class="sP7_E">,
</span></span><span class="line" line="47"><span class="skxfh">      key</span><span class="sP7_E">:</span><span class="su5hD"> process</span><span class="sP7_E">.</span><span class="su5hD">env</span><span class="sP7_E">.</span><span class="s_hVV">GHOST_ADMIN_API_KEY</span><span class="sP7_E">,
</span></span><span class="line" line="48"><span class="skxfh">      version</span><span class="sP7_E">:</span><span class="sjJ54"> `</span><span class="s_sjI">v</span><span class="sjJ54">${</span><span class="sGLFI">require</span><span class="sfo-9">(</span><span class="sjJ54">'</span><span class="s_sjI">./package.json</span><span class="sjJ54">'</span><span class="sfo-9">)</span><span class="sjJ54">.</span><span class="su5hD">version</span><span class="sjJ54">}`</span><span class="sP7_E">,
</span></span><span class="line" line="49"><span class="sP7_E">    }</span><span class="skxfh">)
</span></span><span class="line" line="50"><span emptyLinePlaceholder>
</span></span><span class="line" line="51"><span class="sVHd0">    await</span><span class="su5hD"> api</span><span class="sP7_E">.</span><span class="su5hD">themes</span><span class="sP7_E">.</span><span class="sGLFI">upload</span><span class="skxfh">(</span><span class="sP7_E">{</span><span class="skxfh"> file</span><span class="sP7_E">:</span><span class="su5hD"> zipFile</span><span class="sP7_E"> }</span><span class="skxfh">)
</span></span><span class="line" line="52"><span class="su5hD">    console</span><span class="sP7_E">.</span><span class="sGLFI">log</span><span class="skxfh">(</span><span class="sjJ54">`${</span><span class="su5hD">zipFile</span><span class="sjJ54">}</span><span class="s_sjI"> successfully uploaded.</span><span class="sjJ54">`</span><span class="skxfh">)
</span></span><span class="line" line="53"><span class="sGLFI">    done</span><span class="skxfh">()
</span></span><span class="line" line="54"><span class="sP7_E">  }
</span></span><span class="line" line="55"><span class="sVHd0">  catch</span><span class="skxfh"> (</span><span class="su5hD">err</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="56"><span class="su5hD">    console</span><span class="sP7_E">.</span><span class="sGLFI">error</span><span class="skxfh">(</span><span class="su5hD">err</span><span class="skxfh">)
</span></span><span class="line" line="57"><span class="sGLFI">    done</span><span class="skxfh">(</span><span class="su5hD">err</span><span class="skxfh">)
</span></span><span class="line" line="58"><span class="sP7_E">  }
</span></span><span class="line" line="59"><span class="sP7_E">}
</span></span><span class="line" line="60"><span emptyLinePlaceholder>
</span></span><span class="line" line="61"><span class="sbsja">const</span><span class="s_hVV"> build</span><span class="smGrS"> =</span><span class="sGLFI"> series</span><span class="su5hD">(css)
</span></span><span class="line" line="62"><span emptyLinePlaceholder>
</span></span><span class="line" line="63"><span class="s39Yj">exports</span><span class="sP7_E">.</span><span class="su5hD">build </span><span class="smGrS">=</span><span class="su5hD"> build
</span></span><span class="line" line="64"><span class="s39Yj">exports</span><span class="sP7_E">.</span><span class="su5hD">zip </span><span class="smGrS">=</span><span class="sGLFI"> series</span><span class="su5hD">(build</span><span class="sP7_E">,</span><span class="su5hD"> zipper)
</span></span><span class="line" line="65"><span class="s39Yj">exports</span><span class="sP7_E">.</span><span class="su5hD">deploy </span><span class="smGrS">=</span><span class="su5hD"> deploy
</span></span><span class="line" line="66"><span class="s39Yj">exports</span><span class="sP7_E">.</span><span class="su5hD">default </span><span class="smGrS">=</span><span class="su5hD"> build
</span></span></code></pre><p>在 <code>package.json</code> 中插入脚本：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="sutJx">  /* ... */
</span></span><span class="line" line="3"><span class="s39Yj">  "</span><span class="sseR_">scripts</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="4"><span class="s39Yj">    "</span><span class="sZMiF">build</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">gulp build</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="5"><span class="s39Yj">    "</span><span class="sZMiF">zip</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">gulp zip</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="6"><span class="s39Yj">    "</span><span class="sZMiF">deploy</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">gulp deploy</span><span class="sjJ54">"
</span></span><span class="line" line="7"><span class="sP7_E">  }
</span></span><span class="line" line="8"><span class="sutJx">  /* ... */
</span></span><span class="line" line="9"><span class="sP7_E">}
</span></span></code></pre><p>添加 <code>.gitlab-ci.yml</code> 文件：</p><pre><code><span class="line" line="1"><span class="sQzsp">image</span><span class="sP7_E">:</span><span class="s_sjI"> node:14-slim</span><span class="sutJx"> # 注意：不要用 alpine 的镜像，上传至 https 站点会有问题
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sQzsp">stages</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sP7_E">  -</span><span class="s_sjI"> deploy
</span></span><span class="line" line="5"><span emptyLinePlaceholder>
</span></span><span class="line" line="6"><span class="sQzsp">deploy</span><span class="sP7_E">:
</span></span><span class="line" line="7"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> deploy
</span></span><span class="line" line="8"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sP7_E">    -</span><span class="s_sjI"> yarn install
</span></span><span class="line" line="10"><span class="sP7_E">    -</span><span class="s_sjI"> yarn zip
</span></span><span class="line" line="11"><span class="sP7_E">    -</span><span class="s_sjI"> yarn deploy
</span></span><span class="line" line="12"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sP7_E">    -</span><span class="s_sjI"> tags
</span></span><span class="line" line="14"><span class="sQzsp">  cache</span><span class="sP7_E">:
</span></span><span class="line" line="15"><span class="sQzsp">    paths</span><span class="sP7_E">:
</span></span><span class="line" line="16"><span class="sP7_E">      -</span><span class="s_sjI"> node_modules/
</span></span></code></pre><p>注意，为了避免每次提交代码都部署，<code>deploy</code> 任务限制了只有打了 <code>tag</code> 的 <code>commit</code> 才会触发。</p><p>好了，更新代码，打个 tag 就会自动打包上传至 Ghost 后台了！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于人生的一些思考]]></title>
        <id>/posts/2022/thoughts-about-life</id>
        <link href="https://hadb.me/posts/2022/thoughts-about-life"/>
        <updated>2022-06-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近，老婆孩子回老家了，主要还是因为上海的疫情，幼儿园也不上学，各种兴趣班也都只能线上，索性直接回老家过暑假了，还能搞点兴趣班上一上。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2022/20220620.thoughts-about-life/cover.jpg" alt="封面" /><p>最近，老婆孩子回老家了，主要还是因为上海的疫情，幼儿园也不上学，各种兴趣班也都只能线上，索性直接回老家过暑假了，还能搞点兴趣班上一上。</p><p>他们回去之后，我重新体验到了难得的单身生活，不得不说，还是有一些变化的。</p><p>虽然晚上或者周末的时候，儿子还是偶尔会用他的 iPad 打个视频过来，让我陪他玩王者荣耀，但是频率比在身边的时候要低多了。我得以有大把的个人时间。但是，当我发现我有了大把的属于自己的时间之后，突然又不知道该做啥了。</p><p>以前，闲暇时间总想着去打打游戏，结果现在有大把的时间的时候，反而对游戏提不起太多的兴趣。一个原因是随着生活条件的提升，各种体验的提升，导致多巴胺分泌的阈值本身就变高了；另一个原因是之前老婆孩子在的时候，只能忙里偷闲玩点游戏，这个时候多巴胺分泌的阈值是被压低的，所以很容易能玩会游戏就获得到快乐。</p><p>有时候总会想，人生的意义到底是什么。在这个地球上生活过的人给出了各种各样不同的答案，有些人的答案会流传下来，而大多数人的答案随着自己的死亡，都永远消失在历史的长河中。成为名人很难，你不出名，你的答案，你的文字，你的故事，死后的几十年里，都会消失殆尽，即便不消失，也会淹没在茫茫的数据之中。现在互联网每天产生的海量的数据量，会淹没一切不起眼的资料。而且，你的这些思考，你对人生的答案，有无数的人早已回答过，没有什么特别的，对后人的价值也没有那么大。在一个不断翻新的摩天大楼里，一颗螺丝钉是没有名字的。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ghost 5.0 升级到 5.42]]></title>
        <id>/posts/2023/upgrade-ghost-5-0-to-5-42</id>
        <link href="https://hadb.me/posts/2023/upgrade-ghost-5-0-to-5-42"/>
        <updated>2023-04-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[距离上次升级 Ghost 已经过去快 1 年了，上次是 Ghost 5.0 刚发布的时候升级的，这次直接把容器镜像版本改为最新的 5.42 时，报了个错：]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230408.upgrade-ghost-5-0-to-5-42/cover.jpg" alt="封面" /><p>距离上次升级 Ghost 已经过去快 1 年了，上次是 Ghost 5.0 刚发布的时候升级的，这次直接把容器镜像版本改为最新的 5.42 时，报了个错：</p><pre><code><span class="line" line="1"><span class="s_sjI">ERROR</span><span class="su5hD"> connect ECONNREFUSED </span><span class="s39Yj">127</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">1</span><span class="su5hD">:</span><span class="s39Yj">3306
</span></span><span class="line" line="2"><span class="su5hD">connect ECONNREFUSED </span><span class="s39Yj">127</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">1</span><span class="su5hD">:</span><span class="s39Yj">3306
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="s_sjI">"Unknown database error"
</span></span><span class="line" line="5"><span emptyLinePlaceholder>
</span></span><span class="line" line="6"><span class="s_sjI">Error</span><span class="su5hD"> ID:
</span></span><span class="line" line="7"><span class="s39Yj">500
</span></span><span class="line" line="8"><span emptyLinePlaceholder>
</span></span><span class="line" line="9"><span class="s_sjI">Error</span><span class="su5hD"> Code:
</span></span><span class="line" line="10"><span class="su5hD">ECONNREFUSED
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="s_sjI">Error</span><span class="su5hD">: connect ECONNREFUSED </span><span class="s39Yj">127</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">0</span><span class="su5hD">.</span><span class="s39Yj">1</span><span class="su5hD">:</span><span class="s39Yj">3306
</span></span><span class="line" line="13"><span class="s_sjI">at /var/lib/ghost/versions/5.42.0/node_modules/knex-migrator/lib/database.js:57:19
</span></span><span class="line" line="14"><span class="s_sjI">at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1278:16)
</span></span></code></pre><p>重试了几次都不行，在 Ghost 的文档中找了半天也没发现从 5.0 升级到 5.42 中间有什么 breaking changes。最后从 Ghost 的 Docker 镜像维护仓库找到了相关的 Issue：<a href="https://github.com/docker-library/ghost/pull/323" rel="nofollow">#323</a></p><p>大致原因主要是这样，Ghost 5.0 之后其实是有个 breaking change，就是原先数据库是支持 SQLite3 和 MySQL 5 的，在 5.0 之后，数据库只支持 MySQL 8 了，但是 SQLite3 在开发环境还是支持的。对应的 Docker 镜像，在 5.9 之前，都还是可以继续用之前的 SQLite3 的，但是在 5.9 这个版本中，Docker 镜像将默认数据库改为了 MySQL 8，这就导致从低版本升到高于 5.9 版本的镜像之后，数据库会直接找不到。在这个 PR 中，其实给出了一个临时的解决方案，就是在环境变量中加两个变量，就可以继续使用 SQLite3，这两个变量是：</p><pre><code><span class="line" line="1"><span class="sQzsp">database__client</span><span class="sP7_E">:</span><span class="s_sjI"> sqlite3
</span></span><span class="line" line="2"><span class="sQzsp">database__connection__filename</span><span class="sP7_E">:</span><span class="s_sjI"> /var/lib/ghost/content/data/ghost.db
</span></span></code></pre><p>添加完之后，容器可以正常升级了。</p><p>但，既然官方已经将数据库支持重点改为了 MySQL 8，不怕麻烦的话，也可以升级下数据库。原本准备写个 MySQL 8 的升级教程的，想想不折腾了，我的博客用轻量的 SQLite3 就够了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[GitLab CI 配置自动化打包上传 Python 库]]></title>
        <id>/posts/2023/gitlab-ci-auto-deploy-python-lib</id>
        <link href="https://hadb.me/posts/2023/gitlab-ci-auto-deploy-python-lib"/>
        <updated>2023-04-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[自己之前有些 python 脚本类的项目，会用到一些通用的能力，如读取配置、打日志等，每次都 copy 一份 utils 目录有些不够优雅，于是撸了一个公共库，方便自己使用。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230423.gitlab-ci-auto-deploy-python-lib/cover.jpg" alt="封面" /><p>自己之前有些 python 脚本类的项目，会用到一些通用的能力，如读取配置、打日志等，每次都 copy 一份 utils 目录有些不够优雅，于是撸了一个公共库，方便自己使用。</p><p>为了能配合 GitLab CI，<code>setup.py</code> 需要做一些小调整，版本号不需要手动输入了，直接读取 <code>$CI_COMMIT_TAG</code>，代码如下：</p><pre><code><span class="line" line="1"><span class="sVHd0">import</span><span class="su5hD"> os
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sVHd0">import</span><span class="su5hD"> setuptools
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sVHd0">with</span><span class="sptTA"> open</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">README.md</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">r</span><span class="sjJ54">"</span><span class="sP7_E">)</span><span class="sVHd0"> as</span><span class="su5hD"> fh</span><span class="sP7_E">:
</span></span><span class="line" line="6"><span class="su5hD">    long_description </span><span class="smGrS">=</span><span class="su5hD"> fh</span><span class="sP7_E">.</span><span class="slqww">read</span><span class="sP7_E">()
</span></span><span class="line" line="7"><span emptyLinePlaceholder>
</span></span><span class="line" line="8"><span class="su5hD">setuptools</span><span class="sP7_E">.</span><span class="slqww">setup</span><span class="sP7_E">(
</span></span><span class="line" line="9"><span class="s99_P">    name</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">yuanfen</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="10"><span class="s99_P">    version</span><span class="smGrS">=</span><span class="slqww">os</span><span class="sP7_E">.</span><span class="skxfh">environ</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">CI_COMMIT_TAG</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">0.0.0</span><span class="sjJ54">"</span><span class="sP7_E">),
</span></span><span class="line" line="11"><span class="s99_P">    author</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">Bean</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="12"><span class="s99_P">    author_email</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">bean@yuanfen.net</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="13"><span class="s99_P">    description</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">Yuanfen Python Library</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="14"><span class="s99_P">    long_description</span><span class="smGrS">=</span><span class="slqww">long_description</span><span class="sP7_E">,
</span></span><span class="line" line="15"><span class="s99_P">    long_description_content_type</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">text/markdown</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="16"><span class="s99_P">    url</span><span class="smGrS">=</span><span class="sjJ54">""</span><span class="sP7_E">,
</span></span><span class="line" line="17"><span class="s99_P">    install_requires</span><span class="smGrS">=</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">pyyaml</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">watchdog</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="18"><span class="s99_P">    packages</span><span class="smGrS">=</span><span class="slqww">setuptools</span><span class="sP7_E">.</span><span class="slqww">find_packages</span><span class="sP7_E">(),
</span></span><span class="line" line="19"><span class="s99_P">    classifiers</span><span class="smGrS">=</span><span class="sP7_E">[
</span></span><span class="line" line="20"><span class="sjJ54">        "</span><span class="s_sjI">Programming Language :: Python :: 3</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="21"><span class="sjJ54">        "</span><span class="s_sjI">License :: OSI Approved :: MIT License</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="22"><span class="sjJ54">        "</span><span class="s_sjI">Operating System :: OS Independent</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="23"><span class="sP7_E">    ],
</span></span><span class="line" line="24"><span class="sP7_E">)
</span></span></code></pre><p><code>.gitlab-ci.yml</code> 代码如下：</p><pre><code><span class="line" line="1"><span class="sQzsp">image</span><span class="sP7_E">:</span><span class="s_sjI"> python:3
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span class="sQzsp">stages</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sP7_E">  -</span><span class="s_sjI"> deploy
</span></span><span class="line" line="5"><span emptyLinePlaceholder>
</span></span><span class="line" line="6"><span class="sQzsp">deploy</span><span class="sP7_E">:
</span></span><span class="line" line="7"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> deploy
</span></span><span class="line" line="8"><span class="sQzsp">  variables</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sQzsp">    TWINE_USERNAME</span><span class="sP7_E">:</span><span class="s_sjI"> $TWINE_USERNAME
</span></span><span class="line" line="10"><span class="sQzsp">    TWINE_PASSWORD</span><span class="sP7_E">:</span><span class="s_sjI"> $TWINE_PASSWORD
</span></span><span class="line" line="11"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="12"><span class="sP7_E">    -</span><span class="s_sjI"> python setup.py sdist bdist_wheel
</span></span><span class="line" line="13"><span class="sP7_E">    -</span><span class="s_sjI"> pip install twine -i https://pypi.tuna.tsinghua.edu.cn/simple
</span></span><span class="line" line="14"><span class="sP7_E">    -</span><span class="s_sjI"> twine upload dist/*
</span></span><span class="line" line="15"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="16"><span class="sP7_E">    -</span><span class="s_sjI"> tags
</span></span></code></pre><p>其中，需要在 GitLab 项目 <code>Setting -> CI/CD -> Variables</code> 中配置 <code>TWINE_USERNAME</code> 和 <code>TWINE_PASSWORD</code>，这个是你在 <code>pypi.org</code> 上的用户名和密码。</p><p>另外需要注意的是，仓库的目录结构，<code>setup.py</code> 中指定的 <code>name</code> 是包名，而 <code>__init__.py</code> 所在的文件夹名是实际 <code>import</code> 的导入名。上传包时，PyPI 会检查报名是否重复，但不会检查导入名，所以如果定义了一个容易导致重复的导入名的话，就有可能有冲突。所以一般情况下该文件夹尽量是与包名统一，这样不容易有冲突。</p><p>以下是 ChatGPT 给出的目录结构的建议：</p><blockquote><p>当你创建一个 Python 库时，通常需要遵循一些标准的目录结构和文件命名规范。下面是一个常见的建议目录结构，包括了一些常见文件和目录：</p><pre><code>your_library/
   your_library/
       __init__.py
       module1.py
       module2.py
       ...
   examples/
       example1.py
       example2.py
       ...
   tests/
       test_module1.py
       test_module2.py
       ...
   README.md
   LICENSE
   setup.py
   requirements.txt
   .gitignore

</code></pre><p>在这个目录结构中，<code>your_library</code> 是你的库的名称，它应该与 <code>setup.py</code> 文件中定义的名称相同。<code>your_library</code> 目录包含了你的库的所有源代码和模块文件，以及 <code>__init__.py</code> 文件，用于将 <code>your_library</code> 目录转换为 Python 包。</p><p><code>examples</code> 目录包含了一些示例代码，用于演示如何使用你的库。这些示例代码可以帮助用户更快地了解和使用你的库。</p><p><code>tests</code> 目录包含了单元测试和集成测试代码，用于确保你的库的功能和质量。在这个目录中，你可以为每个模块编写一个或多个测试文件，并使用测试框架运行这些测试。</p><p><code>README.md</code> 文件是你的库的说明文档，用于介绍你的库的功能、用法和示例。<code>LICENSE</code> 文件包含了你的库的许可证信息，用于明确你的库的使用和分发条件。</p><p><code>setup.py</code> 文件是你的库的安装脚本，用于将你的库安装到 Python 环境中。在这个文件中，你需要指定库的名称、版本、作者、描述、依赖等信息。</p><p><code>requirements.txt</code> 文件包含了你的库依赖的其他 Python 包的名称和版本号。在使用 pip 安装你的库时，pip 将自动安装这些依赖包。</p><p><code>.gitignore</code> 文件是 Git 版本控制系统的忽略文件，用于指定哪些文件或目录不需要被 Git 跟踪和提交。</p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[TimedRotatingFileHandler 不会自动清除旧日志的问题]]></title>
        <id>/posts/2023/timedrotatingfilehandler-backupcount-problem</id>
        <link href="https://hadb.me/posts/2023/timedrotatingfilehandler-backupcount-problem"/>
        <updated>2023-04-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[话不多说，直接贴代码：]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230423.timedrotatingfilehandler-backupcount-problem/cover.jpg" alt="封面" /><p>话不多说，直接贴代码：</p><pre><code><span class="line" line="1"><span class="su5hD">file_handler </span><span class="smGrS">=</span><span class="slqww"> TimedRotatingFileHandler</span><span class="sP7_E">(
</span></span><span class="line" line="2"><span class="sjJ54">    "</span><span class="s_sjI">logs/log</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="3"><span class="s99_P">    when</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">midnight</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="4"><span class="s99_P">    backupCount</span><span class="smGrS">=</span><span class="srdBf">365</span><span class="sP7_E">,
</span></span><span class="line" line="5"><span class="s99_P">    encoding</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">utf-8</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="6"><span class="sP7_E">)
</span></span><span class="line" line="7"><span class="su5hD">file_handler</span><span class="sP7_E">.</span><span class="skxfh">suffix</span><span class="smGrS"> =</span><span class="sjJ54"> "</span><span class="s_sjI">%Y%m</span><span class="srdBf">%d</span><span class="s_sjI">.log</span><span class="sjJ54">"
</span></span><span class="line" line="8"><span class="su5hD">file_handler</span><span class="sP7_E">.</span><span class="slqww">setFormatter</span><span class="sP7_E">(</span><span class="slqww">log_formatter</span><span class="sP7_E">)
</span></span></code></pre><p>这是我几年前写的一段写日志文件的代码，前几天发现并没有按照预期只保留 365 个日志文件。研究了一下，发现了问题所在。<code>TimedRotatingFileHandler</code> 中对于 <code>midnight</code> 的操作是这样的：</p><pre><code><span class="line" line="1"><span class="sVHd0">elif</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">when</span><span class="smGrS"> ==</span><span class="sjJ54"> '</span><span class="s_sjI">D</span><span class="sjJ54">'</span><span class="smGrS"> or</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">when</span><span class="smGrS"> ==</span><span class="sjJ54"> '</span><span class="s_sjI">MIDNIGHT</span><span class="sjJ54">'</span><span class="sP7_E">:
</span></span><span class="line" line="2"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">interval</span><span class="smGrS"> =</span><span class="srdBf"> 60</span><span class="smGrS"> *</span><span class="srdBf"> 60</span><span class="smGrS"> *</span><span class="srdBf"> 24</span><span class="sutJx"> # one day
</span></span><span class="line" line="3"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">suffix</span><span class="smGrS"> =</span><span class="sjJ54"> "</span><span class="s_sjI">%Y-%m-</span><span class="srdBf">%d</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">extMatch</span><span class="smGrS"> =</span><span class="sbsja"> r</span><span class="sjJ54">"</span><span class="stzsN">^\d</span><span class="smGrS">{4}</span><span class="sQRbd">-</span><span class="stzsN">\d</span><span class="smGrS">{2}</span><span class="sQRbd">-</span><span class="stzsN">\d</span><span class="smGrS">{2}</span><span class="s39Yj">(</span><span class="sjYin">\.</span><span class="stzsN">\w</span><span class="smGrS">+</span><span class="s39Yj">)</span><span class="smGrS">?</span><span class="stzsN">$</span><span class="sjJ54">"
</span></span><span class="line" line="5"><span class="sutJx"># ... other code
</span></span><span class="line" line="6"><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">extMatch</span><span class="smGrS"> =</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">compile</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">extMatch</span><span class="sP7_E">,</span><span class="slqww"> re</span><span class="sP7_E">.</span><span class="swQdS">ASCII</span><span class="sP7_E">)
</span></span></code></pre><p>可以看到，其默认的 suffix 是 <code>%Y-%m-%d</code>，我之所以要加 <code>.log</code> 后缀，是因为如果不带后缀的话，群晖的文本编辑器默认不能直接打开这个文件，不太方便看日志。</p><p>它默认的匹配正则是 <code>r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"</code>，当我把 suffix 改成 <code>%Y%m%d.log</code> 之后，它就无法匹配到旧日志的数量了，所以保留日志个数的功能会失效。有两种办法解决：</p><p>一是加一行：</p><pre><code><span class="line" line="1"><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">extMatch</span><span class="smGrS"> =</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">compile</span><span class="sP7_E">(</span><span class="sbsja">r</span><span class="sjJ54">"</span><span class="stzsN">^\d</span><span class="smGrS">{4}</span><span class="stzsN">\d</span><span class="smGrS">{2}</span><span class="stzsN">\d</span><span class="smGrS">{2}</span><span class="sjYin">\.</span><span class="sQRbd">log</span><span class="stzsN">$</span><span class="sjJ54">"</span><span class="sP7_E">)
</span></span></code></pre><p>另一种更简单：</p><p>把 <code>suffix</code> 改成 <code>%Y-%m-%d.log</code> 即可，因为默认的 <code>extMatch</code> 是可以匹配到后缀名的。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[上海居转户落户完整经历]]></title>
        <id>/posts/2023/shanghai-luohu</id>
        <link href="https://hadb.me/posts/2023/shanghai-luohu"/>
        <updated>2023-05-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[万里长征，终于到了最后一步，是时候把历时 3 年多的上海落户的经历记录下来了，帮助后人。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230508.shanghai-luohu/cover.jpg" alt="封面" /><p>万里长征，终于到了最后一步，是时候把历时 3 年多的上海落户的经历记录下来了，帮助后人。</p><p>有史可证最早开始准备落户这件事情，是在 2020 年 7 月 7 日，当时所在 HX 公司的一个同事居转户成功了，找他打听了解了一下，才发现上海落户这件事情并不是那么遥不可及。5 年 3 倍这个渠道，对于我们计算机行业的人来说，还是很容易实现的。</p><p>由于早些年加入创业公司以及后来自己创业时，都是按很低的标准交的社保，实际达到 3 倍社保标准是从 2019 年 4 月开始。5 年 3 倍渠道要求近 4 年累计有 36 个月达到 3 倍社保基数，也就是到 2022 年 3 月之后，才能满足落户要求。小孩 2023 年报小学，所以当时定的计划是 2023 年完成上海落户，考虑到 2022 年 3 月就可以满足要求，留了这么多 buffer，应该可以赶得上。</p><p>理想是美好的，现实是残酷的，中间发生了不少插曲，回过头来看，最终只差几天，刚好错过小学入学信息登记的时间点。</p><p>当时为了达成 2023 年上海落户的目标，我设计了 3 个计划：</p><ul><li>Plan A，正常居转户</li><li>Plan B，自己之前创业的公司还一直存续，可以申请高新科技企业，作为 B 计划，走人才引进的方式</li><li>Plan C，当时 HX 公司每年区里有些奖励名额，可以落户，当时我也算 HX 公司 N-1 的管理者，有一定希望走这个渠道</li></ul><p>Plan A，当时在网上看到一些消息，有潜规则，比如即便 5 年内近 36 个月达到 3 倍社保基数了，还需要其余两年至少要达到 1 倍社保基数，如果是这样，我就得再往后延了；另外还看到说要拿到每个公司的离职证明，早些年都没在意，很多离职证明都不在了。</p><p>Plan B，申请高新需要时间和成本，并且通过这个方式得脱产 2 年，挂在自己公司上交社保，只能再次出去创业才行。</p><p>Plan C，希望不大。</p><p>即便如此，我还是齐头并进，3 个 Plan 一起推进。</p><ul><li>2020 年 11 月，我自己公司的高新企业认证公示，2021 年 3 月，拿到了高新企业证书，有效期至 2023 年 11 月。Plan B 高新企业的硬性条件已满足。软著+审计+高新认证+补贴分成，整套下来差不多花了 10 万，不过有政府补贴，整体还是赚的。</li><li>2021 年 4 月，由于职业规划原因，离开了 HX 公司，去了 XM 公司。Plan C 宣告失效。</li><li>由于高新企业认证有效期 3 年，也就是最迟得 2021 年 10 月份左右要转到自己公司交社保，才能在有效期结束之前，达到 2 年的时间期限。但该方案脱产确实成本太高，并且没有好的项目值得出去创业，而且近几年的创业环境也非常差，因此到 2021 年 10 月份之后，Plan B 也宣告失败。</li></ul><p>至此，只剩下居转户一条路了。</p><p>为什么我要制定 Plan B 和 Plan C，除了前面说的，听说居转户有一些潜规则，以及离职证明的卡点外，我当时创业期间还有几个月社保是代缴的，为什么要代缴呢，因为当时是外地的一家国资背景的公司，计划在上海开分公司，但上海的公司还没设立，因此就通过代缴。但是坑的地方在于，代缴的公司只缴了社保，没缴个税；雪上加霜的是，我自己的公司由于我是法人，又一直在做 0 申报。这就导致我的个税记录很混乱，既有两家公司同时申报的情况，又有几个月是个税和社保缴纳单位以及基数都对不上的情况。当时也没有好的渠道去打听和了解，这些情况到底能不能办居转户，会不会成为把我拒之门外的卡点，一直是我之前比较担心的点。</p><p>下面是居转户的一些经历的时间点：</p><ul><li>2021 年 05 月 18 日，咨询了 XM 公司负责落户的同事。XM 公司算是上海二线的互联网公司，规模也不小，有个 SSC 部门是专门帮员工做落户相关的。负责落户的同事告知我这个情况应该是可以落的，到时候把不符合条件的年份剔除掉，提供一些情况说明就可以。</li><li>2022 年 03 月 1 日，居转户时间快满足了，之前 HX 公司的同事把我拉到一个居转户的交流群里，群里同学相互帮助，也获取到了很多群里同学实际办理时的一些信息，很多信息从网上是查不到的，其中就了解到离职证明只有外地工作经历需要提供，因此这方面提升了很多信心。至少不那么担心条件的问题了。</li><li>2022 年 04 月 11 日，咨询 XM 公司的 SSC 同事，疫情期间能否提交申请，SSC 同事发给我一堆需要准备的材料，例如个税记录等。由于个税缴纳税期是 T+1 个月，因此 3 月的个税是 4 月缴纳，并且缴纳之后得再下个月才能看到，基本上要到 5 月份才能看到。</li><li>2022 年 05 月 20 日，材料准备完成，发给 XM SSC 同事。</li><li>2022 年 06 月 06 日，疫情复工了，询问 XM SSC 同事进展，SSC 同事告知目前我所在的公司主体是临港的，临港的要求会比较多，她们之前没操作过，可能需要给我换个主体操作。</li><li>2022 年 06 月 10 日，公司劳动合同主体已更换至 XM 公司旗下 XD 主体。</li><li>2022 年 06 月 13 日，询问网提进展，告知需要社保切换后提交。</li><li>2022 年 06 月 20 日，社保已转入 XD 公司，询问进展，说预计下月初可提交。</li><li>2022 年 07 月 01 日，再次询问是否已提交，告知落户事宜已转交另一同事。</li><li>2022 年 07 月 06 日，与新同事对接，提供了一个基本信息 Excel 表要填写，发现教育经历里需填写高中的学历证书编号，毕业证需要回老家找</li><li>2022 年 07 月 09 日，回老家找到了高中的毕业证书</li><li>2022 年 07 月 11 日，让家人把户口簿更新了下，老家之前撤县设市，户口本上地址信息未更新，和身份证不一致</li><li>2022 年 07 月 15 日，告知需要近 4 年内的公司提供代扣个税的企业端明细截图</li><li>2022 年 07 月 18 日，联系了 HX 公司的薪酬 HR，请求帮忙提供在 HX 公司期间的企业端个税代扣明细截图</li><li>2022 年 07 月 18 日，XM SSC 同事告知预计下月才能看到 XD 的个税记录，届时才能提交</li><li>2022 年 07 月 25 日，HX 公司的 HR 给到了 HX 在职期间的企业端个税代扣明细截图材料</li><li>2022 年 08 月，因职业发展原因，从 XM 公司离职，入职 BL 公司，在 BL 公司咨询时发现办理落户需要过试用期才可办理，得等 6 个月</li><li>2022 年 10 月 11 日，查到了 XM 8 月份的个税记录，联系 XM 公司的 SSC 同事，帮忙开具了在 XM 任职期间的企业端个税代扣明细截图</li><li>2022 年 10 月 14 日，收到 XM 任职期间的企业端个税明细截图，但发现遗漏了几页</li><li>2022 年 10 月 25 日，收到 XM 企业端个税明细截图遗漏的几页</li><li>2023 年 02 月 15 日，联系 BL 落户小助手，准备资料，由于准备材料在 XM 都已准备过，所以比较顺利</li><li>2023 年 02 月 17 日，网上提交资料，随申办状态是待预审</li><li>2023 年 02 月 28 日，预受理退回，原因是居住证办卡时间填写不对、XM 期间企业端个税明细有几列没有截全、17 年有几个月代缴记录，需提供任职公司名、需提交持有居住证期间内所有的个税纳税清单（之前只提供了 19 年之后的）</li><li>2023 年 02 月 28 日，联系 XM SSC 同事，帮忙重新开具企业端个税代扣明细截图</li><li>2023 年 03 月 02 日，XM SSC 同事重新提供了企业端个税代扣明细截图，发现仍然缺列</li><li>2023 年 03 月 03 日，XM SSC 同事用更大的显示器截图重新提供了企业端个税代扣明细截图</li><li>2023 年 03 月 03 日，补充完资料重新提审，其中代缴记录那段经历写了一个补充说明，由于任职公司尚未成立，因此无法提供任职公司名</li><li>2023 年 03 月 09 日，预受理退回，原因是 17 年那几个月社保缴纳的公司和个税缴纳的 YF 公司不一致</li><li>2023 年 03 月 09 日，补充材料重新提审，解释了 17 年自己创业担任 YF 公司法人，因此有 0 申报记录，而代缴公司未代缴个税，所以出现社保缴纳的公司与个税缴纳不一致的问题</li><li>2023 年 03 月 16 日，预受理通过，等待档案、教育背景核实，提供了调档函</li><li>2023 年 03 月 16 日，江苏人社在线提交调档函，申请档案转出</li><li>2023 年 03 月 22 日，档案核实已完成</li><li>2023 年 03 月 23 日，预审通过，网上受理通过，请用人单位尽快至受理点递交书面材料</li><li>2023 年 03 月 30 日，BL 落户小助手线下提交材料</li><li>2023 年 04 月 03 日，现场受理通过，等待初审</li><li>2023 年 04 月 14 日，初审通过，等待审核</li><li>2023 年 04 月 23 日，复核通过，等待审核</li><li>2023 年 04 月 27 日，审核通过</li><li>2023 年 04 月 28 日，审批通过，即将公示</li><li>2023 年 04 月 28 日，公示，公示期至 5 月 4 日</li><li>2023 年 05 月 05 日，公示通过</li><li>2023 年 05 月 08 日，上海所在街道派出所打电话过来，说看到电子准迁证的申请了，要携带夫妻户口簿、身份证、结婚证、房产证、小孩出生证明前往派出所</li><li>2023 年 05 月 09 日，前往街道派出所，以为是直接办理户口，结果是还是要办理准迁证，虽然长三角是网上办理，但因为房产上是我和我老婆名字，小孩不在上面，需要填一堆表格，填写基本信息，允许迁入之类的。这里要吐槽一下，这都信息化时代了，落户的其他很多流程都走线上了，派出所最后这一步，竟然还要手填很多系统里已经存在的信息，效率极其低下，窗口办事半小时，有 20 分钟估计都在手填信息，这些信息没有哪个是系统里没有的，顶多签个字完事了。</li><li>2023 年 05 月 16 日，等了一周了，还没消息，果然线上流程还不如线下来得快。打电话到老家派出所，说迁移证已经点掉了。看来还得等上海这边。下午打电话给上海所在街道派出所，核实了一遍信息，说可以去办了。看来还是不能傻傻等通知。立即去拍身份证照片。</li><li>2023 年 05 月 17 日，早上去街道派出所办理户口本、身份证，顺利上岸。</li><li>2023 年 05 月 29 日，早上去街道派出所领取身份证。</li><li>2023 年 05 月 31 日，去街道社区事务受理服务中心办理《就业创业证》，也就是劳动手册。发现需要先完成社保属性变更，还需要几天时间。</li><li>2023 年 06 月 05 日，办完《就业创业证》。</li><li>2023 年 06 月 07 日，完成人事档案转移，从杨浦人才服务中心转移至静安就业促进中心。</li></ul><p>总结一下，踩过的坑和一些经验：</p><ul><li>社保&个税有问题的月份，扣除时间就可以了，不会成为拒之门外的卡点，不用太担心</li><li>尽量不要代缴，尤其是公司名字带有“人力资源”这样一看就是代缴公司的，审核会要求提供额外材料，个人感觉不如不缴，这段期间扣除时间就行了</li><li>不要去社保缴纳基数低的公司，要去按实际工资缴纳社保个税的公司</li><li>提前找历任公司的 HR，要到企业端个税代扣明细截图，检查好是否齐全，每一列都要截全，一般一屏需要分 3 页才能截全</li><li>同一公司内更换主体要注意，一般会延后 2~3 个月才能办理</li><li>换工作前要注意，有一个规定是要转正后才能在下家办理落户，一般转正都得 6 个月。如果要离职前已经达到落户条件，可以让上家公司帮你提交，后续即便你换了公司，还是可以继续走流程的，多请办理的同事喝几杯咖啡就可以了。我如果这么操作的话，可以提前 6 个月拿到户口了，T.T</li><li>办理身份证的时候，要选择邮寄，不要以为去派出所领取快，其实邮寄更快，邮寄是直接证做好了就直接邮寄给你，如果选择去派出所领的话，先得等身份证到派出所，还得天天刷新状态到哪儿了，然后还得去派出所拿号排队，太耽误事了。</li></ul><p>大致经历和经验就是这样，希望能够帮到需要的朋友。</p><h4 id="附上海历年社会平均工资与社保基数2012-2022">附：上海历年社会平均工资与社保基数（2012-2022）</h4><table><thead><tr><th>年份</th><th>社保缴纳月份</th><th>上年度社平</th><th>1 倍基数</th><th>2 倍基数</th><th>3 倍基数</th></tr></thead><tbody><tr><td>2012</td><td>2012.04-2013.03</td><td>4331</td><td>4331</td><td>8662</td><td>12993</td></tr><tr><td>2013</td><td>2013.04-2014.03</td><td>4692</td><td>4692</td><td>9384</td><td>14076</td></tr><tr><td>2014</td><td>2014.04-2015.03</td><td>5036</td><td>5036</td><td>10072</td><td>15108</td></tr><tr><td>2015</td><td>2015.04-2016.03</td><td>5451</td><td>5451</td><td>10902</td><td>16353</td></tr><tr><td>2016</td><td>2016.04-2017.03</td><td>5939</td><td>5939</td><td>11878</td><td>17817</td></tr><tr><td>2017</td><td>2017.04-2018.03</td><td>6504</td><td>6504</td><td>13008</td><td>19512</td></tr><tr><td>2018</td><td>2018.04-2019.03</td><td>7132</td><td>7132</td><td>14264</td><td>21396</td></tr><tr><td>2019</td><td>2019.04-2019.10</td><td>7832</td><td>7832</td><td>15664</td><td>23496</td></tr><tr><td></td><td>2019.11-2020.06</td><td>8765</td><td>8211</td><td>16422</td><td>24633</td></tr><tr><td>2020</td><td>2020.07-2021.06</td><td>9580</td><td>9339</td><td>18678</td><td>28017</td></tr><tr><td>2021</td><td>2021.07-2022.06</td><td>10338</td><td>10338</td><td>20767</td><td>31014</td></tr><tr><td>2022</td><td>2022.07-2023.06</td><td>11396</td><td>11396</td><td>22792</td><td>34188</td></tr></tbody></table><blockquote><p>特别感谢这篇文章，给了我最初的信心：<a href="https://www.cnblogs.com/TankXiao/p/8203819.html" rel="nofollow">上海程序员 落户攻略</a></p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Cloudflare 加速博客海外访问速度]]></title>
        <id>/posts/2023/use-cloudflare-speed-up-overseas-traffic</id>
        <link href="https://hadb.me/posts/2023/use-cloudflare-speed-up-overseas-traffic"/>
        <updated>2023-05-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这几天对 hadb.me 博客又做了一次迁移和优化。]]></summary>
        <content type="html"><![CDATA[<p>这几天对 hadb.me 博客又做了一次迁移和优化。</p><p>原先 hadb.me 是直接部署在阿里云的 k8s 上的，但为了统一博客的数据备份，决定迁移到 HomeLab ，数据存到 NAS 上，容器部署到 NUC 的 docker 中。这样数据可以跟着整个 NAS 的备份策略一起。所以这次架构调整的主要目的是为了方便博客数据的统一备份。</p><p>但是由于 HomeLab 无法直接暴露 443 端口，所以域名不能直接解析到家里的 IP。</p><p>一开始尝试了下直接接入 Cloudflare，发现境外访问速度很快，但境内直接访问的话，速度堪忧。于是研究了一下，最终根据访问者的位置使用不同的解析方式实现境内外的同时加速的目标。</p><p>最终的架构是这样：</p><figure><img src="https://hadb.me/static/posts/2023/20230518.use-cloudflare-speed-up-overseas-traffic/01.png" alt="hadb.me 博客网络架构"></img><figcaption>hadb.me 博客网络架构</figcaption></figure><p>在境内，直接解析到阿里云的 SLB 上，阿里云上我是有一套 k8s 集群，里面起了个 nginx 容器，反向代理到 HomeLab 的非标端口上，HomeLab 的域名解析通过阿里云解析的 API 动态更新。</p><p>在境外，通过 CNAME 解析到 Cloudflare 上绑定一个其他域名 <code>xxx.com</code>，这个域名通过 Cloudflare 的 API 会动态更新解析到 HomeLab 的外网 IP 上。在这个域名的 Origin Rules 里面设置主机名为 <code>xxx.com</code> 和 <code>hadb.me</code> 的时候都重写端口到 HomeLab 的非标端口上。</p><p>至此，完成架构迁移。既满足了备份需求，海外访问速度上也有提升。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Font2svg 特殊字体渲染方案]]></title>
        <id>/posts/2023/font2svg-solution</id>
        <link href="https://hadb.me/posts/2023/font2svg-solution"/>
        <updated>2023-07-19T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<blockquote><p>2023-09-12 发表于 <a href="https://mp.weixin.qq.com/s/eAlceV6H0JO019m8TozwxA" rel="nofollow">哔哩哔哩技术公众号</a>、<a href="https://www.bilibili.com/read/cv26464950/" rel="nofollow">哔哩哔哩技术专栏</a> 等处。</p><p>GitHub 开源地址：<a href="https://github.com/font2svg" rel="nofollow">Font2svg</a></p><p>注：开源版方案和文中略有差别，但更通用。API 服务已经完成，前端组件的开源版还没搞完。</p></blockquote><h2 id="背景介绍">背景介绍</h2><p>在 Web 开发中，经常会需要在页面中引入一些特殊字体，这些字体通常不在系统字体库的范围内，并且动辄 4~5MB，甚至有些字体超过 10MB，会影响用户加载体验，尤其在手机端使用移动网络的情况下。</p><p>针对不同的业务场景，通常会有以下几种解决方案：</p><h4 id="一使用切图">一、使用切图</h4><p>当需要显示特殊字体的文案为固定文案时，直接使用切图可能是最常见的一种解决方案，无论是透明的 PNG 切图，还是用矢量的 SVG，都能在便捷度和加载体验上有不错的体验。当然，使用 PNG 的时候需要注意高分辨率屏幕的适配，使用 1 倍图在高分辨率的屏幕下肉眼很容易看出模糊。</p><h4 id="二使用原始字体">二、使用原始字体</h4><p>当需要显示特殊字体的文案是动态文案时，就没办法直接用文字切图来实现了。通常在对用户加载体验没有特别要求的场景下，可直接通过 <code>@font-face</code> 来直接引入特殊字体的文件进来。缺点也显而易见，正如前文所说，动辄 4~5MB，甚至超过 10MB 的字体文件，会比较影响用户的加载体验，在首屏需要显示的场景下，用户往往会先看到先渲染默认字体，再闪现成特殊字体的情况。</p><h4 id="三使用裁剪压缩后的字体">三、使用裁剪/压缩后的字体</h4><p>字体裁剪技术，可通过省略字体变体（如某些字体会包含多个粗细）、裁剪字体字符集（省略不常用的字符）、通过其他压缩算法（如 woff、woff2）等方式来降低字体体积。当文案内容相对可控的情况下，用该方案可比直接加载原始字体要节省更多体积。但若字符裁剪过多，会导致缺少部分字符，在显示时出现字体不一致的情况；若字符裁剪过少，体积仍会较大。并且通常情况下，裁剪后的字符数也是远大于用户实际需要渲染的字符数，还是会有非常大的不必要的下载流量。</p><h2 id="实现原理">实现原理</h2><p>在动态渲染特殊字体文案的场景下，为了提升用户的加载体验，我们研发了一个创新的解决方案：「Font2svg」。在介绍实现原理前，先简单介绍下字体相关的基础知识。</p><h4 id="字符character">字符（Character）</h4><p>在计算机中，字符是任意单个文本元素，例如字母、数字、标点符号、汉字甚至 Emoji 表情等。字符通常是构建文本的基本单位。</p><h4 id="字符集character-set">字符集（Character Set）</h4><p>字符集是一组预定义的字符的集合。它是对字符进行分类和组织的方式，以便在计算机系统中能够使用。会为每一个字符分配一个唯一的 ID，叫做 Code Point（码点、内码），常见字符集：ASCII、GB2312、GBK、Unicode 等。</p><h4 id="字符编码character-encoding">字符编码（Character Encoding）</h4><p>字符编码是一种将字符映射到特定二进制数的规则，以便在计算机中存储。通常字符集和字符编码都是成对出现，如 ASCII、GB2312、GBK 等，都是既代表了字符集，也代表了对应的字符编码。而 Unicode 比较特殊，有多种字符编码，如 UTF-8、UTF-16 等。</p><h4 id="字体家族font-family">字体家族（Font Family）</h4><p>字体家族是指具有相似设计特征的一组字体。这些字体共享类似的外观，但在细节上可能略有不同，如粗细、斜体等。例如：「思源宋体」就是一个字体家族。</p><h4 id="字体样式font-style">字体样式（Font Style）</h4><p>字体样式是字体家族中特定字体的变体，它定义了字体的外观。例如：Light、Regular、Bold、Heavy 等。</p><h4 id="字型font">字型（Font）</h4><p>字型是一个字体家族下的特定样式的字体，例如: 思源宋体-Bold</p><h4 id="字形glyph">字形（Glyph）</h4><p>字形是指一个单独字符在特定的字体（字型）中的具体外观或形状。每个字符都有对应的字形，它们描述了字符的细节和轮廓。如下图，就是「哔」这个字符在不同的字体下不同的字形：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/01.png"></img></figure><h4 id="字体度量metrics">字体度量（Metrics）</h4><p>字体度量是指字体中字符的尺寸和位置信息。它包括了字符的宽度、高度、上沿线（ascender）、下沿线（descender）、基线（baseline）等数据。字体度量在排版中非常重要，它决定了字符之间的间距和对齐方式，确保文本在屏幕或打印上的合理布局。</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/02.png"></img></figure><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/03.png"></img></figure><h4 id="轮廓outline">轮廓（Outline）</h4><p>字体轮廓是字形的矢量图形表示，一个字形由若干轮廓组成，每个轮廓由若干条线段或贝塞尔曲线闭合而成。根据字体格式不同，使用的贝塞尔曲线的阶数也有区别。TrueType（TTF）字体采用二次贝塞尔曲线（3 点控制），而 OpenType（OTF）字体采用三次贝塞尔曲线（4 点控制）。</p><p>一个轮廓组成的字母 C：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/04.gif"></img></figure><p>多个轮廓组成的字母 B：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/05.gif"></img></figure><p>二次贝塞尔曲线：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/06.gif"></img></figure><p>三次贝塞尔曲线：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/07.gif"></img></figure><p>本方案的原理本质上还是利用 SVG 来渲染特殊字体，通过上面的介绍，我们知道，字体的轮廓由若干线段和贝塞尔曲线组成，得知了线段的坐标和贝塞尔曲线的控制点，我们就可以绘制出轮廓的 SVG，而有了 SVG 我们就可以用来显示。因此我们可以通过解析字体文件，按需获取对应字符的 SVG 来避免加载整个字体文件。如何去唯一表示一个特定字符呢？那就是通过字符的 Unicode 编码。如下图所示，我们在渲染「哔哩哔哩乾杯」这几个字符的时候，只需要根据「哔」、「哩」、「乾」、「杯」这四个字符的 Unicode 编码去获取他们的 SVG，然后组合起来显示即可。</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/08.png"></img></figure><p>考虑到性能，如果每个字符都实时去请求服务端生成 SVG 的话，并发小的时候没影响，并发大了的话，这个字体转 SVG 的服务就会成为瓶颈。不过由于这些 SVG 生成之后就几乎不会变，所以可以预先全部生成好，放到 CDN 上，每次直接根据 Unicode 编码去 CDN 上获取指定字符的 SVG 即可。这样既避免了服务端的压力，还利用了 CDN 的特性，可以让用户更快的获取到所需的资源。</p><p>「字体转 SVG」只是第一步，在实际渲染中，还需要修改 SVG 的样式，例如字体颜色、下划线等，因此我们还做了一个前端的组件，用来对获取到的 SVG 进行样式的处理以还原字体的样式。这个组件实现了根据所需字符「动态加载 SVG」以及「SVG 复原字体样式」，在使用起来只需要传入字体名称、所需显示的字符、一些字体样式等，大大减少了前端接入的复杂度。</p><p>整体流程如下图，页面开发阶段，在 Font2svg 后台预先上传字体文件，解析所有字符的 SVG，并预先上传至对象存储/CDN 上。在页面加载时，通过前端组件传入需要渲染的字符和对应字体的配置，从 CDN 拉取特定字体特定字符的 SVG，并根据样式对字符的 SVG 进行二次编辑，复原字体上色、描边、下划线等样式。</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/09.png"></img></figure><p>后台效果预览：</p><video src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/10.mp4"></video><h4 id="如何解析字体并生成字符的-svg">如何解析字体并生成字符的 SVG</h4><p>字体解析服务通过 Python 实现，借助 <a href="https://github.com/rougier/freetype-py" rel="nofollow">freetype</a> 这个库对字体进行解析，遍历字体中的每一个字符，每个字符的字形（glyph）包含了轮廓（outline）和度量（metrics）数据，通过 <code>outline.decompose</code> 方法，可以将字符的轮廓数据分解为一系列的路径段，其中每个路径段都是一个点序列，表示字符的一部分。这些路径段可以是直线段、二次贝塞尔曲线或三次贝塞尔曲线。通过这些路径段的类型和点坐标，再通过 <code>svgpathtools</code> 这个库绘制成 SVG 的路径。</p><pre><code><span class="line" line="1"><span class="su5hD">outline</span><span class="sP7_E">.</span><span class="slqww">decompose</span><span class="sP7_E">(
</span></span><span class="line" line="2"><span class="s99_P">    context</span><span class="smGrS">=</span><span class="s39Yj">None</span><span class="sP7_E">,
</span></span><span class="line" line="3"><span class="s99_P">    move_to</span><span class="smGrS">=</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">callbackMoveTo</span><span class="sP7_E">,
</span></span><span class="line" line="4"><span class="s99_P">    line_to</span><span class="smGrS">=</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">callbackLineTo</span><span class="sP7_E">,
</span></span><span class="line" line="5"><span class="s99_P">    conic_to</span><span class="smGrS">=</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">callbackConicTo</span><span class="sP7_E">,
</span></span><span class="line" line="6"><span class="s99_P">    cubic_to</span><span class="smGrS">=</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">callbackCubicTo</span><span class="sP7_E">,
</span></span><span class="line" line="7"><span class="sP7_E">)
</span></span><span class="line" line="8"><span emptyLinePlaceholder>
</span></span><span class="line" line="9"><span class="sbsja">def</span><span class="sGLFI"> callbackMoveTo</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="smGrS"> *</span><span class="sFwrP">args</span><span class="sP7_E">):
</span></span><span class="line" line="10"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="slqww">_verbose</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">MoveTo </span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorsToPoints</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">))
</span></span><span class="line" line="11"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">_lastX</span><span class="sP7_E">,</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">_lastY</span><span class="smGrS"> =</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">].</span><span class="skxfh">x</span><span class="sP7_E">,</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">].</span><span class="skxfh">y
</span></span><span class="line" line="12"><span emptyLinePlaceholder>
</span></span><span class="line" line="13"><span class="sbsja">def</span><span class="sGLFI"> callbackLineTo</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="smGrS"> *</span><span class="sFwrP">args</span><span class="sP7_E">):
</span></span><span class="line" line="14"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="slqww">_verbose</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">LineTo </span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorsToPoints</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">))
</span></span><span class="line" line="15"><span class="su5hD">    line </span><span class="smGrS">=</span><span class="slqww"> Line</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="slqww">lastXyToComplex</span><span class="sP7_E">(),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">]))
</span></span><span class="line" line="16"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">svg_paths</span><span class="sP7_E">.</span><span class="slqww">append</span><span class="sP7_E">(</span><span class="slqww">line</span><span class="sP7_E">)
</span></span><span class="line" line="17"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">_lastX</span><span class="sP7_E">,</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">_lastY</span><span class="smGrS"> =</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">].</span><span class="skxfh">x</span><span class="sP7_E">,</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">].</span><span class="skxfh">y
</span></span><span class="line" line="18"><span emptyLinePlaceholder>
</span></span><span class="line" line="19"><span class="sbsja">def</span><span class="sGLFI"> callbackConicTo</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="smGrS"> *</span><span class="sFwrP">args</span><span class="sP7_E">):
</span></span><span class="line" line="20"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="slqww">_verbose</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">ConicTo</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorsToPoints</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">))
</span></span><span class="line" line="21"><span class="su5hD">    curve </span><span class="smGrS">=</span><span class="slqww"> QuadraticBezier</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="slqww">lastXyToComplex</span><span class="sP7_E">(),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">]),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">1</span><span class="sP7_E">]))
</span></span><span class="line" line="22"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">svg_paths</span><span class="sP7_E">.</span><span class="slqww">append</span><span class="sP7_E">(</span><span class="slqww">curve</span><span class="sP7_E">)
</span></span><span class="line" line="23"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">_lastX</span><span class="sP7_E">,</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">_lastY</span><span class="smGrS"> =</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">1</span><span class="sP7_E">].</span><span class="skxfh">x</span><span class="sP7_E">,</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">1</span><span class="sP7_E">].</span><span class="skxfh">y
</span></span><span class="line" line="24"><span emptyLinePlaceholder>
</span></span><span class="line" line="25"><span class="sbsja">def</span><span class="sGLFI"> callbackCubicTo</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="smGrS"> *</span><span class="sFwrP">args</span><span class="sP7_E">):
</span></span><span class="line" line="26"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="slqww">_verbose</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">CubicTo</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">),</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">vectorsToPoints</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">))
</span></span><span class="line" line="27"><span class="su5hD">    curve </span><span class="smGrS">=</span><span class="slqww"> CubicBezier</span><span class="sP7_E">(
</span></span><span class="line" line="28"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="slqww">lastXyToComplex</span><span class="sP7_E">(),
</span></span><span class="line" line="29"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">0</span><span class="sP7_E">]),
</span></span><span class="line" line="30"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">1</span><span class="sP7_E">]),
</span></span><span class="line" line="31"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="slqww">vectorToComplex</span><span class="sP7_E">(</span><span class="slqww">args</span><span class="sP7_E">[</span><span class="srdBf">2</span><span class="sP7_E">]),
</span></span><span class="line" line="32"><span class="sP7_E">    )
</span></span><span class="line" line="33"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">svg_paths</span><span class="sP7_E">.</span><span class="slqww">append</span><span class="sP7_E">(</span><span class="slqww">curve</span><span class="sP7_E">)
</span></span><span class="line" line="34"><span class="s_hVV">    self</span><span class="sP7_E">.</span><span class="skxfh">_lastX</span><span class="sP7_E">,</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">_lastY</span><span class="smGrS"> =</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">2</span><span class="sP7_E">].</span><span class="skxfh">x</span><span class="sP7_E">,</span><span class="su5hD"> args</span><span class="sP7_E">[</span><span class="srdBf">2</span><span class="sP7_E">].</span><span class="skxfh">y
</span></span></code></pre><p>除了 SVG 的路径之外，还有一个很重要的内容，就是 viewBox。因为在字体中，表达一个字形，除了轮廓之外，还有一个很重要的 metrics 信息，metrics 信息没有还原好，在字体排版的时候就会出现问题。如果以轮廓本身的大小作为容器的话，一些未占满空间的文字，或者一些标点符号，在排版时，就会被缩放的很大；而如果以字体的上沿线和下沿线来计算一个方形容器显示的话，对于中文字符问题不大，但对于半角字符，其中间的间隙就会显得太大，如下图：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/11.png"></img></figure><p>因此，我们需要从 Metrics 中提取每个字形的基本信息。以横排为例，取每个字形自己的宽度来绘制 viewBox，高度还是通过上下沿线相减来计算。这样在排版时就可以得到想要的效果了：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/12.png"></img></figure><pre><code><span class="line" line="1"><span class="sbsja">def</span><span class="sGLFI"> calcViewBox</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="sFwrP"> metrics</span><span class="sP7_E">):
</span></span><span class="line" line="2"><span class="su5hD">    view_box_min_x </span><span class="smGrS">=</span><span class="srdBf"> 0
</span></span><span class="line" line="3"><span class="su5hD">    view_box_min_y </span><span class="smGrS">=</span><span class="smGrS"> -</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">ascender
</span></span><span class="line" line="4"><span class="su5hD">    view_box_width </span><span class="smGrS">=</span><span class="su5hD"> metrics</span><span class="sP7_E">.</span><span class="skxfh">horiAdvance
</span></span><span class="line" line="5"><span class="su5hD">    view_box_height </span><span class="smGrS">=</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">ascender</span><span class="smGrS"> -</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">descender
</span></span><span class="line" line="6"><span class="sVHd0">    return</span><span class="sbsja"> f</span><span class="s_sjI">"</span><span class="srdBf">{</span><span class="su5hD">view_box_min_x</span><span class="srdBf">}</span><span class="srdBf"> {</span><span class="su5hD">view_box_min_y</span><span class="srdBf">}</span><span class="srdBf"> {</span><span class="su5hD">view_box_width</span><span class="srdBf">}</span><span class="srdBf"> {</span><span class="su5hD">view_box_height</span><span class="srdBf">}</span><span class="s_sjI">"
</span></span></code></pre><p>在有了 <code>path</code>、<code>viewBox</code>、<code>width</code>、<code>height</code> 等信息之后，我们就可以直接生成能表达具体字形所需要的 SVG 文件了。</p><p>还有一个特殊字符，那就是空格，他是不包含任何轮廓的，解析轮廓的时候结果是空的。在生成 SVG 的时候，如果 path 是为空会失败，这时候可以生成一个和 <code>viewBox</code> 同样大小的透明度为 0 的轮廓用来占位。</p><pre><code><span class="line" line="1"><span class="su5hD">path </span><span class="smGrS">=</span><span class="sP7_E"> (
</span></span><span class="line" line="2"><span class="slqww">    Path</span><span class="sP7_E">(</span><span class="smGrS">*</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">svg_paths</span><span class="sP7_E">).</span><span class="slqww">scaled</span><span class="sP7_E">(</span><span class="srdBf">1</span><span class="sP7_E">,</span><span class="smGrS"> -</span><span class="srdBf">1</span><span class="sP7_E">)
</span></span><span class="line" line="3"><span class="sVHd0">    if</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">svg_paths</span><span class="sP7_E">)</span><span class="smGrS"> ></span><span class="srdBf"> 0
</span></span><span class="line" line="4"><span class="sVHd0">    else</span><span class="slqww"> parse_path</span><span class="sP7_E">(
</span></span><span class="line" line="5"><span class="sbsja">        f</span><span class="s_sjI">"M 0,</span><span class="srdBf">{</span><span class="smGrS">-</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">ascender</span><span class="srdBf">}</span><span class="s_sjI"> L </span><span class="srdBf">{</span><span class="slqww">metrics</span><span class="sP7_E">.</span><span class="skxfh">horiAdvance</span><span class="srdBf">}</span><span class="s_sjI">,</span><span class="srdBf">{</span><span class="smGrS">-</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">ascender</span><span class="srdBf">}</span><span class="s_sjI"> L </span><span class="srdBf">{</span><span class="slqww">metrics</span><span class="sP7_E">.</span><span class="skxfh">horiAdvance</span><span class="srdBf">}</span><span class="s_sjI">,</span><span class="srdBf">{</span><span class="smGrS">-</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">descender</span><span class="srdBf">}</span><span class="s_sjI"> L 0,</span><span class="srdBf">{</span><span class="smGrS">-</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">face</span><span class="sP7_E">.</span><span class="skxfh">descender</span><span class="srdBf">}</span><span class="s_sjI"> Z"
</span></span><span class="line" line="6"><span class="sP7_E">    )
</span></span><span class="line" line="7"><span class="sP7_E">)
</span></span></code></pre><h4 id="怎么设置字体样式">怎么设置字体样式</h4><p>在前面的步骤中，我们为每个字形预先生成好了对应的 SVG 文件，但我们知道，SVG 文件是一个静态的文件，其字体颜色等样式是固定的，在前端使用的时候，如何去动态设置颜色等样式呢？</p><p>我们在前端去加载 SVG 字体文件的时候，并不是直接通过 <code>img</code> 标签的方式来引入 SVG，而是在前端组件中去下载 SVG 文件内容并解析 DOM 对象，然后根据需要去修改对应的属性或添加额外的样式，例如修改 <code>path</code> 的 <code>fill</code> 属性来设置颜色，修改完样式后再把修改后的 SVG 标签直接插入到文档中。还有一些字体全局的样式信息，例如下划线的位置、粗细等，在不同的字体中，也是有不一样的配置的。我们需要想办法把这些信息也通过 SVG 传递过来。解决方法也很简单，我们在 SVG 的根节点上添加一个自定义的属性，将所需传递的信息转成 <code>JSON</code> 格式然后塞到这个属性里，前端在解析 SVG 的 DOM 时，把这个属性中的数据取出来解析并渲染就可以了。</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/13.png"></img></figure><p>上面截图中，<code>underline</code> 和 <code>underlineThickness</code> 就分别代表了下划线位置和粗细信息，在解析到这两个信息后再做一些转换处理，可通过 <code>::before</code> 伪元素来绘制下划线：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/14.png"></img></figure><h2 id="实践案例">实践案例</h2><h4 id="页面接入">页面接入</h4><p>创作中心每周会给 UP 主推送荣誉周报，页面中首屏有非常大的篇幅显示的是本周关键词，效果如下图：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/15.jpg"></img></figure><p>这个场景有两处使用了特殊字体「方正 FW 筑紫黑」，一个是卡片标题「本周关键词」这几个字，这个是固定标题，用传统的切图方式也是 OK 的，但下面的关键词是根据 UP 主上周的投稿、评论、弹幕、播放等数据由服务端动态下发的，这个就无法通过切图来实现了，需要穷举的关键词特别多。</p><p>在使用本方案之前，该页面使用的是直接引入字体文件来设置字体。由于该页面加载资源较多，在加载时不可避免会出现字体闪烁、图片资源加载过程页面抖动的问题，在手机上通过 4G 网络访问时尤其明显。为了避免这种情况，该页面渲染之前加入了一个 Loading 页，在 Loading 页上加载所需的所有资源，包括图片、字体文件等。</p><p>使用本方案优化前后的数据对比如下：</p><table><thead><tr><th></th><th>渲染文字所需资源</th><th>首屏加载时长</th><th>页面跳失率</th></tr></thead><tbody><tr><td>优化前</td><td>2855KB</td><td>2268ms</td><td>5.47%</td></tr><tr><td>优化后</td><td>45KB</td><td>1791ms</td><td>3.11%</td></tr><tr><td>对比</td><td>🔽 下降 98.4%</td><td>🔽 下降 21%</td><td>🔽 下降 2.36PP</td></tr></tbody></table><ul><li>渲染文字所需资源：优化前字体文件为 2855KB，优化后 JS 组件库为 37KB，4 个 SVG 为 8KB，总共 45KB 的资源，<strong>下降了 98.4%</strong>。</li><li>首屏加载时长：从 2268ms 下降至 1791ms，<strong>下降了 21%</strong>。</li><li>页面跳失率：从 5.47% 降低至 3.11%，<strong>下降了 2.36PP</strong>。</li></ul><p>最终本方案不仅通过降低首屏加载时长提升了用户的加载体验，同时也获得了页面跳失率下降的业务结果。</p><h4 id="失败兜底">失败兜底</h4><p>尽管本方案所需加载的资源量相比直接引入字体包要小很多，但请求数量会根据字符数有所上升，在极端情况下，网络失败会导致资源请求失败；另外，一些字体包含的字符数有限，在请求这个字体本身就不存在的字符的时候，也会出现请求失败的情况。在缺失 SVG 的情况下如何进行兜底的显示也是我们需要考虑的内容。</p><p>在组件侧获取不到对应的 SVG 时，可通过系统默认字体直接在字符位置进行渲染作为兜底，与浏览器默认的处理方式一致。具体效果如下：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/16.png"></img></figure><p>由于兜底的默认字体与目标字体不同，他们的容器大小和下划线位置、下划线粗细等设置均有可能不一样，所以在开启了下划线的情况下，会出现下划线高度和粗细不一致的现象，如下图：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/17.png"></img></figure><p>为了解决这个问题，我们在渲染下划线的时候，对于兜底的默认字体，不采用 css 样式来绘制下划线，而是与 SVG 一样，通过伪元素并且使用目标字体的下划线设置来进行绘制，最终效果如下图：</p><figure><img src="https://hadb.me/static/posts/2023/20230719.font2svg-solution/18.png"></img></figure><h2 id="总结">总结</h2><p>在本文中，我们深入探讨了 Font2svg 方案的技术原理和实现细节。我们通过将字体转换成 SVG 进行渲染，降低了用户渲染特殊字体动态文字所需下载的文件大小，提高了加载速度，从而优化了特殊字体在 Web 页面中的加载体验。</p><p>这套方案在动态渲染特殊字体文案的场景下具有广泛的应用前景，不只是在 Web 端，在客户端上也同样适用。它能为设计师和开发者提供更灵活、高效的特殊字体渲染方案，让设计师不会再因为字体包体积而放弃使用一些艺术字体，同时也让开发者不再为字体包的大小而头疼。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于成长的思考]]></title>
        <id>/posts/2023/thoughts-about-growth</id>
        <link href="https://hadb.me/posts/2023/thoughts-about-growth"/>
        <updated>2023-07-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近参加了公司的一个管理培训计划，上了很多次课，也做了很多课堂及课后作业，也参加了导师对于最后演讲答辩的指导，以及最后的导师点评和分享环节。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230728.thoughts-about-growth/cover.jpg" alt="封面" /><p>最近参加了公司的一个管理培训计划，上了很多次课，也做了很多课堂及课后作业，也参加了导师对于最后演讲答辩的指导，以及最后的导师点评和分享环节。</p><p>给我最大的收获，并不是课堂上学到的一些理论知识，也不是关于管理培训课程本身所带来的目标、团队、文化等主题能力的提升。当然，这些知识本身都会有一定的吸收，于我而言，这些内容并不是什么新鲜的内容，从工作至今，也参加过大大小小很多次类似的培训了。参加这次培训，给我最大的收获是，我重新认识到了阅读的意义。</p><p>近些年来，于我自己而言，随着娱乐生活的丰富，很少再去花精力阅读。日常的阅读基本停留于知乎热榜里的内容、工作所需的专业知识、长/短视频里零碎的信息等。我这些阅读更多的是技能和信息的获取，很少真正去「阅读」，去获得更加沉淀的精华内容。变得更加功利化，只获取表面的、需要的信息。而其余的时间更多用来娱乐了。</p><p>但我越来越发现，那些我生活中遇到的我所「崇拜」的管理者，几乎每一个的阅读量都非常大。在听他们讲话的时候，每每都能感受到他们阅历的丰富，常常都能够旁征博引，举出很多历史上的例子，或者一些名人的话。先不论这些阅读对他们思维上带来的提升，光是这些旁征博引的内容，对于你讲话这件事本身，就已经大有益处：我所说的话，是有理论依据的、是有实际案例的、是有名人背书的。光这点好处，就会让你所要讲的内容更加被人信服、让你所要说的话显得更加有水平。这一点上，我自己是感觉到深深的不如的，是我觉得我所能提升的地方，也是我觉得我有必要去增加「阅读」的原因之一。更何况，系统的阅读，可以改变思维的方式，可以更加了解关于这个社会运作的基本逻辑，遇到一些发生的现象能够更好的理解，能够更加从容和自信，不再那么盲目和焦虑。</p><p>这几年，深深的感受到，专业领域的成长，于我而言，似乎已经没有那么大的吸引力。当你在专业领域积累到一定的程度，靠自己的经验 + 搜索引擎 + ChatGPT 已经能够解决 99% 的问题的时候，你会发现，你再去学习更深入的专业知识的动力和收益都已经变得很小。现阶段的我更想要获得的，是关于思想的成长。我清晰的记得，在多年以前，面对老板的一次苛求和批评，我自己陷入深深的怀疑，在当下很难理解和接受。在后来，我拜读了卡耐基的《人性的弱点》这本书，很多疑问和不解在看这本书的时候都得到了非常好的解释和共鸣。我才明白，老板也是人，他也有弱点。他之所以会这么做，底层都是有原因的。一下子思路就开阔了，就像顿悟了一样。这个世界是有基本的运作规律的，每个人做事情也都是有理可寻的。面对问题，不仅仅要看表面，更要去探寻深层的原因。当然，我的功力还不够，很多时候还是会忘记去多思考一层，或者有时候思考了但是琢磨不透。但我至少明白了，在很多问题的背后，都是有缘由的，都是有逻辑的，有时候只是你没有发现，或你没有想明白。当你探寻到或者想明白了这些缘由和逻辑之后，问题就很好解决了，或者至少能够理解为什么会产生这个问题了。明白这个道理后，再遇到事情，尤其是那些让你出乎意料的事情的时候，会多一分从容，不再是诧异和不知所措。</p><p>还有一点，我觉得我在之前阅读的过程中做的不够。很多书在读的当下，觉得很有共鸣很有收获。但一段时间之后，可能已经忘记里面讲了什么。就好比《人性的弱点》这本书，我已经很难回忆起里面的金句或道理。这不利于我想要在演讲中获得旁征博引的能力。因此我决定，在我的笔记中开辟一个新的一级目录，叫做「阅读」，用来记录我在阅读过程中吸收的精华内容，有利于温故知新，有利于今后的「旁征博引」。</p><p>总的来说，于我个人而言，这次培训最大的收获就是，找到了新的成长方向和方式。那就是通过阅读，去提升「旁征博引」的能力，提升思维能力，为今后的工作和生活提升一些理论基础，提升自己的阅历。说到阅历，通过实践得来的固然深刻，但效率比较低。我觉得阅读是一个更加快速高效获取他人阅历的方式，或许印象没那么深刻，不那么容易吸收成为自己的阅历，但希望通过足够多的数量以及不断的温故知新，能够让我从中多汲取一些。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[三件小事]]></title>
        <id>/posts/2023/three-small-things</id>
        <link href="https://hadb.me/posts/2023/three-small-things"/>
        <updated>2023-09-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[讲一下最近发生的三件小事。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2023/20230909.three-small-things/cover.jpg" alt="封面" /><p>讲一下最近发生的三件小事。</p><p>第一件事情是昨晚回到家准备打游戏时，耳机戴到头上感觉很松，拿下来发现，我一千多的雷蛇新款梭鱼 THX 空间音效双模无线耳机一侧的旋转轴断掉了。淘宝找了半天零件，只有梭鱼 X 的，没有我这款的零件可以买下来更换。联系客服想问问怎么修，让我走公众号自助报障，填了一堆信息，拍了很多照片，最后申请结果认定是人为损坏，不予受理。</p><figure><img src="https://hadb.me/static/posts/2023/20230909.three-small-things/01.jpg"></img></figure><p>想了想，可能是时候告别游戏了。毕竟已经是 30 多岁的人了。而且这几年在游戏上所能获得的快乐也越来越少了。自己越来越菜，一起玩的朋友也越来越少。一直想做一些能够提升自己快乐的事情，而游戏已经是一个对我来讲 ROI 比较低的事情了，需要花很多精力，经常玩到凌晨两三点，透支健康；还会被老婆说；而且从中所获得的快乐和成就感也在日益降低。一直以为我可以一直玩游戏玩下去，但总有那么几个时刻，脑海中会闪现出这样的念头，是时候“告别”游戏了。当然，我所指的告别，也不是说一点都不玩，而是说不再像以前一样，每天投入几个小时去玩。</p><p>第二件事，是今天中午，也就是刚才去星巴克点了杯美式，啡快的口令是：热爱是生活解药。这句话，一下子就穿透到我的心里。是啊，我该去做我所热爱的事情。</p><figure><img src="https://hadb.me/static/posts/2023/20230909.three-small-things/02.jpg"></img></figure><p>以前在入职介绍之类的场合也被人问起过，你的爱好是什么。有人回答我爱好打球，有人回答我爱好弹钢琴，有人回答我爱好旅行。我也常常想，我真正的爱好是什么，喜欢做的事和爱好其实是两件事。思来想去，其实我并没有什么别的爱好，唯一能从中获得快乐和成就感的事情，可能就是写代码这一件事。很多人写代码，成为程序员是一份工作。而对我来说，这本身就是我的爱好，我也很庆幸我能以爱好作为自己的工作。这或许也是我比大部分人在这件事情上做得更好的一个原因，因为我热爱写代码这件事。</p><p>上次在 B 站看王德峰的一个视频，记下了他的这段话：</p><blockquote><p>发愤忘食，乐以忘忧，不知老之将至。做学问，不觉其苦，但觉其乐。因为知道这辈子来做什么事情的，我这件事做得会比别人更好些，我就全心全意去做；别的事情我做不好，我不做了。知天命。因为你既然知道这件事情是你在这辈子来到人世间最值得你做的事情，会做的比别人好一些。你明白了这一条，你看五十岁之后的人生，多么幸福。——<a href="https://www.bilibili.com/video/BV1i44y1c7Vb/?share_source=copy_web&vd_source=76ca94671296b9336d664f327867a2c7" rel="nofollow">【王德峰】从 15 岁到 70 岁，如何度过这个人生阶段？</a></p></blockquote><p>当时感触很深。我自认为在写代码这件事情上，我比别人做得更好些。原因可能有很多，经验、热爱、性格，等等，有很多的原因。原因不去过多探寻，但至少从结果上来看，我比很多人做得更好。我就应该全心全意去做，发挥更大的价值。我所能想到的事，就是尽可能在开源项目上发光发热，尽我所能，做一些贡献。比如一些不那么常用，无利可图的在线工具，往往没有什么人做，已有的那些质量也很差，我可以做得比他们更好。这是我的强项，并且我也有动力去做。这应该是我在没有其他更好的目标的时候需要坚持去做的事。</p><p>在上次想到这些事情后，我重新拾起之前做的 <code>tools.yuanfen.net</code> 站点，升级了框架到 Nuxt 3（Vue 3），添加了一个图片转 PDF 的功能，这也是我自己遇到的实际需求，当时需要把一些扫描的图片转成 PDF，在线的一些工具用了都不那么好用，比如页面边距不能控制、图片质量不能控制等等，而能控制的，又要收费。当时就想，这东西我完全可以自己撸一套，并且可以比这些拙劣的工具做得更好，我应该去做，并且比他们做得更精致更好用一些。然后我就手撸了一个。在撸的过程中，遇到 <code>element-plus</code> 图片预览组件缺少的一个旋转事件的能力，于是给 <code>element-plus</code> 提交了一个 PR，并且在这周被 merge 到主线了。增加了不少成就感。在工作中积累的几个独立的技术组件，也计划在有空余事件的时候，独立开源出来，回馈社区。这些都是我计划中的事情，希望能够借这次耳机挂掉的契机，加快进度。要知道，在很长一段时间内（好几年），我周末都是在打游戏，而没有写过代码。</p><p>第三件事，是喝完星巴克骑着哈啰回家的路上，看到一个流浪汉睡在中铁门口的大理石台阶上笑眯眯的睡着午觉，因为那里可以吹到中铁大楼吹出来的空调的凉气。当时脑海中闪过的想法是，他这样生活也挺惬意的，没有什么烦恼，如果我碌碌无为一生，其实和他没有什么两样。人活在世上还是要想办法创造自己的价值，那才是你本身的价值。上班为公司所创造的价值，最终都是在资本家的身上，他们通过给你发工资买断了你为公司所创造的价值。你只是用自己的劳动力换了一些钱，而并没有带来真正的价值。到头来你自我回顾，发现除了从公司所获得的金钱这个数字，没有别的东西了。所以还是得努力创造公司外的价值，尽可能多做一些开源项目，多沉淀一些属于个人的内容，不管这些东西在当下产生了多大的价值，至少在若干年后，回顾自己一生的时候，这些都可以沉淀下来，成为自己的作品。你只有积累了一个又一个作品之后，才有谈价值的资格。如果你一个作品都没有，又有何谈资呢？</p><p>人的成长，可能也就是在那一个个瞬间，所做的一些思考，或者一些决定。往往这些瞬间都毫不起眼，是那种如果不做记录在日后都不会再回忆起的瞬间。但若干年后回过头来看，这些成长的瞬间有可能就组成了人生的一个个转折点。</p><p>不断写文章也是我认为一件非常有意义的事情。尽管在现在这个时代，照片的频率已经比小时候那个时代要频繁的多了，很多画面、很多回忆都可以通过照片来记录。但人的成长，生命的记录，仅仅靠照片、视频是不够的，在思想上的成长，还是得靠文字来记录。若干年后回忆起自己的一生，通过这些文字串上一个时间轴的照片，就可以勾勒出一个更加丰满和真实的自己。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 年度回顾]]></title>
        <id>/posts/2024/annual-review-2023</id>
        <link href="https://hadb.me/posts/2024/annual-review-2023"/>
        <updated>2024-01-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[又到一年回顾时，这一年发生了很多。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240103.annual-review-2023/cover.jpg" alt="封面" /><p>又到一年回顾时，这一年发生了很多。</p><h2 id="健康">健康</h2><p>先说健康上的问题吧。好消息是，尝试了几年都没能减下去的体重，终于在今年减重成功。坏消息是，之所以能减重是因为得了糖尿病。</p><p>丈人年初查出来血糖有点高，医生说他是糖尿病。我们给他买了个血糖仪。买回来大家挨个测着玩，结果发现我把他还高，而且都过 10 了。去医院检查，吃药，结合饮食控制以及锻炼，体重降得特别快，3 个月就降了十斤。目前正保持着健康的饮食和规律的锻炼，脂肪肝也没了，最近的一次体检，各项指标也都正常。也算因祸得福。不过糖尿病可能要伴随一辈子了。但我认为也是好事，我不得不保持健康的生活，不得不管住嘴迈开腿。都是因果，之前嘴馋大吃大喝，晚上熬夜还吃很多夜宵零食。有这些因才导致了糖尿病的果。但糖尿病的因，也会带来健康生活的果。对于身体来说，总要有些平衡。胰岛能力下降了，就得通过少吃多运动来平衡一下。</p><ul><li>睡眠提要：2023 年平均睡眠时间 6 小时 6 分钟</li><li>运动提要：2023 年平均步行和跑步公里数为 5.3 公里</li><li>步数提要：2023 年平均步数为 7923 步</li><li>能量摘要：2023 年平均活动消耗能量为 454 千卡</li></ul><p>由于是近几个月才开始规律运动，所以全年数据上并不是很好看，24 年的数据到时候再对比下看看。</p><h2 id="生活">生活</h2><p>今年一共点了 243 杯咖啡，其中瑞幸 203 杯，星巴克 22 杯，Manner 18 杯。当然有一些是我请别人和的，不过考虑到也有别人请我喝的，所以大体上差不多。24 年争取喝到 365 杯，达成每天 1 杯咖啡的目标。</p><p>2023 年理财收益率 -1.67%，比 2022 年成绩好一些，2022 年的收益率是 -4.52%。</p><h4 id="增值服务开销">增值服务开销：</h4><table><thead><tr><th>服务/软件</th><th align="center">类别</th><th align="center">时间</th><th align="right">金额</th><th align="center">备注</th></tr></thead><tbody><tr><td>Godaddy</td><td align="center">创业投资</td><td align="center">2023-01-30</td><td align="right">¥169.00</td><td align="center">hadb.me 域名续费 1 年</td></tr><tr><td>微信认证</td><td align="center">创业投资</td><td align="center">2023-09-11</td><td align="right">¥300.00</td><td align="center">实名认证</td></tr><tr><td>八戒财税</td><td align="center">创业投资</td><td align="center">2023-11-17</td><td align="right">¥2,397.00</td><td align="center">代账服务 1 年</td></tr><tr><td>Godaddy</td><td align="center">创业投资</td><td align="center">2023-12-30</td><td align="right">¥171.87</td><td align="center">hadb.me 域名续费 1 年</td></tr><tr><td>阿里云</td><td align="center">创业投资</td><td align="center">全年</td><td align="right">¥5,737.33</td><td align="center">ECS、RDS、域名、负载均衡等服务</td></tr><tr><td>小计</td><td align="center"></td><td align="center"></td><td align="right">¥8,775.2</td><td align="center"></td></tr><tr><td>OTP Auth Pro</td><td align="center">工具</td><td align="center">2023-01-05</td><td align="right">¥25.00</td><td align="center"></td></tr><tr><td>寻隐 App</td><td align="center">工具</td><td align="center">2023-03-15</td><td align="right">¥12.00</td><td align="center"></td></tr><tr><td>西部数据</td><td align="center">工具</td><td align="center">2023-04-01</td><td align="right">¥212.00</td><td align="center">梯子</td></tr><tr><td>Stash</td><td align="center">工具</td><td align="center">2023-04-10</td><td align="right">¥1.17</td><td align="center">美区 App $3.99  减去账号内礼品卡余额</td></tr><tr><td>美图秀秀会员</td><td align="center">工具</td><td align="center">2023-08-05</td><td align="right">¥12.00</td><td align="center"></td></tr><tr><td>APTV Pro</td><td align="center">工具</td><td align="center">2023-09-11</td><td align="right">¥28.00</td><td align="center"></td></tr><tr><td>GitHub Copilot</td><td align="center">工具</td><td align="center">2023-11-22</td><td align="right">¥714.02</td><td align="center">一年</td></tr><tr><td>百度网盘</td><td align="center">工具</td><td align="center">全年</td><td align="right">¥115.00</td><td align="center">百度网盘月卡 4~8 月</td></tr><tr><td>iCloud</td><td align="center">工具</td><td align="center">全年</td><td align="right">¥72.00</td><td align="center">￥6*12 个月</td></tr><tr><td>小计</td><td align="center"></td><td align="center"></td><td align="right">¥1,191.19</td><td align="center"></td></tr><tr><td>共享单车</td><td align="center">生活</td><td align="center">全年</td><td align="right">¥265.50</td><td align="center">哈啰 + 美团</td></tr><tr><td>京东会员</td><td align="center">生活</td><td align="center">2023-12-31</td><td align="right">¥88.00</td><td align="center">京东&京东金融联合会员 1 年</td></tr><tr><td>小计</td><td align="center"></td><td align="center"></td><td align="right">¥353.50</td><td align="center"></td></tr><tr><td>城市：天际线</td><td align="center">游戏</td><td align="center">2023-01-01</td><td align="right">¥262.80</td><td align="center">Steam</td></tr><tr><td>暴雪娱乐</td><td align="center">游戏</td><td align="center">2023-01-21</td><td align="right">¥70.53</td><td align="center">台服战网充值 NT $300  TWD，$ 10.34</td></tr><tr><td>森林之子</td><td align="center">游戏</td><td align="center">2023-02-24</td><td align="right">¥108.00</td><td align="center">Steam</td></tr><tr><td>恐龙救援车</td><td align="center">游戏</td><td align="center">2023-03-03</td><td align="right">¥18.00</td><td align="center"></td></tr><tr><td>大脚车总动员</td><td align="center">游戏</td><td align="center">2023-03-05</td><td align="right">¥30.00</td><td align="center"></td></tr><tr><td>Angry Birds</td><td align="center">游戏</td><td align="center">2023-04-14</td><td align="right">¥6.82</td><td align="center">美区 App $0.99</td></tr><tr><td>全面战争模拟器</td><td align="center">游戏</td><td align="center">2023-05-21</td><td align="right">¥70.00</td><td align="center">Steam</td></tr><tr><td>大富翁 11</td><td align="center">游戏</td><td align="center">2023-05-27</td><td align="right">¥46.20</td><td align="center">Steam</td></tr><tr><td>Scelight</td><td align="center">游戏</td><td align="center">2023-06-25</td><td align="right">¥101.36</td><td align="center">星际 2 录像分析工具，$14</td></tr><tr><td>恐龙编程</td><td align="center">游戏</td><td align="center">2023-08-06</td><td align="right">¥48.00</td><td align="center"></td></tr><tr><td>文明 6 白金版</td><td align="center">游戏</td><td align="center">2023-09-18</td><td align="right">¥50.31</td><td align="center">Steam</td></tr><tr><td>文明 6 手游</td><td align="center">游戏</td><td align="center">2023-09-22</td><td align="right">¥72.97</td><td align="center">美区 App $9.99</td></tr><tr><td>棋院 App 会员</td><td align="center">游戏</td><td align="center">全年</td><td align="right">¥48.00</td><td align="center">连续包月忘记取消 1~4 月</td></tr><tr><td>小计</td><td align="center"></td><td align="center"></td><td align="right">¥932.99</td><td align="center"></td></tr><tr><td>腾讯视频会员</td><td align="center">娱乐</td><td align="center">2023-02-24</td><td align="right">¥238.00</td><td align="center">包年</td></tr><tr><td>芒果 TV 会员</td><td align="center">娱乐</td><td align="center">2023-04-01</td><td align="right">¥198.00</td><td align="center">包年</td></tr><tr><td>虎牙打赏</td><td align="center">娱乐</td><td align="center">2023-04-24</td><td align="right">¥13.20</td><td align="center">直播打赏，两个 ￥6.6</td></tr><tr><td>爱奇艺会员</td><td align="center">娱乐</td><td align="center">2023-05-13</td><td align="right">¥218.00</td><td align="center">包年</td></tr><tr><td>微信读书会员</td><td align="center">娱乐</td><td align="center">2023-07-26</td><td align="right">¥168.00</td><td align="center">包年</td></tr><tr><td>QQ 音乐会员</td><td align="center">娱乐</td><td align="center">2023-07-28</td><td align="right">¥158.00</td><td align="center">包年</td></tr><tr><td>爱奇艺会员</td><td align="center">娱乐</td><td align="center">2023-10-12</td><td align="right">¥15.00</td><td align="center"></td></tr><tr><td>B 站创作推广</td><td align="center">娱乐</td><td align="center">全年</td><td align="right">¥100.00</td><td align="center"></td></tr><tr><td>抖音打赏</td><td align="center">娱乐</td><td align="center">全年</td><td align="right">¥2.00</td><td align="center"></td></tr><tr><td>小计</td><td align="center"></td><td align="center"></td><td align="right">¥1,110.20</td><td align="center"></td></tr><tr><td>合计</td><td align="center"></td><td align="center"></td><td align="right">¥12,363.08</td><td align="center"></td></tr></tbody></table><h2 id="创造">创造</h2><p>今年为了给自己省事，为了让猿奋的项目更清晰，自己给自己开发了一套 OA 系统。肯定没有专业 OA 系统功能齐全，但对于我来说，能识别发票 pdf，能从税务平台导入发票信息，能从企业银行导入流水信息，能管理应收应付绑定发票和流水，能够批量导出应收应付对应的发票和流水方便财务记账，已完全满足我的需求。</p><p>目前已实现的功能主要聚焦在财务当面，因为这是我之前的痛点和刚需。之前每个月整理这些资料太费精力。24 年准备继续完善合同和人力资源管理方面的功能。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20240226]]></title>
        <id>/posts/2024/diary-20240226</id>
        <link href="https://hadb.me/posts/2024/diary-20240226"/>
        <updated>2024-02-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[感觉今天啥都没干，开了个线上会，企微回了一些消息，微信聊了会天，一天就过去了。]]></summary>
        <content type="html"><![CDATA[<p>感觉今天啥都没干，开了个线上会，企微回了一些消息，微信聊了会天，一天就过去了。</p><p>早上洗澡的时候，回忆起小时候去外公家的路，路上有一个很窄的桥，小时候最怕过那个桥，以至于小时候做梦的时候都会梦到从那个桥上掉进河里。但是每当听到「世上只有妈妈好」这首歌的时候，脑海中浮现的又是这座桥，以及桥旁边的芦苇叶，以及我坐在妈妈自行车上过这座桥的场景。因为妈妈是在这座桥上教我唱这首歌。这可能是关于我幼儿时代比较早的一些记忆了。</p><p>差不多的年纪，其他还有一些仍在存在脑海中的记忆。小时候要在庄上买个玩具，妈妈没给我买，回去的路上我一直嚷嚷着要，然后直接从自行车后面跳下来摔到了地上。到了家我还是在嚷嚷着要，妈妈只能又骑我去庄上把玩具买了回来。当时老家的外墙面，好像是那种很粗糙的表面，有小石子在里面，凹凸不平的那种装饰面，老房子客厅的地面好像是泥土的还是水泥的，没印象了，通往二楼的楼梯有个拐角直接上去，好像小时候还从上面滚下来过。二楼有个平房，小时候还可以把黑白电视机放到窗户口，一起坐在平房上面乘凉看电视。爸妈房间的彩电，有个天线，也是一直通到平房上，信号不好的时候，还需要去动一动天线，当时在那个电视机上看过铁臂阿童木和叮当猫的动画片。小时候经常会睡在爷爷奶奶的房间，夏天就在蚊帐里面，有时候还需要捉蚊子。还记得晚上睡觉前爷爷给我讲望梅止渴的故事。有一年过年前，每天晚上睡觉前我都在倒计时，还有几天过年。到了过年那一天，穿上新衣服，别提多开心了。那时候年味很足，屋外面的地上会用面粉之类的东西打上很多圆圆的印记。平房下面是个厨房，想到那个厨房的锅的时候，我脑海中浮现出「温水煮青蛙」这个词语，可能第一次知道这个词的时候，当时就是想象的在这口锅里煮吧。厨房昏暗的灯光下，我曾经坐在小板凳上写过作业。厨房往里走是浴室，洗衣机、浴缸、洗手池都在那个小房间。小时候洗澡就是在浴缸放些热水，然后外面用一层浴帘罩住保温。妈妈给我洗澡的时候就是往身上拍点水，然后嘴里说着：「拍拍心，不害病」之类的话。小时候得过一次中耳炎，好像就是因为洗澡的时候，洗发水进耳朵了，还滴了一段时间的药水。浴室外面印象很清楚，是我们家和邻居家的一个中间区域，长了很多草，还有一颗很大的栀子花，开花的时候很香。小时候还跑进去玩过，还看到过狗狗拉的干干的便便。说到狗狗，邻居家的「黄儿」是陪伴了我儿时的一直很聪明的老母狗，生了很多狗宝宝，当时我们那附近很多人家的狗都是她生的。当然它也咬过很多次外人，邻居好几次去给别人去打狂犬疫苗。它的脚上有一处没有毛，那是它跟蛇打斗过被蛇咬过的痕迹。它还有个儿子叫「花儿」，是我小时候最喜欢的狗，只可惜后来被偷狗的给毒死了，不过那是后来的故事了，我上初中时还写过一篇回忆的文章，也不知还能不能找得到了。</p><p>大约是在我上小学一年级的时候，家里拆了老房子，重新盖了现在的新房子。盖新房子的时候，老房子的平房和厨房一开始没有拆掉，当时我们都住在平房下面以及厨房里。那时候草堆里找到一窝刺猬，当时还放在房间里的一个桶里养了一段时间。小刺猬眼睛都没睁开。后来他们的结局就不知道怎么样了，没印象了。盖新楼房的时候，我贪玩也从工人爬上楼的「跳板」上走，下雨天跳板很滑，我从二楼的位置滑下去摔在地上，家里人很担心，但好在没受伤，家里人把我抱到房里看电视了，当时还很庆幸那天不用写作业了。电视里放的是 97 年版的天龙八部，我印象很深刻。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20240227]]></title>
        <id>/posts/2024/diary-20240227</id>
        <link href="https://hadb.me/posts/2024/diary-20240227"/>
        <updated>2024-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天早上起得早，9 点多就到公司。买了杯咖啡坐在楼下晒了会太阳。红四喜和摸鱼群里聊了会天，聊的都是个人努力/时代机遇与成功的关系。我的结论大致如下：]]></summary>
        <content type="html"><![CDATA[<p>今天早上起得早，9 点多就到公司。买了杯咖啡坐在楼下晒了会太阳。红四喜和摸鱼群里聊了会天，聊的都是个人努力/时代机遇与成功的关系。我的结论大致如下：</p><ul><li>一个人的成功大部分因素不是靠个人努力，而是靠时代发展的机遇。不努力，机遇来了自己也抓不住；努力了，也不一定就能上对车。</li><li>经济大环境不行，干啥都不行。这年头就得学学鸵鸟，把头埋着，摸摸鱼，把口袋捂紧。啥都不干，钱留着就是好事，投资啥都是亏钱，经济好了再复出。</li><li>有可能生不逢时，如果早生个十年，可能就是享受到互联网红利的那波人了。就看下坡路要走多久了，如果一直走下去，我们这代可能就是时代的炮灰。</li><li>就这么简单过一生也没啥不好，早点想清楚，早点认命，早点享福。折腾到最后一无所有，还不如趁早享受人生。</li></ul><p>引用江泽民的一句语录：「一个人的命运啊，当然要靠自我奋斗，但是也要考虑到历史的进程。」</p><p>中午还是日常一杯咖啡 + 一个多小时散步。</p><p>下午感觉啥都没干。</p><p>又荒废了一天。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20240229]]></title>
        <id>/posts/2024/diary-20240229</id>
        <link href="https://hadb.me/posts/2024/diary-20240229"/>
        <updated>2024-02-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天听闻 ioredis 被 redis 收购，并且翻了下作者的博客，这个开源项目他坚持了 9 年。并且看到他的 Medis 项目之前在 App Store 上卖了 4 万多美刀，还是挺羡慕的。]]></summary>
        <content type="html"><![CDATA[<p>今天听闻 <code>ioredis</code> 被 <code>redis</code> 收购，并且翻了下作者的博客，这个开源项目他坚持了 9 年。并且看到他的 <code>Medis</code> 项目之前在 App Store 上卖了 4 万多美刀，还是挺羡慕的。</p><p>也躺了挺久了，看看能不能在受众是开发者、开源代码 + AppStore 付费支持的方向上找到一个突破点，让自己接下来的几年能够有一个长期坚持的项目。</p><p>Font2svg 是一个不错的项目，如果实在找不到别的方向，也可以发力 Font2svg，只不过付费支持比较难一点，倒是可以做个 mac 工具，把 font 转成 svg，但是得看看不用 python，用前端能不能实现。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[群晖 DSM7.2 Web Station 网页服务重复无法删除的问题]]></title>
        <id>/posts/2024/how-to-delete-web-service-of-synology-dsm-7-2</id>
        <link href="https://hadb.me/posts/2024/how-to-delete-web-service-of-synology-dsm-7-2"/>
        <updated>2024-03-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天遇到一个问题，记录一下，解决方案参考：Cannot modify / delete Web Station Web Service created by Container Manager]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240314.how-to-delete-web-service-of-synology-dsm-7-2/cover.png" alt="封面" /><p>今天遇到一个问题，记录一下，解决方案参考：<a href="https://community.synology.com/enu/forum/1/post/161835?reply=502868" rel="nofollow">Cannot modify / delete Web Station Web Service created by Container Manager</a></p><p>问题截图如下（当时忘记截图了，该截图摘自上述文档）：</p><p>解决方法：</p><ol><li>ssh 登录群晖</li><li>执行 <code>sudo synopkg stop WebStation</code>，停止 Web Station 服务</li><li>删除 <code>/usr/syno/etc/packages/WebStation/Service.json</code> 中需要删除的服务（注意记录一下 service_id，下一步需要用到）</li><li>删除 <code>/usr/syno/etc/packages/WebStation/WSResource.json</code> 中上述 id 的 service（注意记录一下 mustache 的路径，下一步需要用到）</li><li>删除上述路径的 <code>.mustache</code> 文件</li><li>执行 <code>sudo synopkg start WebStation</code>，重新启动 Web Station 服务</li></ol><p>去 DSM 中检查一下，任务完成！</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[记录一次 WordPress 被恶意代码注入的问题]]></title>
        <id>/posts/2024/wordpress-hacked</id>
        <link href="https://hadb.me/posts/2024/wordpress-hacked"/>
        <updated>2024-03-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天发现之前帮一个客户维护的服务器流量近期一直比较高，是平常的几十倍。看了下请求，都是一些奇奇怪怪的 URL，并且甚至还能返回 200。访问看了下，是一些别的产品的营销页，看了下请求来源，也都是一些营销机器人。]]></summary>
        <content type="html"><![CDATA[<p>今天发现之前帮一个客户维护的服务器流量近期一直比较高，是平常的几十倍。看了下请求，都是一些奇奇怪怪的 URL，并且甚至还能返回 200。访问看了下，是一些别的产品的营销页，看了下请求来源，也都是一些营销机器人。</p><p>初步怀疑是客户 WordPress 的管理员密码被撞库了，然后 WordPress 本身又有一些漏洞导致代码文件被改了。上去看了下，发现篡改了很多文件。</p><p>后续就是将 WordPress 的代码恢复成之前的版本，清理了一些不用的管理员账号，并且把剩下唯一的管理员密码重新修改了。然后在 <code>Apache</code> 上把流量较高的一些请求的路由和 UA 做了限制，直接禁止访问降低带宽。</p><pre><code>RewriteCond %{HTTP_USER_AGENT} (DataForSeoBot|SemrushBot) [NC,OR]
RewriteCond %{REQUEST_URI} ^/godsend/ [NC]
RewriteRule .* - [F]
</code></pre><p>后面流量就恢复正常了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[MP4Box.js 获取视频旋转信息]]></title>
        <id>/posts/2024/get-video-rotation-by-mp4box-js</id>
        <link href="https://hadb.me/posts/2024/get-video-rotation-by-mp4box-js"/>
        <updated>2024-05-22T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240522.get-video-rotation-by-mp4box-js/cover.jpg" alt="封面" /><blockquote><p>声明：本文部分内容使用 ChatGPT 生成</p></blockquote><h2 id="序言">序言</h2><p>公司的一个项目中用到 <code>MP4Box.js</code> 在上传视频前去解析视频的宽高，并且根据宽高的比例做一些拦截，只允许 16:9 横屏的素材。后来发现一个问题，部分竖屏的素材也被提交上来了。经过研究，发现这类视频可能是由手机拍摄的，带了旋转信息，因此 <code>MP4Box.js</code> 中的原始宽高有问题。</p><h2 id="什么是-mp4boxjs">什么是 MP4Box.js</h2><p><a href="https://github.com/gpac/mp4box.js" rel="nofollow">MP4Box.js</a> 是一个支持在浏览器中处理 MP4 文件的 JS 库，可以实现获取 MP4 文件的元数据信息、分割文件、提取媒体样本等高级处理能力。</p><p>通过 <code>MP4Box.js</code> 可以从 <code>videoTrack</code> 中的 <code>width</code> 和 <code>height</code> 中获取视频的宽高，对于一般的视频都是 OK 的，但是带了旋转信息，通过 MP4Box 读出的宽高仍是旋转前的宽高，导致在一些场景下的判断会有问题。那么，如何获取到视频的旋转信息呢？</p><h2 id="mp4boxjs-如何获取旋转信息">MP4Box.js 如何获取旋转信息</h2><p>在 MP4 和 MOV 文件中，旋转信息通常存储在 <code>tkhd</code> (Track Header Box) 或 <code>mvhd</code> (Movie Header Box) 中。这个信息会影响视频轨道的显示方式。</p><p>Track Header Box (<code>tkhd</code>)：包含一个 <code>matrix</code> 的矩阵，描述视频帧的旋转。</p><p><code>matrix</code> 字段中的旋转信息是通过一个 3x3 矩阵来表示的，具体可以参考 ISO/IEC 14496-12 标准。</p><p>具体实现代码：</p><pre><code><span class="line" line="1"><span class="su5hD">Math</span><span class="sP7_E">.</span><span class="sGLFI">atan2</span><span class="su5hD">(videoTrack</span><span class="sP7_E">.</span><span class="su5hD">matrix[</span><span class="srdBf">1</span><span class="su5hD">]</span><span class="sP7_E">,</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">matrix[</span><span class="srdBf">0</span><span class="su5hD">]) </span><span class="smGrS">*</span><span class="su5hD"> (</span><span class="srdBf">180</span><span class="smGrS"> /</span><span class="su5hD"> Math</span><span class="sP7_E">.</span><span class="s_hVV">PI</span><span class="su5hD">)
</span></span></code></pre><p>其中，<code>Math.atan2</code> 是 JS 中的一个数学函数，用于计算从点 (0, 0) 到点 (x, y) 之间的直线与 x 轴正方向之间的角度，角度的单位为弧度。这个函数能够处理所有的象限，因此可以返回从 -π 到 π 之间的值。<code>Math.atan2</code> 函数在计算几何、游戏开发、图形编程以及需要处理极坐标转换等场景中非常有用。与 <code>Math.atan</code> 不同，<code>Math.atan2</code> 可以处理 (x, y) 都为零的情况，并根据 x 和 y 的符号确定正确的象限。</p><h2 id="完整实现代码">完整实现代码</h2><pre><code><span class="line" line="1"><span class="sbsja">function</span><span class="sGLFI"> getVideoMetaInfo</span><span class="sP7_E">(</span><span class="s99_P">file</span><span class="smGrS">:</span><span class="sbgvK"> File</span><span class="sP7_E">)</span><span class="smGrS">:</span><span class="sbgvK"> Promise</span><span class="sP7_E"><</span><span class="sZMiF">any</span><span class="sP7_E">></span><span class="sP7_E"> {
</span></span><span class="line" line="2"><span class="sVHd0">  return</span><span class="smGrS"> new</span><span class="sZMiF"> Promise</span><span class="skxfh">(</span><span class="sP7_E">(</span><span class="s99_P">resolve</span><span class="sP7_E">,</span><span class="s99_P"> reject</span><span class="sP7_E">)</span><span class="sbsja"> =></span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="sbsja">    const</span><span class="s_hVV"> mp4boxFile</span><span class="smGrS"> =</span><span class="su5hD"> MP4Box</span><span class="sP7_E">.</span><span class="sGLFI">createFile</span><span class="skxfh">()
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="su5hD">    mp4boxFile</span><span class="sP7_E">.</span><span class="sGLFI">onReady</span><span class="smGrS"> =</span><span class="sbsja"> function</span><span class="sP7_E"> (</span><span class="s99_P">info</span><span class="smGrS">:</span><span class="sZMiF"> any</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="6"><span class="sVHd0">      if</span><span class="skxfh"> (</span><span class="su5hD">info</span><span class="smGrS"> &&</span><span class="su5hD"> info</span><span class="sP7_E">.</span><span class="su5hD">videoTracks</span><span class="sP7_E">?.</span><span class="s_hVV">length</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="7"><span class="sbsja">        const</span><span class="s_hVV"> videoTrack</span><span class="smGrS"> =</span><span class="su5hD"> info</span><span class="sP7_E">.</span><span class="su5hD">videoTracks</span><span class="skxfh">[</span><span class="srdBf">0</span><span class="skxfh">]
</span></span><span class="line" line="8"><span class="sbsja">        const</span><span class="s_hVV"> result</span><span class="smGrS"> =</span><span class="sP7_E"> {
</span></span><span class="line" line="9"><span class="skxfh">          duration</span><span class="sP7_E">:</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">duration</span><span class="smGrS"> /</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">timescale</span><span class="sP7_E">,
</span></span><span class="line" line="10"><span class="skxfh">          codec</span><span class="sP7_E">:</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">codec</span><span class="sP7_E">,
</span></span><span class="line" line="11"><span class="skxfh">          fps</span><span class="sP7_E">:</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">nb_samples</span><span class="smGrS"> /</span><span class="skxfh"> (</span><span class="su5hD">videoTrack</span><span class="sP7_E">.</span><span class="su5hD">duration</span><span class="smGrS"> /</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">timescale</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="12"><span class="skxfh">          width</span><span class="sP7_E">:</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">video</span><span class="sP7_E">.</span><span class="su5hD">width</span><span class="sP7_E">,
</span></span><span class="line" line="13"><span class="skxfh">          height</span><span class="sP7_E">:</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">video</span><span class="sP7_E">.</span><span class="su5hD">height</span><span class="sP7_E">,
</span></span><span class="line" line="14"><span class="skxfh">          rotation</span><span class="sP7_E">:</span><span class="su5hD"> Math</span><span class="sP7_E">.</span><span class="sGLFI">atan2</span><span class="skxfh">(</span><span class="su5hD">videoTrack</span><span class="sP7_E">.</span><span class="su5hD">matrix</span><span class="skxfh">[</span><span class="srdBf">1</span><span class="skxfh">]</span><span class="sP7_E">,</span><span class="su5hD"> videoTrack</span><span class="sP7_E">.</span><span class="su5hD">matrix</span><span class="skxfh">[</span><span class="srdBf">0</span><span class="skxfh">]) </span><span class="smGrS">*</span><span class="skxfh"> (</span><span class="srdBf">180</span><span class="smGrS"> /</span><span class="su5hD"> Math</span><span class="sP7_E">.</span><span class="s_hVV">PI</span><span class="skxfh">)</span><span class="sP7_E">,
</span></span><span class="line" line="15"><span class="sP7_E">        }
</span></span><span class="line" line="16"><span emptyLinePlaceholder>
</span></span><span class="line" line="17"><span class="sGLFI">        resolve</span><span class="skxfh">(</span><span class="su5hD">result</span><span class="skxfh">)
</span></span><span class="line" line="18"><span class="sP7_E">      }
</span></span><span class="line" line="19"><span class="sP7_E">    }
</span></span><span class="line" line="20"><span emptyLinePlaceholder>
</span></span><span class="line" line="21"><span class="su5hD">    mp4boxFile</span><span class="sP7_E">.</span><span class="sGLFI">onError</span><span class="smGrS"> =</span><span class="sbsja"> function</span><span class="sP7_E"> (</span><span class="s99_P">info</span><span class="smGrS">:</span><span class="sZMiF"> any</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="22"><span class="su5hD">      console</span><span class="sP7_E">.</span><span class="sGLFI">error</span><span class="skxfh">(</span><span class="sjJ54">'</span><span class="s_sjI">mp4box.js error</span><span class="sjJ54">'</span><span class="sP7_E">,</span><span class="su5hD"> info</span><span class="skxfh">)
</span></span><span class="line" line="23"><span class="sGLFI">      reject</span><span class="skxfh">(</span><span class="su5hD">info</span><span class="skxfh">)
</span></span><span class="line" line="24"><span class="sP7_E">    }
</span></span><span class="line" line="25"><span emptyLinePlaceholder>
</span></span><span class="line" line="26"><span class="sbsja">    const</span><span class="s_hVV"> reader</span><span class="smGrS"> =</span><span class="su5hD"> file</span><span class="sP7_E">.</span><span class="sGLFI">stream</span><span class="skxfh">()</span><span class="sP7_E">.</span><span class="sGLFI">getReader</span><span class="skxfh">()
</span></span><span class="line" line="27"><span class="sbsja">    let</span><span class="su5hD"> offset</span><span class="smGrS"> =</span><span class="srdBf"> 0
</span></span><span class="line" line="28"><span class="su5hD">    reader</span><span class="sP7_E">.</span><span class="sGLFI">read</span><span class="skxfh">()</span><span class="sP7_E">.</span><span class="sGLFI">then</span><span class="skxfh">(</span><span class="sbsja">function</span><span class="sGLFI"> getNextChunk</span><span class="sP7_E">({</span><span class="s99_P"> done</span><span class="sP7_E">,</span><span class="s99_P"> value</span><span class="sP7_E"> }</span><span class="smGrS">:</span><span class="sZMiF"> any</span><span class="sP7_E">)</span><span class="sP7_E"> {
</span></span><span class="line" line="29"><span class="sVHd0">      if</span><span class="skxfh"> (</span><span class="su5hD">done</span><span class="skxfh">) </span><span class="sP7_E">{
</span></span><span class="line" line="30"><span class="su5hD">        mp4boxFile</span><span class="sP7_E">.</span><span class="sGLFI">flush</span><span class="skxfh">()
</span></span><span class="line" line="31"><span class="sVHd0">        return
</span></span><span class="line" line="32"><span class="sP7_E">      }
</span></span><span class="line" line="33"><span emptyLinePlaceholder>
</span></span><span class="line" line="34"><span class="sbsja">      const</span><span class="s_hVV"> copy</span><span class="smGrS"> =</span><span class="su5hD"> value</span><span class="sP7_E">.</span><span class="su5hD">buffer
</span></span><span class="line" line="35"><span class="su5hD">      copy</span><span class="sP7_E">.</span><span class="su5hD">fileStart</span><span class="smGrS"> =</span><span class="su5hD"> offset
</span></span><span class="line" line="36"><span class="su5hD">      offset</span><span class="smGrS"> +=</span><span class="su5hD"> value</span><span class="sP7_E">.</span><span class="s_hVV">length
</span></span><span class="line" line="37"><span class="su5hD">      mp4boxFile</span><span class="sP7_E">.</span><span class="sGLFI">appendBuffer</span><span class="skxfh">(</span><span class="su5hD">copy</span><span class="skxfh">)
</span></span><span class="line" line="38"><span class="su5hD">      reader</span><span class="sP7_E">.</span><span class="sGLFI">read</span><span class="skxfh">()</span><span class="sP7_E">.</span><span class="sGLFI">then</span><span class="skxfh">(</span><span class="su5hD">getNextChunk</span><span class="skxfh">)
</span></span><span class="line" line="39"><span class="sP7_E">    }</span><span class="skxfh">)
</span></span><span class="line" line="40"><span class="sP7_E">  }</span><span class="skxfh">)
</span></span><span class="line" line="41"><span class="sP7_E">}
</span></span></code></pre><h2 id="后记">后记</h2><p>在解决这个问题的过程中，我发现另一个强大的 JS 库：<a href="https://github.com/buzz/mediainfo.js" rel="nofollow">mediainfo.js</a>，已经帮我们做好了这一切，并且支持解析更多格式的文件。</p><p>我基于这个库，做了一个可视化的工具页：<a href="https://tools.yuanfen.net/metadata" rel="nofollow">媒体文件元数据解析</a>，方便解析媒体文件的元数据，纯浏览器本地解析，性能优异，并且不需要读完整个文件，读完头就可以了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[W3C 技术分享 —— 《前端在数字人创作工具中的应用实践》]]></title>
        <id>/posts/2024/webvolve-series-events-speech</id>
        <link href="https://hadb.me/posts/2024/webvolve-series-events-speech"/>
        <updated>2024-05-29T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240529.webvolve-series-events-speech/cover.jpg" alt="封面" /><h2 id="会议背景">会议背景</h2><p>「<a href="https://www.w3.org/2024/01/webevolve-series-events/media/zh.index.html" rel="nofollow">Web 进化论 - 2024 年度大会</a>」是由 W3C 中国（北京航空航天大学）发起的 “Web 进化论” 系列专题活动之一。本次活动围绕机器学习、WebGPU 与媒体技术展开，重点聚焦多媒体 Web 技术、媒体传输、机器学习、元宇宙、沉浸式 Web、以及 GPU 在 Web 标准化领域的应用。与会者将在此分享多媒体 Web 技术的最新动态，探讨媒体传输的技术趋势，并深入发掘如何利用机器学习与 GPU 加速技术来推动 Web 标准化进程，进而赋能产业发展。大会旨在征集来自国内产业界的技术用例与标准需求，加强行业间的交流合作，促进前沿技术与标准的融合，推动相关标准的制定与实施。</p><h2 id="演讲内容">演讲内容</h2><p>我分享的主题是《前端在数字人创作工具中的应用实践》。</p><blockquote><p>摘要：邓斌（哔哩哔哩资深工程师）展示了基于音色克隆和数字分身技术的“必剪 Studio”智能创作平台，介绍了绿幕抠像的原理、流程、技术挑战和应对方案，以及音视频合成、音频波形可视化、音频在线转码、SSML 可视化编辑器等应用实践（参见<a href="https://www.w3.org/2024/01/webevolve-series-events/media/slides/deng-bin.pdf" rel="nofollow">演示文稿</a>、现场视频：<a href="https://www.bilibili.com/video/BV1yT421a7Eb/" rel="nofollow">B 站</a>、<a href="https://youtu.be/DbGPi54nIv4?si=Akb1pnWDyF-fjniL" rel="nofollow">YouTube</a>）。</p></blockquote>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[日记 - 20240611]]></title>
        <id>/posts/2024/diary-20240611</id>
        <link href="https://hadb.me/posts/2024/diary-20240611"/>
        <updated>2024-06-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近的工作压力较大，压力大的原因主要在于，问题不可控，项目核心依赖的内部团队不太靠谱，不在我所能控制范围内，但汇报压力、以及线上问题的压力均会落在我身上。]]></summary>
        <content type="html"><![CDATA[<p>最近的工作压力较大，压力大的原因主要在于，问题不可控，项目核心依赖的内部团队不太靠谱，不在我所能控制范围内，但汇报压力、以及线上问题的压力均会落在我身上。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[subsystem request failed on channel]]></title>
        <id>/posts/2024/subsystem-request-failed-on-channel</id>
        <link href="https://hadb.me/posts/2024/subsystem-request-failed-on-channel"/>
        <updated>2024-06-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[问题：]]></summary>
        <content type="html"><![CDATA[<p>问题：</p><p>以下代码是创建文件目录，并复制文件到 nas</p><pre><code><span class="line" line="1"><span class="su5hD">DEST_DIR</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">/volume3/docker/</span><span class="sjJ54">$(</span><span class="sbgvK">dirname</span><span class="sjJ54"> ${</span><span class="su5hD">FILE</span><span class="smGrS">#</span><span class="su5hD">synology</span><span class="smGrS">/</span><span class="su5hD">docker</span><span class="smGrS">/</span><span class="sjJ54">})"
</span></span><span class="line" line="2"><span class="sptTA">echo</span><span class="sjJ54"> "</span><span class="s_sjI">准备创建目录 </span><span class="su5hD">$DEST_DIR</span><span class="sjJ54">"
</span></span><span class="line" line="3"><span class="sbgvK">ssh</span><span class="stzsN"> -p</span><span class="srdBf"> 5110</span><span class="s_sjI"> root@192.168.31.10</span><span class="sjJ54"> "</span><span class="s_sjI">mkdir -p </span><span class="su5hD">$DEST_DIR</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sptTA">echo</span><span class="sjJ54"> "</span><span class="s_sjI">准备复制 </span><span class="su5hD">$FILE</span><span class="s_sjI"> 到 </span><span class="su5hD">$DEST_DIR</span><span class="sjJ54">"
</span></span><span class="line" line="6"><span class="sbgvK">scp</span><span class="stzsN"> -P</span><span class="srdBf"> 5110</span><span class="su5hD"> $FILE </span><span class="s_sjI">root@192.168.31.10:</span><span class="su5hD">$DEST_DIR
</span></span></code></pre><p>ssh 命令没问题，但 scp 命令报如下错：</p><pre><code><span class="line" line="1"><span class="sbgvK">subsystem</span><span class="s_sjI"> request</span><span class="s_sjI"> failed</span><span class="s_sjI"> on</span><span class="s_sjI"> channel</span><span class="srdBf"> 0
</span></span><span class="line" line="2"><span class="sbgvK">scp:</span><span class="s_sjI"> Connection</span><span class="s_sjI"> closed
</span></span></code></pre><p>研究了半天，发现加个 <code>-O</code> 就可以解决了：</p><pre><code><span class="line" line="1"><span class="sbgvK">scp</span><span class="stzsN"> -O</span><span class="stzsN"> -P</span><span class="srdBf"> 5110</span><span class="su5hD"> $FILE </span><span class="s_sjI">root@192.168.31.10:</span><span class="su5hD">$DEST_DIR
</span></span></code></pre><p>参考：<a href="https://stackoverflow.com/questions/74311661/subsystem-request-failed-on-channel-0-scp-connection-closed" rel="nofollow">subsystem request failed on channel 0 scp: Connection closed</a></p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[思维切片 - 20240801]]></title>
        <id>/posts/2024/slice-of-mind-20240801</id>
        <link href="https://hadb.me/posts/2024/slice-of-mind-20240801"/>
        <updated>2024-08-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近脑海里有个「思维切片」想法，源自于「视频切片」。视频切片是指从很长的一段视频中切出一个片段，比如从冗长的直播回放中切出一段精彩的内容出来。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240801.slice-of-mind-20240801/cover.jpg" alt="封面" /><p>最近脑海里有个「思维切片」想法，源自于「视频切片」。视频切片是指从很长的一段视频中切出一个片段，比如从冗长的直播回放中切出一段精彩的内容出来。</p><p>而我们的思维也可以切片，记录当下脑海中的一些想法和观点，不一定是针对某一件事，而是记录当下对于一些问题的看法和观点。记录下来，以便自己日后回顾，往大点说可以作为时代的一个缩影。这个时代，通过多媒体记录生活中的事物和事件比较常见，但对于思维本身的记录则比较少，普通人很少去做这样的事情。这个事情还是有意义，比如现在的年轻人，相比较于十年前的我们那一代，对于工作、结婚、生娃等方面的想法，已经大不相同。如果有很多人在这个时代记录下来自己的思维切片，那么未来的人们就可以通过这些思维切片来了解这个时代的人们是怎么想的，是怎么看待这个世界的。</p><p>尝试一下这种记录方式，搞一个专题，定期做切片。这样想写点什么就不一定非要有个主题了，也能增加我的写作频率。即便是水的文章，也当做是一个记录吧。</p><h2 id="博客迁移">博客迁移</h2><p>最近的精力主要在做博客迁移这件事情，原先最早在大学时代开始搭建自己的博客，用的 WordPress，后来因为 WordPress 太重，换成了 Hexo，再后来关注到 Ghost，一直在等待 Ghost 的正式版，2017 年 Ghost 正式版一出来之后就立马切到了 Ghost。一直用到现在，七八年的时间了。</p><p>Ghost 有一点不太好，对于我这种习惯通过 Markdown 文件来编辑和保存文档的人来说，需要同时维护两个地方，在本地 Markdown 编辑完再复制到 Ghost 中发布，需要修改的话，也得改两个地方。再加上 Ghost 的主题修改实在不太方便，官方的主题也是一般，所以一直想重新搞一套博客系统。直到看到 <a href="https://content.nuxt.com/" rel="nofollow">Nuxt Content</a>，了解下发现很方便，完全满足我的要求，并且也是我熟悉的 Nuxt 框架，所以就决定用 Nuxt Content 来重新搭建博客系统。</p><p>另外还有一个促使我更换博客系统的原因是，我近期把个人域名从阿里云转到 Cloudflare 了，成本比阿里云还低，并且不再想走国内部署方式了。国内的备案体系太蛋疼了，举一个例子，之前我花了很多精力做的「星际战区」项目，在星际 2 的社区内还是比较受欢迎的，爬暴雪的 API，将所有人的 MMR 放到一个排行榜里，支持搜索个人、战队甚至星际酒馆 RPG 的排名等。但由于外服的玩家昵称没有审查机制，导致个别违规昵称被爬虫爬到并且展示在我的网站上，然后网信办电话就来了，说涉及到一号领导人，问题很严重，当时被迫决定暂时关站。但后来想把服务器放国外重启都重启不了了，因为阿里云把我那个域名禁止解析、禁止转出了，太狠了。这也是我决定不再把个人域名放在国内域名服务商的原因。</p><h2 id="关于-yak-shaving">关于 Yak Shaving</h2><p>在用 Nuxt Content 重新搭建我的博客的过程中，用到了一些 nuxt 生态的 module，遇到了一些问题，并且给几个项目也提交了 PR，还跟 <a href="https://antfu.me/" rel="nofollow">Anthony Fu</a> 在 GitHub 上进行了对话。我还是比较喜欢和崇拜他的，之前还给他的 <a href="https://opencollective.com/antfu7" rel="nofollow">开源基金会</a> 捐款了。特别喜欢他的一篇文章《<a href="https://antfu.me/posts/about-yak-shaving-zh" rel="nofollow">关于 Yak Shaving</a>》，我看了之后特别有感觉，并且最近正在按这个思路去实践。</p><blockquote><p>Yak Shaving 的字面意思是为剪牦牛毛，而引申出来的意思是，当你在进行一个工作时，发现另一个工作还没有完成，你便先去解决那个工作，在进行那个工作时，你又发现另一个工作…… 如此往复，让你偏离了原本本该完成的工作，最终却也什么都没有完成。</p></blockquote><p>这个现象在我身上特别常见，虽然说 Yak Shaving 本身指的是一件负面的场景，但利用好 Yak Shaving，可以不断给自己动力去做新的东西，因为他们都是为了解决问题而不得不做的。而且我发现通过这种方式去做的事情，效率会特别高，并且会有很多意外的收获。后面打算继续坚持下去，不断通过这种方式去做促进自己做一些对开源社区有意义的事情。</p><h2 id="关于奥运会">关于奥运会</h2><p>最近奥运会如火如荼的在展开，但我现在对于奥运会这件事情的关注程度仅限于看看新闻了，不像小时候那样，会看电视，看比赛了。现在这个社会，信息太多了，娱乐方式也太多了，并且现在的体育竞技，感觉也没有以前那么纯粹了，裹挟了太多商业利益甚至政治因素。</p><h2 id="关于开车">关于开车</h2><p>最近天太热了，我开始开车上下班了，公司楼下停车场需要排半年以上，先办了附近君庭广场的车位。开车上下班感觉还是很爽的，不过我的消费观念还没有跟上，等天气不热了，我估计还是会坐地铁上下班，因为时间成本差不多的，都是半小时多一点，但开车每个月额外增加了一两千的成本。本着能省就省的原则，不热了再换回地铁。</p><h2 id="关于风暴之门">关于风暴之门</h2><p>这两天风暴之门 EA 版上线了，作为最早一批众筹的玩家，花了 400 多，昨天试完了一下战役，感觉挺失望的，做工和十年前的星际相比，太差了，人物太丑就不说了，过场动画人物的嘴都是不动的。现在这些游戏，半成品都没做出来就开始众筹卖钱，等个大半年发个半成品出来，才粗制滥造了，很失望。只能说看看正式版之后的成色了，但就目前这个样子来看，跟星际 2 还是差距太多。尽管有暴雪老员工加持，但毕竟还是小作坊，跟十年前的暴雪无法同日而语。</p><h2 id="关于游戏">关于游戏</h2><p>去年 9 月写了篇文章《<a href="../2023/three-small-things">三件小事</a>》，当时耳机坏了，而且那时候熬夜特别多，有了下面的感慨：</p><blockquote><p>想了想，可能是时候告别游戏了。毕竟已经是 30 多岁的人了。而且这几年在游戏上所能获得的快乐也越来越少了。自己越来越菜，一起玩的朋友也越来越少。一直想做一些能够提升自己快乐的事情，而游戏已经是一个对我来讲 ROI 比较低的事情了，需要花很多精力，经常玩到凌晨两三点，透支健康；还会被老婆说；而且从中所获得的快乐和成就感也在日益降低。一直以为我可以一直玩游戏玩下去，但总有那么几个时刻，脑海中会闪现出这样的念头，是时候“告别”游戏了。当然，我所指的告别，也不是说一点都不玩，而是说不再像以前一样，每天投入几个小时去玩。</p></blockquote><p>回顾近一年，游戏频率确实下降了很多，主要还是星际 2 这个游戏还是太看状态了，长时间不打，就很难打，毕竟是竞技游戏，需要不断练习，并且注定会有输赢。最近重拾了 PC 上的大表哥，准备把他当成休闲时玩的游戏，拿个全成就，打打猎，骑骑马，也是挺放松的。星际 2 还是等国服吧，明年春暖花开时。总的来说，我的游戏就是：竞技游戏玩星际，战役合作等风暴之门，休闲消磨时间玩大表哥，手机无聊玩会炉石（PS: 手机上前几个月一直在玩逐鹿，现在已经逐渐提不起兴趣）。</p><h2 id="关于大环境">关于大环境</h2><p>互联网环境这几年确实每况愈下，5 年前的我绝对不会担心工作的问题。现在不同了，即便个人能力再强，在这个大环境下，竞争压力也会特别大。听到过很多大龄码农被迫转行或者在多个公司之间颠沛流离的故事。根本原因还是国家整体经济下行。昨天难产的上海 2023 年平均工资终于出来了，相比往年晚了一个月。最终结果是 <code>上海市2023年度全口径城镇单位就业人员平均工资为12307元/月。</code> 这个数据的参考性要打个问号，电子化时代，平均工资这件事情需要拖延这么久才能算出来，有多少时间是花在研究调整统计口径上？存款利率下调、房市下跌、股市萎靡、降薪、裁员、找不到工作、网约车司机爆满等等，这些方方面面都体现着经济在走下坡路。经济不是光靠 GDP、平均工资这些数据来体现的，数据口径的可操作的空间太大了。同样一个指标，不同的口径可以得出截然不同的数据。</p><p>在这样的大环境下，具体到个人，关键词就是：苟。追求升职加薪，在现在这个环境下并不是一件容易的事情，甚至可能应该需要避免去升职加薪。职位越高薪资越高，风险也越高。没有靠山，还是做个中层比较安全和高性价比。长期的稳定性比跳槽获取短期的加薪，更加重要。从各大公司人员流动的情况来看，大家都在做着类似的选择。更多的人选择苟在一个公司，跳槽频率降了，很少有人出去，也很少有人进来，缺少了新鲜血液，团队的流动性和积极性都有下降。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[博客从 Ghost 迁移至 Nuxt Content]]></title>
        <id>/posts/2024/switch-blog-from-ghost-to-nuxt-content</id>
        <link href="https://hadb.me/posts/2024/switch-blog-from-ghost-to-nuxt-content"/>
        <updated>2024-08-07T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<h3 id="凌晨">凌晨</h3><p>最近花了很多精力在迁移博客系统，从 Ghost 迁移至基于 Nuxt Content 自研的博客系统。</p><p>目前样式和功能基本搞好了，刚才也完成了 2012 年以来所有的文章的重新录入工作。</p><p>暂时先缓口气。明天看看能不能把评论数据迁移过来。然后就可以考虑把域名切过来了。</p><hr></hr><h3 id="下午">下午</h3><p>目前存在的一些暂时无法解决的问题：</p><ol><li>Feed 订阅目前 Nuxt 官方插件 <a href="https://nuxt.com/modules/feed" rel="nofollow">@nuxtjs/feed</a> 暂未支持 Nuxt 3，可参见这个 issue：<a href="https://github.com/nuxt-community/feed-module/issues/106" rel="nofollow">Nuxt 3 Support</a>，有几个大佬做过尝试，经历了非常漫长的过程，但最终都没完成。我大致看了下历史，目前主要卡点还是 content 目前没有提供在服务端生成 html 的方法，没办法生成 feed 的全文内容。所以目前自己接 <a href="https://github.com/jpmonette/feed" rel="nofollow">feed</a> 库去实现也只能显示摘要，无法生成全文，这个问题暂时没有找到更好的解决方案。</li><li>CloudFlare Pages 部署的时候，worker bundle 体积太大，导致部署失败，主要原因是 <code>Shiki</code> 体积太大，参考 issue：<a href="https://github.com/nuxt/content/issues/2422" rel="nofollow">This module add more than 2.5MB to the output</a>。目前解法是用 <code>pnpm generate</code> 部署纯静态模式。</li><li>纯静态模式部署有个问题，是当我的文章 tag 带了特殊符号的时候，比如 <code>C#</code>、<code>CI/CD</code> 这样的 tag，在路由上通过 <code>encodeURIComponent</code> 处理后，prerender 出来的文件名会变成 <code>C%23</code>、<code>CI%2FCI</code>，这样的文件名在 CloudFlare Pages 上会导致 404。目前通过自己限制标签不带特殊符号来解决。</li></ol><hr></hr><h3 id="晚上">晚上</h3><p>评论数据已经迁移过来了；准备把历史的 PV 数据也迁移过来，不过之前没有页面 PV 的计数器，只能从 Google Analytics 上导出数据，不过只有去年 4 月份迁移了 GA4 的数据，之前的数据都没了。以前也没怎么在意这些数据，不过现在回顾的时候发现没有了这些数据还是挺可惜的。</p><p>拉了从 23 年 4 月至今的 GA4 的数据，分析了下，还挺有意思：</p><p>浏览量排名前十的文章：</p><table><thead><tr><th>页面</th><th>PV</th><th>UV</th><th>平均浏览次数</th><th>平均互动时长</th></tr></thead><tbody><tr><td><a href="../2021/fastboot-failed-remote-operation-not-permitted">fastboot FAILED (remote: Operation not permitted) 的问题</a></td><td>1140</td><td>857</td><td>1.330221704</td><td>20.03967328</td></tr><tr><td><a href="../2016/bad-request-invalid-hostname">“Bad Request - Invalid Hostname”的解决办法</a></td><td>787</td><td>729</td><td>1.079561043</td><td>17.31550069</td></tr><tr><td><a href="../2021/synology-letsencrypt-multiple-domain-cert-configuration">群晖 Let's Encrypt 配置多个泛域名 SSL 证书自动更新</a></td><td>667</td><td>485</td><td>1.375257732</td><td>30.11958763</td></tr><tr><td><a href="../2020/k8s-cert-manager-tls">k8s 上利用 cert-manager 自动签发 TLS 证书</a></td><td>429</td><td>376</td><td>1.140957447</td><td>21.03191489</td></tr><tr><td><a href="../2019/solve-pycharm-adding-pipenv-error">解决 PyCharm 设置 pipenv 报错的问题</a></td><td>367</td><td>327</td><td>1.122324159</td><td>23.82568807</td></tr><tr><td><a href="../2015/change-domain-in-weixin">微信公众号中更换域名</a></td><td>324</td><td>303</td><td>1.069306931</td><td>12.46534653</td></tr><tr><td><a href="../2020/android-webview-picture-cache">安卓 WebView 图片离线缓存方案</a></td><td>300</td><td>247</td><td>1.214574899</td><td>22.47773279</td></tr><tr><td><a href="../2016/aliyun-cdn-not-support-sni">解决阿里云 CDN 回源 https 返回 503 错误的问题</a></td><td>271</td><td>253</td><td>1.071146245</td><td>16.31620553</td></tr><tr><td><a href="../2020/batch-edit-acl-for-oss">批量修改阿里云 OSS 的 ACL 权限</a></td><td>240</td><td>173</td><td>1.387283237</td><td>13.13872832</td></tr><tr><td><a href="../2019/centos-wire-ieee8021x-config">CentOS 有线网卡配置 IEEE 802.1X 上网</a></td><td>224</td><td>152</td><td>1.473684211</td><td>41.42763158</td></tr></tbody></table><p>其中排名第一的这篇文章还包含了多种语言的标题数据，应该是通过网页翻译之后的结果，看来还帮助到了一些国际友人：</p><ul><li>Problema fastboot FAILED (remoto: operación no permitida)</li><li>fastboot FAILED (remote: Operation not permitted) problem</li><li>fastboot FAILED (remote: Operation not permitted) 的問題</li><li>fastboot FAILED (remoto: operación no permitida) problema</li><li>fastboot FAILED (удаленный: операция не разрешена) проблема</li><li>fastboot FAILED(원격: 작업이 허용되지 않음) 문제</li><li>problem fastboot FAILED (zdalny: operacja niedozwolona).</li><li>problème de fastboot FAILED (à distance: opération non autorisée)</li></ul><p>另外一个数据是，平均互动时长最长的一篇文章是：<a href="../2023/shanghai-luohu">上海居转户落户完整经历</a>，达到了 <code>105.8</code> 秒，平均每个人看了约 2 分钟。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[编程初学者学习、就业指南]]></title>
        <id>/posts/2024/guide-for-beginners</id>
        <link href="https://hadb.me/posts/2024/guide-for-beginners"/>
        <updated>2024-08-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近面试了一些实习生，发现很多同学对于如何学习、如何找工作还是有不少困惑。这里我总结了一些经验，希望对初学者有所帮助。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240815.guide-for-beginners/cover.jpg" alt="封面" /><p>最近面试了一些实习生，发现很多同学对于如何学习、如何找工作还是有不少困惑。这里我总结了一些经验，希望对初学者有所帮助。</p><p>经验这个东西，需要时间去摸索，需要实践去试错，在不断尝试不断学习中才能获得。如果在初学阶段能够有指路人提供一些建议和方向，那绝对会少走很多弯路。</p><h2 id="技术要靠自学">技术要靠自学</h2><p>一个非常突出的矛盾点是，学校所教的内容与实际工作所需完全脱节。这一点在我上学的时候就是这样，现在依然如此。不过我意识得比较早，在我刚接触学校专业课程之后就发现，这些课程太理论，教材太陈旧了，根本无法满足我对技术的需求。所以我在大学期间我基本全部在自学，自己研究技术。</p><p>究其原因，其实也不难理解。国内本科教育主要还是在培养基础理论知识，实践和应用基本都是辅助。在计算机技术这块，教材的更新迭代速度远远比不上技术的发展速度。尤其在前端领域，新框架、新轮子层出不穷，出教材的这些教授学习都来不及，更别说出教材。而且大学的专业也不会分那么细，基本上教一些通用的理论基础、编程基础就差不多了。专业对口肯定是比不上外面的培训班的，那些毕竟是针对就业速成的，都是干货，并且以实战为主。</p><p>所以，对于技术这块，还是要靠自学。尤其是想从事这个行业的话，一定要早做准备，积累技术经验，等到快毕业前再去学习，已经很晚了，和其他早就开始自己钻研技术的同学比起来，差距就会很明显。</p><h2 id="怎么自学技术">怎么自学技术</h2><p>兴趣是最好的老师，这句话绝对没错。对技术感兴趣的同学，甚至不需要指导，就已经在自学的路上了。因为兴趣，你会主动去尝试一些新技术，或者捣鼓一些新玩意，尝试的过程中会遇到各种各样的问题，在解决问题的过程中，你就已经在学习了，并且通过解决问题的过程来学习，效果是最好的。</p><p>对于兴趣没有那么浓厚，不知道该如何下手的同学，该怎么办呢？我的解法是，给自己创造需求。</p><p>比如，给自己建一个网站，用来写写博客，记录学习过程。在这个过程中，你会遇到很多问题。比如如何搭一个网站，用现成的博客系统，还是纯手撸？现成的博客系统的话如何部署？如何绑定域名？手撸的网站前后端分别用什么语言，什么框架，哪些组件？我需要实现哪些功能？页面是纯静态部署，还是带服务端渲染？各种方案各有哪些优缺点？……</p><p>在各种各样的问题中，尝试去找到自己的解决方案，在这个过程中，你就已经在学习各种各样的知识和技术了。</p><h2 id="精力有限需要集中">精力有限，需要集中</h2><p>前面也讲到，技术经验一定是需要时间去积累的，只能不断去学习去实践。但计算机技术上，永远有学不完的技术。对于技术方向的选择上，最好也要聚焦。全栈不是一件坏事，但是对于目前国内的就业环境来说，大部分的工作岗位还是比较细分的，专门的全栈岗位非常少。对于精力有限的同学来说，最好是专注于一个方向去打好基础，有余力再去横向扩展。不过有精力又喜欢折腾的同学，可以尝试各种方向，不要被一种技术所局限，实现目的有各种各样的方式，选择适合自己的就好。</p><p>另外，大学期间还要应对各种考试、作业、实验、论文，还要陪室友打游戏、陪男女朋友吃饭、逛街、看电影，还有各种社团活动，以及其他各种各样的事情。真正留给你自学技术的时间，其实很有限。我当年学校的课基本都是翘课，学业也基本上是应付考试为主，把这些时间都用来研究技术。但尽管如此，我还是花了很多的时间在玩游戏、看电影、社团活动上。人精力是有限的，很多分散精力的事情也是难以避免的。只能说尽可能去集中精力，减少分散精力的事情。</p><p>举一个例子，最近一段时间面试到的一些实习生，很多简历里都有各种各样的竞赛得奖，比如蓝桥杯。我具体不太了解这个竞赛的含金量，但是就我面到的一些同学来看，有很多非常水的同学。与其把精力花在这些竞赛上，不如把这些时间花在学习技术上，打好基础上，效果会更好。（能力特别强的同学除外）</p><h2 id="关于开源">关于开源</h2><p>面试过程中，有几个同学向我讨教经验，说到想做一些开源项目，加入开源社区，问我有没有什么建议。</p><p>我觉得这个想法非常好，但顺序不太对。不是想做开源项目而做开源项目。而是当你遇到一个问题，并且通过你的项目解决了某类问题。这时候如果你想开源，帮助到其他人，那就直接代码提交到 GitHub 开源就完事了，你的项目足够好，能够解决别人的问题，自然而然会有人关注，会有人使用。</p><p>但对于大部分同学来说，很难做到。刚起步的你们，甚至很难遇到真正困难的问题，更别提做出解决这些问题的方案了。</p><p>其实，开源项目也并不是那么遥不可及，很多项目也是由各种各样的贡献者添砖加瓦不断完善不断补充而成。当你用到一些开源项目，并且遇到问题的时候，不要停留在搜索阶段。当你搜不到解决方案的时候，尝试去看下项目源码，看看原因出在哪里，如果这个问题还没有人提过，你可以提一个 issue，如果问题很明确，可以尝试去解决，然后提交 PR。在这样的过程中，你是真正遇到问题，并且去尝试解决问题，这样的经历对于你学习和成长是非常有帮助的，并且在这个过程中你还可以帮助到开源项目，成为开源项目贡献者大军的一员。</p><p>最近我在重新搭建我的博客系统的过程中，用到了 nuxt 生态的一些新项目，并且也遇到了各种各样的问题，给其中多个项目都提交了 PR。在这个过程中，我自己也学到了很多，我觉得对我成长最大的一件事情就是，我对开源项目“祛魅”了。我内心不再觉得那些开源项目有多么神秘，因为他们也是人写的，也是各种各样的贡献者提交 PR 组成的。项目未必能覆盖所有的情况，总会有一些未考虑到的情况，会被你遇到。随着使用的深入，你总会遇到各种各样的问题，这时候主动去找到问题，并且提出解决方案，在不知不觉中，你会发现，原来你已经融入这个社区了。</p><h2 id="关于实习工作">关于实习工作</h2><p>有同学没有实习过，向我了解实习工作到底是什么样的，需要那些能力和知识。</p><p>先说一下我对实习这件事情的理解，实习是一件“双赢”的事情。对于公司来说，实习生是一个廉价的劳动力，可以帮助公司完成一些简单的工作，减轻正式员工的负担。对于实习生来说，实习是一个学习的过程，可以在公司里学到一些实际工作中的技术和流程，也可以锻炼自己的沟通能力、团队协作能力等等。</p><p>因此，其实公司对于实习生的要求没有那么高，主要还是做一些杂事。基础知识扎实，能够独立解决一些简单问题，能够良好沟通协作，就可以了。</p><p>但问题在于现在这个行业的竞争比较激烈，一个 HC 有很多人在竞争，这个情况下只能从里面挑能力更强的人进来。所以有时候，你没拿到那个 offer，并不是公司要求太高，而是你的竞争对手太卷，能力太强。</p><p>在这种情况下，只能不断提高自身的能力，才能增强竞争力，才能有更多的机会拿到心仪的实习 offer。</p><h2 id="关于找工作">关于找工作</h2><p>大厂还是小厂，我觉得有条件的情况下，还是优先选大厂。大厂相对规范，相对稳定，并且有利于提升简历的背景，也利于后面继续换别的大厂工作。一个都是不知名小厂的简历，和另一个都是知名大厂的简历，你觉得哪个简历被用人单位选中的概率更高？</p><p>但是，大厂的竞争会比较激烈，没拿到大厂 offer 的情况下，也可以先去小厂积累经验，等到有了更多的经验和能力，再去尝试大厂。最好不要去外包公司，去了外包公司，基本上就很难再去大厂了。</p><h2 id="简历该怎么写">简历该怎么写</h2><p>最近发现一些简历里面，都不约而同提到一个前端低代码平台，并且结构都很相似。问到实现细节上，又不太答得出来。不确定是不是一些教程上提到的这个项目。</p><p>大学生本身项目经历很少，确实简历内容会相对简单。但与其在简历中写一些这样教程中的看起来很高大上的项目，不如自己去做一些小项目，把这些项目写到简历上。这样的项目，你是真正参与过的，你是真正了解的，你是真正可以讲出来的。这样的项目，会让你在面试的时候更有底气。最好是能直接部署到线上，贴上访问地址，能够直接体验到效果，更有说服力。我记得我当年第一次去面试的时候，带了个 surface pro，给面试官看了下我之前做过的一些作品，面试官当场就说你这也太牛了，轻松通过了面试。</p><h2 id="付费咨询">付费咨询</h2><p>面试的过程中很多同学会问我很多问题，给他们解答之后，都觉得受益匪浅。受他们启发，我感觉可以尝试一下付费咨询。毕竟在这个行业摸爬滚打了这么多年，创过业、待过外企、上市公司，也面试过很多人，对于学习、就业方向有一些自己的看法和经验，可以分享给有需要的同学。</p><p>如果你有关于学习方向、就业方向等相关的问题，可以通过下面的方式联系我，约个时间聊个半小时到一小时。费用的话，先定个 <code>¥198</code> 吧。</p><ul><li>邮箱： <a href="mailto:hi@hadb.me" target="_blank" rel="noopener noreferrer">hi@hadb.me</a></li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于 AI 的思考]]></title>
        <id>/posts/2024/thoughts-about-ai</id>
        <link href="https://hadb.me/posts/2024/thoughts-about-ai"/>
        <updated>2024-08-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[AI 现在很火，LLM 的诞生和火爆，让 AI 达到了前所未有的高度。无数开发者、公司趋之若鹜，都希望能在这个风口分到一杯羹。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2024/20240815.thoughts-about-ai/cover.jpg" alt="封面" /><p>AI 现在很火，LLM 的诞生和火爆，让 AI 达到了前所未有的高度。无数开发者、公司趋之若鹜，都希望能在这个风口分到一杯羹。</p><p>不禁回忆起好几年前，我在啃吴恩达的机器学习课程时的场景。那时候的我，也对 AI 充满了兴趣，但在尝试了一段时间后，我发现 AI 的门槛太高了，最终也没有坚持下去。</p><p>现在，Dify、扣子这类的编排工具，极大降低了 AI 应用的制作门槛。很多从来没有接触过 AI 开发、甚至没有接触过代码的人，也在尝试进行 AI 相关的应用创造。这些编排工具，有点低代码平台的味道，AI 的低代码平台。</p><p>我认为在这样繁华火爆的背后，也需要去冷静思考。LLM 不是万能，也不是所有的应用场景都适合用 LLM 来实现。有些功能用 LLM 来实现，有些高射炮打蚊子的感觉了，成本也是需要考虑的。</p><p>其实 Dify、扣子这类编排工具，是真正吃了风口的 AI 好应用。他们本身并不依赖 LLM，本质上还是普通应用。他们提供的是 AI 工具，这类工具的需求是真正有需求的地方。而他们的诞生又降低了门槛，让更多的用户涌进来，对他们来说扩大了自身的市场需求。</p><p>我个人还是更喜欢传统的代码来实现功能和需求，更可控，更易于维护。现在的 AI 发展，还没有到能够完全替代传统开发的地步。我认为，未来也达不到这样的地步。AI 可以极大的辅助代码开发，但不会替代传统代码。根本上来看，还是成本问题。如果任何需求底层都是通过 LLM 来实现，那成本是相当高的，也是没有必要的。至少目前 AI 编排工具这类实现的应用，无法去完全替代传统的代码应用。</p><p>现在还有一些 AI 项目在往另一个赛道发展，就是通过描述需求让 AI 生成完整的项目代码。这个方向我是比较认可的，不过对于特别复杂的项目，我感觉可行性还是有限。一个特别略微复杂点的项目，与其通过不断的提示词来描述需求、改进代码，可能还不如直接手撸代码来的方便。完整的复杂项目来说，人工整合还是必不可少的。</p><p>代码生成这个方向，我还是觉得像 GitHub Copilot 这样作为辅助工具的应用场景更大一点。我现在在写代码的时候，因为有 GitHub Copilot 的存在，效率已经得到了大大的提升。但可能由于成本的问题，GitHub Copilot 现在并不会基于整个项目的上下文进行代码提示，似乎还是基于当前文件的上下文，对于整个项目的融会贯通上还不够智能。确实作为商业项目，读取整个项目代码在成本上不太现实，并且 token 长度技术上可能也无法支持。其实也不用读取完整项目，自己调用搜索去根据关键词查找多个文件中需要的部分，作为上下文，可能也是个路子。</p><p>不管怎么说，AI 的发展是个好事情，对于每个人来说都是机会与挑战。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[汽车天窗漏水的经历]]></title>
        <id>/posts/2024/car-leak-experience</id>
        <link href="https://hadb.me/posts/2024/car-leak-experience"/>
        <updated>2024-11-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[昨天早上要起早送儿子去足球比赛，我先下去把车子开出来，车子刚动的时候我发现不对劲，听到水声。我往副驾座位下一看，完蛋，看到地毯湿了。]]></summary>
        <content type="html"><![CDATA[<p>昨天早上要起早送儿子去足球比赛，我先下去把车子开出来，车子刚动的时候我发现不对劲，听到水声。我往副驾座位下一看，完蛋，看到地毯湿了。</p><figure><img src="https://hadb.me/static/posts/2024/20241104.car-leak-experience/01.jpg"></img></figure><p>我扭头往后面一看，更完了，后座下面的水都能养鱼了！后座也有点发霉，水应该就是从后座上面漏下来的。</p><figure><img src="https://hadb.me/static/posts/2024/20241104.car-leak-experience/02.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2024/20241104.car-leak-experience/03.jpg"></img></figure><p>赶紧跟老婆打电话让他们打车去踢球，我得去修车了。</p><p>前两天我还在说这次这个台风“康妮”差点意思，上海给了暴雨橙色预警，但后来雨也没下多大。没想到这么快就被打脸，车子进水了。</p><p>我的车是 2014 款奥迪 A6L，十年车龄了，但开的不多，才四万多公里，车子保养的都挺不错，除了表面划痕之外，里面其他没出过问题。漏水这个问题其实不是第一次遇到，上次就有信号，但是没当回事。上次台风的时候，忘了从哪里回来，带着老婆儿子一起在车上，路上突然下瓢泼大雨，我儿子坐后面跟我说上面漏水，当时一看还真是，滴了几滴水下来，但不严重，后面也没再漏。当时我心想着可能是雨太急，上面排水没来得及下水，才渗了点进来，也就没当回事。</p><p>这次之所以这么严重，还有一个重要原因。就是我们小区停车位很紧张，加上我车不怎么开，平时都是习惯地铁上下班，也很方便，所以车子停在小区电瓶车棚下面最角落的位置，这个位置基本不怎么动，而且不在树下面，树叶没那么多。之前落叶落果季停在树下，车子全是果子太脏了，这个位置我最喜欢。但是在下大雨的时候，就有问题了，电瓶车棚的雨棚是斜坡的，大雨的时候水都是顺着斜坡直接流下来的，刚好直接倒在车顶上，漏水问题就被放大得很严重了。</p><p>背景介绍完了，回到昨天早上，我小心翼翼用藤原拓海弯道漂移不撒一滴水的技术，把车顺利开到了京东养车的店里。路上除了刹车时能听到水从后座涌到前座的声音，启动时水从前座涌到后座的声音，其他也没啥。到了店里，还没到8点，店员刚开门，我把车开上去，跟一个师傅说，我这有个紧急的问题，车子漏水了，帮忙看看。师傅表示最近这问题他们这儿还挺常见，但我这个漏水量确实不少。一般就是车顶防水胶条老化导致的，顶上胶条都换掉，右边前后排座椅拆掉清理、排水。我问了下一整套下来大概多少钱，师傅说大概两三千。我一听，心里反而踏实了，这价格还能接受。我就说那就麻烦师傅了，师傅说好的，你回去等电话吧，我问大概要多久，师傅说看天气，大概三五天，然后我就骑了个哈啰回去了。很少这么早起来在外面晃悠，骑着哈啰在路上享受了一下清晨的新鲜空气。</p><p>事情讲完了，总结一下这件事情带来的经验：</p><ol><li>奇怪的知识增加了：
<ul><li>汽车座位下面是密封的，积水是能养鱼的</li><li>左右排是隔离的，不会互相渗过去，但前后排是相通的</li></ul></li><li>天窗漏水哪怕只是漏几滴，也不要不当回事，千里之堤溃于蚁穴，一旦能漏几滴，就能让你座椅下养鱼</li><li>台风天，车子不要停在雨棚下，尤其是有斜坡的雨棚下</li><li>不要随便立 flag，打脸真的虽迟但到</li></ol>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何管理并自动更新 HomeLab 中的容器]]></title>
        <id>/posts/2024/managing-containers-in-my-homelab</id>
        <link href="https://hadb.me/posts/2024/managing-containers-in-my-homelab"/>
        <updated>2024-12-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[经过一段时间的探索和优化，我已经找到一个特别适合我的方法，来自动化管理我 HomeLab 中的众多容器。一直想写一篇文章来记录一下，拖到今天才开始动笔。]]></summary>
        <content type="html"><![CDATA[<p>经过一段时间的探索和优化，我已经找到一个特别适合我的方法，来自动化管理我 HomeLab 中的众多容器。一直想写一篇文章来记录一下，拖到今天才开始动笔。</p><p>首先交代一下背景，我的 HomeLab 中目前总共管理着 50 多个容器，分布在群晖 NAS 以及另外两台 NUC 的虚拟机中。从最初的直接通过 compose 文件手动管理，到后来使用了 Portainer，再到现在的基于 GitLab CI 的自动化管理方式，逐渐变得更自动化、更方便、也更不容易出错。</p><h2 id="为什么要自动化管理">为什么要自动化管理</h2><p>一开始上 Portainer，是为了解决手动管理多个设备中的容器，频繁 SSH 到不同设备中比较麻烦的问题。用 Portainer 确实可以方便快捷地管理多个设备中的容器，当容器数量比较少的时候，还是非常推荐的。</p><p>随着容器数量的变多，Portainer 上我遇到两个不太好解决的问题：</p><ol><li>容器的自动更新，Portainer 的 Business 版是提供了自动更新的功能的，但可惜 CE 版没有</li><li>容器的配置文件管理、配置和 compose 文件的备份、版本记录等</li></ol><p>第二个问题比较好解决，通过 git 管理 compose 文件和配置文件，结合 GitLab CI 在文件变更的时候自动 SSH 到对应宿主机上执行容器的更新操作。</p><p>第一个问题，是我在本博客项目上使用 renovate 来更新前端依赖的时候，从他们的文章中看到，renovate 也可以检测 Docker 镜像的更新，于是灵光一现，基于上一步所有 compose 文件都已经在 GitLab 中管理了，那再结合 renovate，就可以实现容器的自动更新了。</p><h2 id="最终形态">最终形态</h2><p>中间摸索的步骤由于已经过了差不多几个月了，不太容易复盘了，就把最终的形态分享一下。</p><figure><img src="https://hadb.me/static/posts/2024/20241212.managing-containers-in-my-homelab/01.png"></img></figure><p>如上图，所有容器的配置文件、compose 文件都放在 <code>config-files</code> 这个项目中，该项目通过 GitLab 管理，renovate 在定期检测到镜像更新的时候，会自动提交一个 MR，可以通过一定规则配置成自动合并或手动合并，当然也支持手动修改配置文件或 compose 文件。MR 合并或配置文件修改后触发 GitLab CI，通过 SSH 登录对应宿主机上，执行对应的配置文件更新或容器更新的操作。</p><p>我的 <code>config-files</code> 目录结构：</p><pre><code>.
├── aliyun
│   ├── gateway
│   └── hk
├── homelab
│   ├── scripts
│   ├── synology
│   ├── vm-rocky-01
│   └── vm-rocky-02
├── .gitlab-ci.yml
└── renovate.json
</code></pre><p><code>aliyun</code> 目录中的 <code>gateway</code> 和 <code>hk</code> 是我在杭州和香港的两台服务器，部署一些外网项目，也是通过上述的方式管理。<code>homelab</code> 中的 <code>scripts</code> 是一些脚本，与本文无关，但也是通过 <code>config-files</code> 项目统一管理的。</p><p>我的 <code>renovate.json</code> 如下：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">$schema</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">https://docs.renovatebot.com/renovate-schema.json</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="3"><span class="s39Yj">  "</span><span class="sseR_">extends</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">config:recommended</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">group:allNonMajor</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">:semanticCommitTypeAll(chore)</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="4"><span class="s39Yj">  "</span><span class="sseR_">packageRules</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [
</span></span><span class="line" line="5"><span class="sP7_E">    {
</span></span><span class="line" line="6"><span class="s39Yj">      "</span><span class="sZMiF">matchManagers</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">docker-compose</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="7"><span class="s39Yj">      "</span><span class="sZMiF">matchPackageNames</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">sonatype/nexus3</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">clickhouse/clickhouse-server</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="8"><span class="s39Yj">      "</span><span class="sZMiF">enabled</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="s39Yj"> false
</span></span><span class="line" line="9"><span class="sP7_E">    },
</span></span><span class="line" line="10"><span class="sP7_E">    {
</span></span><span class="line" line="11"><span class="s39Yj">      "</span><span class="sZMiF">matchManagers</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">docker-compose</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="12"><span class="s39Yj">      "</span><span class="sZMiF">matchPackageNames</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">gitlab/gitlab-runner</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="13"><span class="s39Yj">      "</span><span class="sZMiF">versionCompatibility</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">^(?<compatibility>.*)-(?<version>.*)$</span><span class="sjJ54">"
</span></span><span class="line" line="14"><span class="sP7_E">    },
</span></span><span class="line" line="15"><span class="sP7_E">    {
</span></span><span class="line" line="16"><span class="s39Yj">      "</span><span class="sZMiF">matchManagers</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">docker-compose</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="17"><span class="s39Yj">      "</span><span class="sZMiF">matchUpdateTypes</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">minor</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">patch</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="18"><span class="s39Yj">      "</span><span class="sZMiF">automerge</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="s39Yj"> true</span><span class="sP7_E">,
</span></span><span class="line" line="19"><span class="s39Yj">      "</span><span class="sZMiF">schedule</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">before 11am on Monday</span><span class="sjJ54">"</span><span class="sP7_E">]
</span></span><span class="line" line="20"><span class="sP7_E">    }
</span></span><span class="line" line="21"><span class="sP7_E">  ],
</span></span><span class="line" line="22"><span class="s39Yj">  "</span><span class="sseR_">rangeStrategy</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">bump</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="23"><span class="s39Yj">  "</span><span class="sseR_">timezone</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">Asia/Shanghai</span><span class="sjJ54">"
</span></span><span class="line" line="24"><span class="sP7_E">}
</span></span></code></pre><p>里面有一些自定义的规则，比如禁用了 <code>sonatype/nexus3</code> 和 <code>clickhouse/clickhouse-server</code> 的自动更新，以及适配了 <code>gitlab/gitlab-runner</code> 的不规则的版本号。</p><p><code>.gitlab-ci.yml</code> 如下：</p><pre><code><span class="line" line="1"><span class="sQzsp">stages</span><span class="sP7_E">:
</span></span><span class="line" line="2"><span class="sP7_E">  -</span><span class="s_sjI"> pass
</span></span><span class="line" line="3"><span class="sP7_E">  -</span><span class="s_sjI"> deploy
</span></span><span class="line" line="4"><span emptyLinePlaceholder>
</span></span><span class="line" line="5"><span class="sQzsp">pass</span><span class="sP7_E">:
</span></span><span class="line" line="6"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> pass
</span></span><span class="line" line="7"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="8"><span class="sP7_E">    -</span><span class="s_sjI"> echo "Current branch is $CI_COMMIT_BRANCH, pass."
</span></span><span class="line" line="9"><span class="sQzsp">  except</span><span class="sP7_E">:
</span></span><span class="line" line="10"><span class="sP7_E">    -</span><span class="s_sjI"> main
</span></span><span class="line" line="11"><span emptyLinePlaceholder>
</span></span><span class="line" line="12"><span class="sQzsp">deploy</span><span class="sP7_E">:
</span></span><span class="line" line="13"><span class="sQzsp">  stage</span><span class="sP7_E">:</span><span class="s_sjI"> deploy
</span></span><span class="line" line="14"><span class="sQzsp">  before_script</span><span class="sP7_E">:
</span></span><span class="line" line="15"><span class="sP7_E">    -</span><span class="s_sjI"> eval $(ssh-agent -s)
</span></span><span class="line" line="16"><span class="sP7_E">    -</span><span class="s_sjI"> echo "$SSH_PRIVATE_KEY" | ssh-add -
</span></span><span class="line" line="17"><span class="sP7_E">    -</span><span class="s_sjI"> mkdir -p ~/.ssh
</span></span><span class="line" line="18"><span class="sP7_E">    -</span><span class="s_sjI"> echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
</span></span><span class="line" line="19"><span emptyLinePlaceholder>
</span></span><span class="line" line="20"><span class="sQzsp">  script</span><span class="sP7_E">:
</span></span><span class="line" line="21"><span class="sP7_E">    -</span><span class="s_sjI"> MODIFIED_FILES=$(git diff --name-only --diff-filter=ACMRT $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA)
</span></span><span class="line" line="22"><span class="sP7_E">    -</span><span class="s_sjI"> DELETED_FILES=$(git diff --name-status --diff-filter=DR $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | awk '{print $2}')
</span></span><span class="line" line="23"><span class="sP7_E">    -</span><span class="sVHd0"> |
</span></span><span class="line" line="24"><span class="s_sjI">      for FILE in $DELETED_FILES; do
</span></span><span class="line" line="25"><span class="s_sjI">        echo "文件移除 $FILE"
</span></span><span class="line" line="26"><span class="s_sjI">        # 如果文件是 compose.yaml，执行 docker compose down
</span></span><span class="line" line="27"><span class="s_sjI">        if [[ "$FILE" == "*/compose.yaml" ]]; then
</span></span><span class="line" line="28"><span class="s_sjI">          if [[ "$FILE" == "homelab/synology/*" ]]; then
</span></span><span class="line" line="29"><span class="s_sjI">            DEST_DIR="/volume3/docker/$(dirname ${FILE#homelab/})"
</span></span><span class="line" line="30"><span class="s_sjI">            echo "退出容器 synology:$(dirname ${FILE#homelab/synology/})"
</span></span><span class="line" line="31"><span class="s_sjI">            ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && cd $DEST_DIR && docker-compose down"
</span></span><span class="line" line="32"><span class="s_sjI">          fi
</span></span><span class="line" line="33"><span emptyLinePlaceholder>
</span></span><span class="line" line="34"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-01/*" ]]; then
</span></span><span class="line" line="35"><span class="s_sjI">            DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-01/})"
</span></span><span class="line" line="36"><span class="s_sjI">            echo "退出容器 vm-rocky-01:$(dirname ${FILE#homelab/vm-rocky-01/})"
</span></span><span class="line" line="37"><span class="s_sjI">            ssh root@vm-rocky-01.home "cd $DEST_DIR && docker compose down"
</span></span><span class="line" line="38"><span class="s_sjI">          fi
</span></span><span class="line" line="39"><span emptyLinePlaceholder>
</span></span><span class="line" line="40"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-02/*" ]]; then
</span></span><span class="line" line="41"><span class="s_sjI">            DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-02/})"
</span></span><span class="line" line="42"><span class="s_sjI">            echo "退出容器 vm-rocky-02:$(dirname ${FILE#homelab/vm-rocky-02/})"
</span></span><span class="line" line="43"><span class="s_sjI">            ssh root@vm-rocky-02.home "cd $DEST_DIR && docker compose down"
</span></span><span class="line" line="44"><span class="s_sjI">          fi
</span></span><span class="line" line="45"><span emptyLinePlaceholder>
</span></span><span class="line" line="46"><span class="s_sjI">          # if [[ "$FILE" == "aliyun/gateway/*" ]]; then
</span></span><span class="line" line="47"><span class="s_sjI">          #   DEST_DIR="/root/config-files/gateway/$(dirname ${FILE#aliyun/gateway/})"
</span></span><span class="line" line="48"><span class="s_sjI">          #   echo "退出容器 gateway:$(dirname ${FILE#aliyun/gateway/})"
</span></span><span class="line" line="49"><span class="s_sjI">          #   ssh root@gateway.aliyun "cd $DEST_DIR && docker compose down"
</span></span><span class="line" line="50"><span class="s_sjI">          # fi
</span></span><span class="line" line="51"><span class="s_sjI">        fi
</span></span><span class="line" line="52"><span emptyLinePlaceholder>
</span></span><span class="line" line="53"><span class="s_sjI">        # 删除对应文件
</span></span><span class="line" line="54"><span class="s_sjI">        if [[ "$FILE" == "homelab/synology/*" ]]; then
</span></span><span class="line" line="55"><span class="s_sjI">          FILE_PATH="/volume3/docker/${FILE#homelab/}"
</span></span><span class="line" line="56"><span class="s_sjI">          echo "从 Synology 删除文件: $FILE_PATH"
</span></span><span class="line" line="57"><span class="s_sjI">          ssh -p 5110 root@synology.home "rm -f $FILE_PATH"
</span></span><span class="line" line="58"><span class="s_sjI">        fi
</span></span><span class="line" line="59"><span emptyLinePlaceholder>
</span></span><span class="line" line="60"><span class="s_sjI">        if [[  "$FILE" == "homelab/vm-rocky-01/*" ]]; then
</span></span><span class="line" line="61"><span class="s_sjI">          FILE_PATH="/root/${FILE#homelab/vm-rocky-01/}"
</span></span><span class="line" line="62"><span class="s_sjI">          echo "从 vm-rocky-01 删除文件: $FILE_PATH"
</span></span><span class="line" line="63"><span class="s_sjI">          ssh root@vm-rocky-01.home "rm -f $FILE_PATH"
</span></span><span class="line" line="64"><span class="s_sjI">        fi
</span></span><span class="line" line="65"><span emptyLinePlaceholder>
</span></span><span class="line" line="66"><span class="s_sjI">        if [[  "$FILE" == "homelab/vm-rocky-02/*" ]]; then
</span></span><span class="line" line="67"><span class="s_sjI">          FILE_PATH="/root/${FILE#homelab/vm-rocky-02/}"
</span></span><span class="line" line="68"><span class="s_sjI">          echo "从 vm-rocky-02 删除文件: $FILE_PATH"
</span></span><span class="line" line="69"><span class="s_sjI">          ssh root@vm-rocky-02.home "rm -f $FILE_PATH"
</span></span><span class="line" line="70"><span class="s_sjI">        fi
</span></span><span class="line" line="71"><span emptyLinePlaceholder>
</span></span><span class="line" line="72"><span class="s_sjI">        if [[ "$FILE" == "aliyun/gateway/*" ]]; then
</span></span><span class="line" line="73"><span class="s_sjI">          FILE_PATH="/root/config-files/gateway/${FILE#aliyun/gateway/}"
</span></span><span class="line" line="74"><span class="s_sjI">          echo "从 gateway.aliyun 删除文件: $FILE_PATH"
</span></span><span class="line" line="75"><span class="s_sjI">          ssh root@gateway.aliyun "rm -f $FILE_PATH"
</span></span><span class="line" line="76"><span class="s_sjI">        fi
</span></span><span class="line" line="77"><span class="s_sjI">      done
</span></span><span class="line" line="78"><span emptyLinePlaceholder>
</span></span><span class="line" line="79"><span class="s_sjI">      for FILE in $MODIFIED_FILES; do
</span></span><span class="line" line="80"><span class="s_sjI">        echo "文件变更 $FILE"
</span></span><span class="line" line="81"><span class="s_sjI">        if [ -e "$FILE" ]; then
</span></span><span class="line" line="82"><span class="s_sjI">          if [[ "$FILE" == "homelab/synology/*" ]]; then
</span></span><span class="line" line="83"><span class="s_sjI">            DEST_DIR="/volume3/docker/$(dirname ${FILE#homelab/})"
</span></span><span class="line" line="84"><span class="s_sjI">            ssh -p 5110 root@synology.home "mkdir -p $DEST_DIR"
</span></span><span class="line" line="85"><span class="s_sjI">            echo "复制文件 $FILE 到 Synology: $DEST_DIR"
</span></span><span class="line" line="86"><span class="s_sjI">            scp -O -P 5110 $FILE root@synology.home:$DEST_DIR
</span></span><span class="line" line="87"><span class="s_sjI">          fi
</span></span><span class="line" line="88"><span emptyLinePlaceholder>
</span></span><span class="line" line="89"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-01/*" ]]; then
</span></span><span class="line" line="90"><span class="s_sjI">            DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-01/})"
</span></span><span class="line" line="91"><span class="s_sjI">            ssh root@vm-rocky-01.home "mkdir -p $DEST_DIR"
</span></span><span class="line" line="92"><span class="s_sjI">            echo "复制文件 $FILE 到 vm-rocky-01: $DEST_DIR"
</span></span><span class="line" line="93"><span class="s_sjI">            scp -O $FILE root@vm-rocky-01.home:$DEST_DIR
</span></span><span class="line" line="94"><span class="s_sjI">          fi
</span></span><span class="line" line="95"><span emptyLinePlaceholder>
</span></span><span class="line" line="96"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-02/*" ]]; then
</span></span><span class="line" line="97"><span class="s_sjI">            DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-02/})"
</span></span><span class="line" line="98"><span class="s_sjI">            ssh root@vm-rocky-02.home "mkdir -p $DEST_DIR"
</span></span><span class="line" line="99"><span class="s_sjI">            echo "复制文件 $FILE 到 vm-rocky-02: $DEST_DIR"
</span></span><span class="line" line="100"><span class="s_sjI">            scp -O $FILE root@vm-rocky-02.home:$DEST_DIR
</span></span><span class="line" line="101"><span class="s_sjI">          fi
</span></span><span class="line" line="102"><span emptyLinePlaceholder>
</span></span><span class="line" line="103"><span class="s_sjI">          if [[ "$FILE" == "aliyun/gateway/*" ]]; then
</span></span><span class="line" line="104"><span class="s_sjI">            DEST_DIR="/root/config-files/gateway/$(dirname ${FILE#aliyun/gateway/})"
</span></span><span class="line" line="105"><span class="s_sjI">            ssh root@gateway.aliyun "mkdir -p $DEST_DIR"
</span></span><span class="line" line="106"><span class="s_sjI">            echo "复制文件 $FILE 到 gateway.aliyun: $DEST_DIR"
</span></span><span class="line" line="107"><span class="s_sjI">            scp -O $FILE root@gateway.aliyun:$DEST_DIR
</span></span><span class="line" line="108"><span class="s_sjI">          fi
</span></span><span class="line" line="109"><span emptyLinePlaceholder>
</span></span><span class="line" line="110"><span class="s_sjI">          if [[ "$FILE" == "aliyun/hk/nginx/*" ]]; then
</span></span><span class="line" line="111"><span class="s_sjI">            DEST_DIR="/etc/nginx/$(dirname ${FILE#aliyun/hk/nginx/})"
</span></span><span class="line" line="112"><span class="s_sjI">            ssh root@hk.aliyun "mkdir -p $DEST_DIR"
</span></span><span class="line" line="113"><span class="s_sjI">            echo "复制文件 $FILE 到 hk.aliyun: $DEST_DIR"
</span></span><span class="line" line="114"><span class="s_sjI">            scp -O $FILE root@hk.aliyun:$DEST_DIR
</span></span><span class="line" line="115"><span class="s_sjI">          fi
</span></span><span class="line" line="116"><span class="s_sjI">        fi
</span></span><span class="line" line="117"><span class="s_sjI">      done
</span></span><span class="line" line="118"><span emptyLinePlaceholder>
</span></span><span class="line" line="119"><span class="s_sjI">      for FILE in $MODIFIED_FILES; do
</span></span><span class="line" line="120"><span class="s_sjI">        if [ -e "$FILE" ]; then
</span></span><span class="line" line="121"><span class="s_sjI">          # 如果文件是 compose.yaml，执行 docker compose up -d
</span></span><span class="line" line="122"><span class="s_sjI">          if [[ "$FILE" == "*/compose.yaml" ]]; then
</span></span><span class="line" line="123"><span class="s_sjI">            if [[ "$FILE" == "homelab/vm-rocky-01/renovate/compose.yaml" ]]; then
</span></span><span class="line" line="124"><span class="s_sjI">              echo "跳过部署 vm-rocky-01:renovate"
</span></span><span class="line" line="125"><span class="s_sjI">              continue
</span></span><span class="line" line="126"><span class="s_sjI">            fi
</span></span><span class="line" line="127"><span emptyLinePlaceholder>
</span></span><span class="line" line="128"><span class="s_sjI">            if [[ "$FILE" == "homelab/synology/*" ]]; then
</span></span><span class="line" line="129"><span class="s_sjI">              DEST_DIR="/volume3/docker/$(dirname ${FILE#homelab/})"
</span></span><span class="line" line="130"><span class="s_sjI">              echo "部署容器 synology:$(dirname ${FILE#homelab/synology/})"
</span></span><span class="line" line="131"><span class="s_sjI">              ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && cd $DEST_DIR && docker-compose up -d"
</span></span><span class="line" line="132"><span class="s_sjI">            fi
</span></span><span class="line" line="133"><span emptyLinePlaceholder>
</span></span><span class="line" line="134"><span class="s_sjI">            if [[ "$FILE" == "homelab/vm-rocky-01/*" ]]; then
</span></span><span class="line" line="135"><span class="s_sjI">              DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-01/})"
</span></span><span class="line" line="136"><span class="s_sjI">              echo "部署容器 vm-rocky-01:$(dirname ${FILE#homelab/vm-rocky-01/})"
</span></span><span class="line" line="137"><span class="s_sjI">              ssh root@vm-rocky-01.home "cd $DEST_DIR && docker compose up -d"
</span></span><span class="line" line="138"><span class="s_sjI">            fi
</span></span><span class="line" line="139"><span emptyLinePlaceholder>
</span></span><span class="line" line="140"><span class="s_sjI">            if [[ "$FILE" == "homelab/vm-rocky-02/*" ]]; then
</span></span><span class="line" line="141"><span class="s_sjI">              DEST_DIR="/root/$(dirname ${FILE#homelab/vm-rocky-02/})"
</span></span><span class="line" line="142"><span class="s_sjI">              echo "部署容器 vm-rocky-02:$(dirname ${FILE#homelab/vm-rocky-02/})"
</span></span><span class="line" line="143"><span class="s_sjI">              ssh root@vm-rocky-02.home "cd $DEST_DIR && docker compose up -d"
</span></span><span class="line" line="144"><span class="s_sjI">            fi
</span></span><span class="line" line="145"><span emptyLinePlaceholder>
</span></span><span class="line" line="146"><span class="s_sjI">            # if [[ "$FILE" == "aliyun/gateway/*" ]]; then
</span></span><span class="line" line="147"><span class="s_sjI">            #   DEST_DIR="/root/config-files/gateway/$(dirname ${FILE#aliyun/gateway/})"
</span></span><span class="line" line="148"><span class="s_sjI">            #   echo "部署容器 gateway.aliyun:$(dirname ${FILE#aliyun/gateway/})"
</span></span><span class="line" line="149"><span class="s_sjI">            #   ssh root@gateway.aliyun "cd $DEST_DIR && docker compose up -d"
</span></span><span class="line" line="150"><span class="s_sjI">            # fi
</span></span><span class="line" line="151"><span emptyLinePlaceholder>
</span></span><span class="line" line="152"><span class="s_sjI">            echo "容器重新部署完成"
</span></span><span class="line" line="153"><span class="s_sjI">            continue
</span></span><span class="line" line="154"><span class="s_sjI">          fi
</span></span><span class="line" line="155"><span emptyLinePlaceholder>
</span></span><span class="line" line="156"><span class="s_sjI">          # 如果文件是 homelab/synology/nginx/*, 重新启动 nginx
</span></span><span class="line" line="157"><span class="s_sjI">          if [[ "$FILE" == "homelab/synology/nginx/*" ]]; then
</span></span><span class="line" line="158"><span class="s_sjI">            echo "重新启动 synology:nginx 服务"
</span></span><span class="line" line="159"><span class="s_sjI">            ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && docker exec nginx nginx -s reload"
</span></span><span class="line" line="160"><span class="s_sjI">          fi
</span></span><span class="line" line="161"><span emptyLinePlaceholder>
</span></span><span class="line" line="162"><span class="s_sjI">          # 如果文件是 aliyun/gateway/nginx/*, 重新启动 nginx
</span></span><span class="line" line="163"><span class="s_sjI">          if [[ "$FILE" == "aliyun/gateway/nginx/*" ]]; then
</span></span><span class="line" line="164"><span class="s_sjI">            echo "重新启动 gateway.aliyun:nginx"
</span></span><span class="line" line="165"><span class="s_sjI">            ssh root@gateway.aliyun "nginx -s reload"
</span></span><span class="line" line="166"><span class="s_sjI">          fi
</span></span><span class="line" line="167"><span emptyLinePlaceholder>
</span></span><span class="line" line="168"><span class="s_sjI">          # 如果文件是 aliyun/hk/nginx/*, 重新启动 nginx
</span></span><span class="line" line="169"><span class="s_sjI">          if [[ "$FILE" == "aliyun/hk/nginx/*" ]]; then
</span></span><span class="line" line="170"><span class="s_sjI">            echo "重新启动 hk.aliyun:nginx"
</span></span><span class="line" line="171"><span class="s_sjI">            ssh root@hk.aliyun "nginx -s reload"
</span></span><span class="line" line="172"><span class="s_sjI">          fi
</span></span><span class="line" line="173"><span emptyLinePlaceholder>
</span></span><span class="line" line="174"><span class="s_sjI">          # 如果文件是 rinetd.conf, 重新启动 rinetd
</span></span><span class="line" line="175"><span class="s_sjI">          if [[ "$FILE" == "homelab/synology/rinetd/config/rinetd.conf" ]]; then
</span></span><span class="line" line="176"><span class="s_sjI">            echo "重新启动 synology:rinetd 服务"
</span></span><span class="line" line="177"><span class="s_sjI">            ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && cd /volume3/docker/synology/rinetd && docker-compose restart"
</span></span><span class="line" line="178"><span class="s_sjI">          fi
</span></span><span class="line" line="179"><span emptyLinePlaceholder>
</span></span><span class="line" line="180"><span class="s_sjI">          # 如果文件是 gitlab.rb, 重新配置 gitlab
</span></span><span class="line" line="181"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-01/gitlab/config/gitlab.rb" ]]; then
</span></span><span class="line" line="182"><span class="s_sjI">            echo "重新配置 vm-rocky-01:gitlab"
</span></span><span class="line" line="183"><span class="s_sjI">            ssh root@vm-rocky-01.home "docker exec gitlab gitlab-ctl reconfigure"
</span></span><span class="line" line="184"><span class="s_sjI">          fi
</span></span><span class="line" line="185"><span emptyLinePlaceholder>
</span></span><span class="line" line="186"><span class="s_sjI">          # 如果文件是 prometheus.yml, 重新启动 prometheus
</span></span><span class="line" line="187"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-01/prometheus/config/prometheus.yml" ]]; then
</span></span><span class="line" line="188"><span class="s_sjI">            echo "重新启动 vm-rocky-01:prometheus"
</span></span><span class="line" line="189"><span class="s_sjI">            ssh root@vm-rocky-01.home "cd /root/prometheus && docker compose restart"
</span></span><span class="line" line="190"><span class="s_sjI">          fi
</span></span><span class="line" line="191"><span emptyLinePlaceholder>
</span></span><span class="line" line="192"><span class="s_sjI">          # 如果文件是 telegraf.conf, 重新启动 telegraf
</span></span><span class="line" line="193"><span class="s_sjI">          if [[ "$FILE" == "homelab/synology/telegraf/conf/telegraf.conf" ]]; then
</span></span><span class="line" line="194"><span class="s_sjI">            echo "重新启动 synology:telegraf"
</span></span><span class="line" line="195"><span class="s_sjI">            ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && cd /volume3/docker/synology/telegraf && docker-compose restart"
</span></span><span class="line" line="196"><span class="s_sjI">          fi
</span></span><span class="line" line="197"><span emptyLinePlaceholder>
</span></span><span class="line" line="198"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-01/telegraf/conf/telegraf.conf" ]]; then
</span></span><span class="line" line="199"><span class="s_sjI">            echo "重新启动 vm-rocky-01:telegraf"
</span></span><span class="line" line="200"><span class="s_sjI">            ssh root@vm-rocky-01.home "cd /root/telegraf && docker compose restart"
</span></span><span class="line" line="201"><span class="s_sjI">          fi
</span></span><span class="line" line="202"><span emptyLinePlaceholder>
</span></span><span class="line" line="203"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-02/telegraf/conf/telegraf.conf" ]]; then
</span></span><span class="line" line="204"><span class="s_sjI">            echo "重新启动 vm-rocky-02:telegraf"
</span></span><span class="line" line="205"><span class="s_sjI">            ssh root@vm-rocky-02.home "cd /root/telegraf && docker compose restart"
</span></span><span class="line" line="206"><span class="s_sjI">          fi
</span></span><span class="line" line="207"><span emptyLinePlaceholder>
</span></span><span class="line" line="208"><span class="s_sjI">          # 如果文件是 bots/config/clash/*.yaml, 更新 clash 配置
</span></span><span class="line" line="209"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-02/bots/config/clash/*.yaml" ]]; then
</span></span><span class="line" line="210"><span class="s_sjI">            echo "重新启动 vm-rocky-02:bots"
</span></span><span class="line" line="211"><span class="s_sjI">            ssh root@vm-rocky-02.home "cd /root/bots && docker compose restart"
</span></span><span class="line" line="212"><span class="s_sjI">            echo "等待 10 秒"
</span></span><span class="line" line="213"><span class="s_sjI">            sleep 10
</span></span><span class="line" line="214"><span class="s_sjI">            echo "重新启动 synology:clash-premium"
</span></span><span class="line" line="215"><span class="s_sjI">            ssh -p 5110 root@synology.home "export PATH=\$PATH:/usr/local/bin && cd /volume3/docker/synology/clash-premium && docker-compose restart"
</span></span><span class="line" line="216"><span class="s_sjI">          fi
</span></span><span class="line" line="217"><span emptyLinePlaceholder>
</span></span><span class="line" line="218"><span class="s_sjI">          # 如果文件是 artalk/data/artalk.yml, 重新启动 artalk
</span></span><span class="line" line="219"><span class="s_sjI">          if [[ "$FILE" == "homelab/vm-rocky-02/artalk/data/artalk.yml" ]]; then
</span></span><span class="line" line="220"><span class="s_sjI">            echo "重新启动 vm-rocky-02:artalk"
</span></span><span class="line" line="221"><span class="s_sjI">            ssh root@vm-rocky-02.home "cd /root/artalk && docker compose restart"
</span></span><span class="line" line="222"><span class="s_sjI">          fi
</span></span><span class="line" line="223"><span emptyLinePlaceholder>
</span></span><span class="line" line="224"><span class="s_sjI">        fi
</span></span><span class="line" line="225"><span class="s_sjI">      done
</span></span><span class="line" line="226"><span class="sP7_E">    -</span><span class="s_sjI"> echo "部署完成"
</span></span><span class="line" line="227"><span class="sQzsp">  only</span><span class="sP7_E">:
</span></span><span class="line" line="228"><span class="sP7_E">    -</span><span class="s_sjI"> main
</span></span></code></pre><p>其中，<code>SSH_KNOWN_HOSTS</code> 和 <code>SSH_PRIVATE_KEY</code> 存储在 GitLab CI/CD 的变量中，用于 SSH 登录到对应的宿主机上。</p><p>当我需要改某个容器的配置文件时，例如当我需要修改 <code>synology</code> 上的 <code>nginx</code> 配置，我只需要去 <code>config-files</code> 项目中修改 <code>homelab/synology/nginx/nginx.conf</code> 文件，然后 commit，push，等待几秒钟，GitLab CI 就会自动帮我完成配置文件的更新以及 <code>nginx</code> 的重启。</p><p>每周一，renovate 会自动发起并合并更新容器镜像版本的 MR，然后 GitLab CI 会自动帮我更新容器。</p><p>对了，GitLab 、GitLab Runner、Renovate 也都是通过容器私有化部署在 HomeLab 中的，这些就不赘述。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 年度总结]]></title>
        <id>/posts/2025/end-of-2024</id>
        <link href="https://hadb.me/posts/2025/end-of-2024"/>
        <updated>2025-01-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[又是一年过去，但 2024 年我可以比较自豪地说，我达成了很多了不起的成就。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/cover.jpg" alt="封面" /><p>又是一年过去，但 2024 年我可以比较自豪地说，我达成了很多了不起的成就。</p><h3 id="新成就连续一年每天喝咖啡">✅ 新成就：连续一年每天喝咖啡</h3><p>年初定了一个小目标，争取每天不间断喝咖啡，我可以自豪的宣布，达成了！</p><ul><li>累计打卡 366 天</li><li>总杯数 418 杯</li><li>平均每天 1.14 杯</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/25.jpg"></img></figure><h3 id="新成就入手咖啡机并学会了拉花">✅ 新成就：入手咖啡机，并学会了拉花</h3><p>在国庆节前，我入手了一台半自动咖啡机，铂富 BES880，并从每天中午在公司喝一杯瑞幸，改成了每天早上自己做一杯咖啡。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/01.jpg"></img></figure><p>进入冬天改喝热拿铁之后，我尝试了拉花的学习，经过一个月左右的陆续练习，我终于悟了，可以拉出一个比较不错的大白心了！</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/02.jpg"></img></figure><p>明年争取学会拉郁金香。</p><h3 id="新成就第一次杀青">✅ 新成就：第一次“杀青”</h3><p>今年参与了公司 1024 程序员节视频的拍摄，第一次体验当“专业演员”，从化妆、花絮、道具、灯光、摄影、导演等等，一群人服务我一个人感觉，拍摄完成的时候，有个工作人员对我说，Bean 老师，恭喜杀青了，第一次有人跟我说这么专业的词语。虽然只有短短一个多小时，但真是一次难忘的体验。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/03.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/04.jpg"></img></figure><h3 id="新成就单次游泳-1000-米">✅ 新成就：单次游泳 1000 米</h3><p>在曼谷酒店的游泳池里，戴着 Apple Watch 游了个 1000 米，这是我第一次单次游这么长的距离（中途有休息）。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/05.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/06.png"></img></figure><h3 id="新成就第一次在-w3c-年度大会分享">✅ 新成就：第一次在 W3C 年度大会分享</h3><p>今年的 W3C·Web 进化论是由 B 站承办的，公司有几个分享名额，我有幸分享了团队在数字人领域的一些探索和实践。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/07.jpg"></img></figure><h3 id="新成就第一次在房车里过夜">✅ 新成就：第一次在房车里过夜</h3><p>年底的自驾游回程途中，我们在青岛住了一个民宿，里面有个房车，第一次体验了在房车里过夜，感觉非常棒。</p><p>房车里设施齐全，还带了灶台，不过我们自己也带了一个炉子，煮了一顿泡面火锅，还煮了茶，非常有意思。这个炉子和茶壶买来第一次用，特别喜欢，而且炉子是折叠的，特方便。以后的露营或者自驾游都会带上，非常实用。希望能用很多年。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/30.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/26.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/27.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/28.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/29.jpg"></img></figure><h3 id="️-技术折腾homelab-升级降本增效">🛠️ 技术折腾：HomeLab 升级，降本增效</h3><p>今年升级了自己的 HomeLab，从电视柜迁移到了正规的机柜中。并且将阿里云上所有的服务器都迁移到了我的 HomeLab，仅保留了一个最低配的 ECS 作为网络代理。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/24.jpg"></img></figure><p>HomeLab 的自动化的工作流也进行了非常多的优化，可以参见上一篇文章中的一些介绍，经历了很长时间的打磨，现在已经比较稳定和完善。</p><p>另外，今年将自己 10 年的移动号码携号转网到了电信，同时把我老婆、丈人、丈母娘的手机号全部一起携号转网到我的电信账号下作为副卡，5G 套餐总流量是 200G/月，宽带也升级到了 2000M FTTR，总成本其实相比之前每个号单独付月租还降低了一些。</p><h3 id="终于升级了-etc-设备">🚗 终于升级了 ETC 设备</h3><p>关于换 ETC 这个事情已经拖延了好几年了，之前 ETC 办的非常早，是那种插卡的设备。在各大银行发力之前就已经办了，那时候是不绑定银行卡的，需要去线下充值，很不方便。后来买了个蓝牙充值的设备自己充值，但仍然很麻烦。后来想更换，但是需要注销旧设备才能买新设备，而我这款的苏通卡只能去高速口的服务点线下注销，很不方便。今年终于找到个机会去注销了，然后线上买了个「ETC助手」的 3 代设备，很小巧，很方便。</p><h3 id="️-年度旅行汇总">✈️ 年度旅行汇总</h3><p>今年的旅行也比较丰富：</p><ul><li>4月，去了云南，玩了昆明、大理、丽江</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/08.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/09.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/10.jpg"></img></figure><ul><li>5月，自驾去了宜兴</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/11.jpg"></img></figure><ul><li>8月，去了泰国，玩了曼谷和华欣</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/12.jpg" alt="郑王庙"></img><figcaption>郑王庙</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/13.jpg" alt="曼谷恐怖的摩托大军"></img><figcaption>曼谷恐怖的摩托大军</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/14.jpg" alt="曼谷是一个繁华与落后的混合体，繁华的几公里外是这样的街道"></img><figcaption>曼谷是一个繁华与落后的混合体，繁华的几公里外是这样的街道</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/15.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/16.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/17.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/18.jpg" alt="海上的云像《楚门的世界》里那样美得不真实"></img><figcaption>海上的云像《楚门的世界》里那样美得不真实</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/19.jpg" alt="华欣，早上 6 点去酒店外的海滩看日出"></img><figcaption>华欣，早上 6 点去酒店外的海滩看日出</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/20.jpg" alt="5 年过去了，这个麦当劳一点没变"></img><figcaption>5 年过去了，这个麦当劳一点没变</figcaption></figure><ul><li>12月，日照-青岛-威海自驾游</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/31.jpg" alt="日照的一顿海鲜，包括疙瘩汤和一盘水饺才 168，特实惠"></img><figcaption>日照的一顿海鲜，包括疙瘩汤和一盘水饺才 168，特实惠</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/32.jpg" alt="日照海边的咖啡店「咖啡与海」，环境很棒"></img><figcaption>日照海边的咖啡店「咖啡与海」，环境很棒</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/33.jpg" alt="日照海边的咖啡店「咖啡与海」，环境很棒"></img><figcaption>日照海边的咖啡店「咖啡与海」，环境很棒</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/34.jpg" alt="青岛民宿附近的海边，有很多礁石"></img><figcaption>青岛民宿附近的海边，有很多礁石</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/35.jpg" alt="青岛的海鸥"></img><figcaption>青岛的海鸥</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/36.jpg" alt="威海的布鲁维斯号货轮"></img><figcaption>威海的布鲁维斯号货轮</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/37.jpg" alt="威海灯塔"></img><figcaption>威海灯塔</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/38.jpg" alt="威海灯塔下的一尺花园咖啡店"></img><figcaption>威海灯塔下的一尺花园咖啡店</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/39.jpg" alt="威海猫头山观景台下的风景"></img><figcaption>威海猫头山观景台下的风景</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/40.jpg" alt="威海国际海水浴场"></img><figcaption>威海国际海水浴场</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/41.jpg" alt="威海国际海水浴场-泡沫海浪"></img><figcaption>威海国际海水浴场-泡沫海浪</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/30.jpg" alt="回程途中在青岛别墅区住了一晚房车"></img><figcaption>回程途中在青岛别墅区住了一晚房车</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/42.jpg" alt="青岛别墅区外的墙上"></img><figcaption>青岛别墅区外的墙上</figcaption></figure><h3 id="️-结婚十周年">👩🏻‍❤️‍👨🏻 结婚十周年</h3><p>一眨眼结婚十周年了，在这个浮躁的社会里，能够相亲相爱还是一件挺不容易的事情。这十年里，互相经历了很多，也一起成长了很多。身边有很多过得不那么开心的例子，对比之下，简简单单的幸福尤为珍惜。珍惜当下，享受生活。</p><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/21.jpg" alt="纪念日那天去的咖啡店，这张照片很喜欢，有点王家卫风"></img><figcaption>纪念日那天去的咖啡店，这张照片很喜欢，有点王家卫风</figcaption></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/22.jpg"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250102.end-of-2024/23.jpg"></img></figure><h3 id="️-年度运动健康汇总">❤️ 年度运动健康汇总</h3><ul><li>运动步数：平均 10680 步/天，相比去年提升 34.8%</li><li>运动距离：平均 7.4 公里/天，相比去年提升 39.6%</li><li>睡眠时长：平均 6 小时 22 分钟/天，相比去年提升 4.4%</li><li>能量消耗：平均 503 千卡/天，相比去年提升 10.8%</li></ul><h3 id="挑战去-100-家不同的咖啡店22100">⏳ 挑战去 100 家不同的咖啡店（22/100）</h3><p>不得不说，当初我立这个 flag 的时候，没想到进展会这么慢，以为可以在一年内达成，但其实去不同的咖啡店（不包括连锁咖啡），并且还得是能够足够发朋友圈的，还是需要一点时间去寻找。但人生就是一个不断积累和不断挑战的过程，这个成就作为一个支线挑战，我会持续记录并继续完成。</p><h3 id="其他">其他</h3><p>今年对生活的一个比较大的变化是，决定把生活当成一场长期的游戏，按照游戏中的任务、成就的方式来记录生活。这种记录会比较有意思，并且可以更好的体验生活。</p><p>另外，还有一个想法，就是每年入坑一个新的产品或领域，例如今年入坑了咖啡机，明年有点想入坑「3D 打印机」，已经研究了一段时间了，但还没有决定，看看明年是不是可以找到合适的契机入坑。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何修改 VSCode 侧边栏字体]]></title>
        <id>/posts/2025/change-font-of-vscode-sidebar</id>
        <link href="https://hadb.me/posts/2025/change-font-of-vscode-sidebar"/>
        <updated>2025-01-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[如果你折腾过 VSCode 自定义字体的话，你应该知道目前 VSCode 只能自定义编辑器以及终端的字体样式，而侧边栏的字体样式是无法自定义的。不过你可以通过 window.zoomLevel 来曲线救国实现侧边栏字号的调整，但字体依旧无法设置，关于这个问题的讨论，可以查看这个存在了近十年但仍未解决的 issue#519。上下文实在太长，有几百个评论，我也没仔细研究其原因。有兴趣的可以研究一下来龙去脉。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250107.change-font-of-vscode-sidebar/cover.png" alt="封面" /><p>如果你折腾过 VSCode 自定义字体的话，你应该知道目前 VSCode 只能自定义编辑器以及终端的字体样式，而侧边栏的字体样式是无法自定义的。不过你可以通过 <code>window.zoomLevel</code> 来曲线救国实现侧边栏字号的调整，但字体依旧无法设置，关于这个问题的讨论，可以查看这个存在了近十年但仍未解决的 <a href="https://github.com/microsoft/vscode/issues/519" rel="nofollow">issue#519</a>。上下文实在太长，有几百个评论，我也没仔细研究其原因。有兴趣的可以研究一下来龙去脉。</p><p>经过一顿探寻，发现可以通过 <a href="https://marketplace.visualstudio.com/items?itemName=drcika.apc-extension" rel="nofollow">Apc Customize UI++</a> 这个插件来实现侧边栏字体的调整，这个插件可以通过简单的配置来实现。另外还可以通过 <a href="https://marketplace.visualstudio.com/items?itemName=be5invis.vscode-custom-css" rel="nofollow">Custom CSS and JS Loader</a> 这个插件来实现，这个可以自定义 CSS 样式，可定制化程度更高，不过没有前者方便。我这里打算使用前者。</p><figure><img src="https://hadb.me/static/posts/2025/20250107.change-font-of-vscode-sidebar/01.png"></img></figure><p>遗憾的是，经过尝试，这个 <code>Apc Customize UI++</code> 这个插件目前在 <code>1.93</code> 以上的版本中已经无法使用。在相关 <a href="https://github.com/drcika/apc-extension/issues/230#issuecomment-2421377174" rel="nofollow">issue#230</a> 中发现一个国人开发的可替代的插件：<a href="https://marketplace.visualstudio.com/items?itemName=subframe7536.custom-ui-style" rel="nofollow">Custom UI Style</a>。</p><figure><img src="https://hadb.me/static/posts/2025/20250107.change-font-of-vscode-sidebar/02.png"></img></figure><p>对于我这种只需要修改字体的，配制就比较简单了，侵入性也比较小。配置如下：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">custom-ui-style.font.monospace</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">Jetbrains Mono</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="3"><span class="s39Yj">  "</span><span class="sseR_">custom-ui-style.font.sansSerif</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">Jetbrains Mono</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">}
</span></span></code></pre><p>最后看下整体效果吧：</p><figure><img src="https://hadb.me/static/posts/2025/20250107.change-font-of-vscode-sidebar/03.png"></img></figure>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[零成本部署！阿里云百炼 + Open WebUI 打造专属 DeepSeek-R1]]></title>
        <id>/posts/2025/deploy-deepseek-r1-for-free</id>
        <link href="https://hadb.me/posts/2025/deploy-deepseek-r1-for-free"/>
        <updated>2025-02-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[大家好，众所周知，国产之光 DeepSeek 现在的热度远比当时 ChatGPT 出来的时候要火多了。泼天的流量再加上各种恶意攻击，导致 DeepSeek 一直存在性能问题。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/cover.png" alt="封面" /><p>大家好，众所周知，国产之光 DeepSeek 现在的热度远比当时 ChatGPT 出来的时候要火多了。泼天的流量再加上各种恶意攻击，导致 DeepSeek 一直存在性能问题。</p><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/02.png"></img></figure><p>开启了深度思考就经常出现那句经典名言：服务器繁忙，请稍后再试。</p><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/03.png"></img></figure><p>以至于社区已经出现了各种搞笑段子：</p><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/01.jpg"></img></figure><p>好了，废话不多说，在这样的情况下各大云厂商都纷纷推出了自己的模型部署服务，支持 DeepSeek，并且会赠送很多免费额度。</p><p>今天来教大家如何用阿里云百炼平台和开源工具 Open WebUI，零成本部署专属的 DeepSeek R1 模型！全程无需复杂代码，跟着我做就能拥有企业级 AI 服务！我只花了不到半小时就完成了整个服务的部署，并且本视频的脚本有一部分就是由我自己部署的 DeepSeek-R1 来写的。</p><h2 id="前期准备">前期准备</h2><ol><li>注册阿里云账号并实名认证（已有账号可跳过）</li><li>进入 <a href="https://www.aliyun.com/product/bailian" rel="nofollow">阿里云百炼</a> 的管理控制台，开通大模型服务，获取 <code>API KEY</code></li><li>准备一台有 Docker 环境的服务器，用于部署 WebUI（本机部署亦可）</li></ol><h2 id="open-webui-部署">Open WebUI 部署</h2><ol><li>通过 Docker Compose 直接部署 <a href="https://github.com/open-webui/open-webui" rel="nofollow">open-webui</a><pre><code><span class="line" line="1"><span class="sQzsp">version</span><span class="sP7_E">:</span><span class="sjJ54"> '</span><span class="s_sjI">3</span><span class="sjJ54">'
</span></span><span class="line" line="2"><span class="sQzsp">services</span><span class="sP7_E">:
</span></span><span class="line" line="3"><span class="sQzsp">  openwebui</span><span class="sP7_E">:
</span></span><span class="line" line="4"><span class="sQzsp">    image</span><span class="sP7_E">:</span><span class="s_sjI"> ghcr.io/open-webui/open-webui:0.5.12（版本号可更新为当前最新 tag 版本号，或使用 main 拉取最新）
</span></span><span class="line" line="5"><span class="sQzsp">    ports</span><span class="sP7_E">:
</span></span><span class="line" line="6"><span class="sP7_E">      -</span><span class="sjJ54"> '</span><span class="s_sjI">3000:8080</span><span class="sjJ54">'
</span></span><span class="line" line="7"><span class="sQzsp">    volumes</span><span class="sP7_E">:
</span></span><span class="line" line="8"><span class="sP7_E">      -</span><span class="s_sjI"> ./data:/app/backend/data
</span></span></code></pre></li><li>启动好实例后，通过 <code>http://localhost:3000/</code> 访问 Open WebUI，如果是在云服务器上部署，还有一些域名解析、nginx 代理等操作，这里不再赘述。如果手动设置的 nginx，需要加上 websocket 相关的请求头，具体不赘述，可搜索一下。<pre><code><span class="line" line="1"><span class="smGrS">proxy_http_version </span><span class="srdBf">1.1</span><span class="sP7_E">;
</span></span><span class="line" line="2"><span class="smGrS">proxy_set_header </span><span class="su5hD">Upgrade </span><span class="sP7_E">$</span><span class="su5hD">http_upgrade</span><span class="sP7_E">;
</span></span><span class="line" line="3"><span class="smGrS">proxy_set_header </span><span class="su5hD">Connection </span><span class="sP7_E">$</span><span class="su5hD">connection_upgrade</span><span class="sP7_E">;
</span></span></code></pre></li><li>登录进去之后设置好管理员帐号，如果出现白屏需要等待一段时间，大概率是默认的 OpenAI 的接口卡住了，等后面把 API 改成阿里云百炼的，就不会卡住了。有条件的可以给容器设置好 <code>HTTP_PROXY</code> 的代理，后面联网搜索需要用到。</li></ol><h2 id="deepseek-r1-接入">DeepSeek-R1 接入</h2><ol><li>进入 Open WebUI 的 <code>管理员面板</code> -> <code>外部链接</code> -> <code>设置</code></li></ol><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/04.png"></img></figure><ol start="2"><li>将 OpenAI 的 API 地址改成阿里云百炼的 API 地址：<code>https://dashscope.aliyuncs.com/compatible-mode/v1</code>，秘钥输入百炼获取的 <code>API KEY</code>，模型 ID 输入 <code>deepseek-r1</code>，记得一定要点输入框后面那个 <code>+</code> 加号，不然添加不进去。最后点击“保存”按钮。</li></ol><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/12.png"></img></figure><ol start="3"><li>打开新会话，测试模型响应。</li><li>如果需要使用阿里云百炼支持的其他模型，可在上述模型 ID 中手动添加需要的模型 ID，或者也可以再添加一个外部连接，模型 ID 留空，就可以添加除了 <code>deepseek-r1</code> 和 <code>deepseek-v3</code> 之外的其他所有模型了。之所以需要这么操作是因为目前如果直接留空的话，默认拉不出 <code>deepseek</code> 相关的模型，手动设置 ID 才能使用，估计日后等阿里云百炼彻底支持 DeepSeek 了就不需要那么操作了。</li></ol><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/09.png"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/10.png"></img></figure><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/11.png"></img></figure><h2 id="设置联网搜索">设置联网搜索</h2><ol><li>登录 <a href="https://developers.google.com/custom-search" rel="nofollow">Google 开发者账号</a></li><li>去 <a href="https://programmablesearchengine.google.com/controlpanel/all" rel="nofollow">可编程搜索引擎</a> 添加一个自定义搜索引擎</li><li><a href="https://developers.google.com/custom-search/v1/introduction" rel="nofollow">获取密钥</a></li><li>点击引擎名称，<a href="https://programmablesearchengine.google.com/controlpanel/all" rel="nofollow">获取搜索引擎 ID</a></li><li>前往 Open WebUI 设置页面，联网搜索引擎中下拉选择 <code>google_pse</code>，输入密钥和搜索引擎ID，点击保存。搜索结果数量可设置多一些（会导致 token 消耗多）</li></ol><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/06.png"></img></figure><ol start="6"><li>打开新会话，发现已经有「联网搜索」选项</li></ol><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/07.png"></img></figure><h2 id="结语">结语</h2><p>好了，现在你已经拥有属于自己的 DeepSeek-R1 模型了，并且可以免费使用 100 万 Token，自开通起半年有效期。可在 <a href="https://bailian.console.aliyun.com/detail/deepseek-r1#/model-market/detail/deepseek-r1" rel="nofollow">阿里云百炼 DeepSeek-R1</a> 页面实时查看自己剩余的免费额度数量以及过期时间。有一说一，这 Token 消耗还挺快的，一下午已经消耗了 5 万多 Token 了。</p><figure><img src="https://hadb.me/static/posts/2025/20250211.deploy-deepseek-r1-for-free/08.png"></img></figure><p>同样的，你还可以白嫖腾讯云，同样也有 100万的免费 Token。</p><hr></hr><img src="https://hadb.me/static/reward-code.jpg" alt="赞赏码"></img>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何在 Open WebUI 中显示 DeepSeek-R1 的思考过程]]></title>
        <id>/posts/2025/display-deepseek-r1-thinking</id>
        <link href="https://hadb.me/posts/2025/display-deepseek-r1-thinking"/>
        <updated>2025-02-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[书接上回，咱们部署好了 DeepSeek-R1 之后，发现没办法显示思考过程，只能等结果出来之后查看结果，这个体感上就会感觉响应很慢。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/cover.png" alt="封面" /><p>书接上回，咱们部署好了 DeepSeek-R1 之后，发现没办法显示思考过程，只能等结果出来之后查看结果，这个体感上就会感觉响应很慢。</p><p>查看了 <a href="https://github.com/open-webui/open-webui" rel="nofollow">open-webui</a> 的 issues，发现已经有人提过这个问题，并且已有<a href="https://github.com/open-webui/open-webui/issues/9488#issuecomment-2640537231" rel="nofollow">解决方案</a>，那就是通过 <a href="https://docs.openwebui.com/pipelines/" rel="nofollow">Pipelines</a> 来实现。具体步骤如下：</p><h2 id="_1-部署-pipelines-容器">1. 部署 pipelines 容器</h2><ul><li>docker run 命令：</li></ul><pre><code><span class="line" line="1"><span class="sbgvK">docker</span><span class="s_sjI"> run</span><span class="stzsN"> -d</span><span class="stzsN"> -p</span><span class="s_sjI"> 9099:9099</span><span class="stzsN"> --add-host=host.docker.internal:host-gateway</span><span class="stzsN"> -v</span><span class="s_sjI"> pipelines:/app/pipelines</span><span class="stzsN"> --name</span><span class="s_sjI"> pipelines</span><span class="stzsN"> --restart</span><span class="s_sjI"> always</span><span class="s_sjI"> ghcr.io/open-webui/pipelines:main
</span></span></code></pre><ul><li>docker-compose.yml 配置：</li></ul><pre><code><span class="line" line="1"><span class="sQzsp">services</span><span class="sP7_E">:
</span></span><span class="line" line="2"><span class="sQzsp">  open-webui-pipelines</span><span class="sP7_E">:
</span></span><span class="line" line="3"><span class="sQzsp">    container_name</span><span class="sP7_E">:</span><span class="s_sjI"> open-webui-pipelines
</span></span><span class="line" line="4"><span class="sQzsp">    image</span><span class="sP7_E">:</span><span class="s_sjI"> ghcr.io/open-webui/pipelines:main
</span></span><span class="line" line="5"><span class="sQzsp">    network_mode</span><span class="sP7_E">:</span><span class="s_sjI"> bridge
</span></span><span class="line" line="6"><span class="sQzsp">    ports</span><span class="sP7_E">:
</span></span><span class="line" line="7"><span class="sP7_E">      -</span><span class="sjJ54"> '</span><span class="s_sjI">9099:9099</span><span class="sjJ54">'
</span></span><span class="line" line="8"><span class="sQzsp">    volumes</span><span class="sP7_E">:
</span></span><span class="line" line="9"><span class="sP7_E">      -</span><span class="s_sjI"> ./pipelines:/app/pipelines
</span></span><span class="line" line="10"><span class="sQzsp">    restart</span><span class="sP7_E">:</span><span class="s_sjI"> always
</span></span></code></pre><h2 id="_2-配置管道连接">2. 配置管道连接</h2><ul><li>在 <code>管理员设置</code> -> <code>外部连接</code> 中按 <code>+</code> 添加一个连接。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/01.png"></img></figure><ul><li>管道地址通过内网 IP、hostname 或域名等，加上前面 pipelines 容器的端口号 <code>9099</code>；密钥是固定的 <code>0p3n-w3bu!</code>。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/02.png"></img></figure><h2 id="_3-创建函数">3. 创建函数</h2><ul><li>访问 <a href="https://openwebui.com/f/zgccrui/deepseek_r1" rel="nofollow">DeepSeek R1 Function</a> 函数页面，通过 <code>Get</code> 按钮完成注册。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/03.png"></img></figure><ul><li>注册完成之后，在弹出的页面中输入自己的站点地址自动跳转并导入函数。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/04.png"></img></figure><ul><li>也可以手动复制该函数代码，然后点击 <code>+</code> 加号手动添加。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/05.png"></img></figure><ul><li>以下是我在 <code>1.2.10</code> 版本上修改过的代码，支持了模型显示名的配置，这样方便与原始版本区分</li></ul><pre><code><span class="line" line="1"><span class="s2W-s">"""
</span></span><span class="line" line="2"><span class="sithA">title: DeepSeek R1
</span></span><span class="line" line="3"><span class="sithA">author: zgccrui
</span></span><span class="line" line="4"><span class="sithA">description: 在OpwenWebUI中显示DeepSeek R1模型的思维链 - 仅支持0.5.6及以上版本
</span></span><span class="line" line="5"><span class="sithA">version: 1.2.10
</span></span><span class="line" line="6"><span class="sithA">licence: MIT
</span></span><span class="line" line="7"><span class="s2W-s">"""
</span></span><span class="line" line="8"><span emptyLinePlaceholder>
</span></span><span class="line" line="9"><span class="sVHd0">import</span><span class="su5hD"> json
</span></span><span class="line" line="10"><span class="sVHd0">import</span><span class="su5hD"> httpx
</span></span><span class="line" line="11"><span class="sVHd0">import</span><span class="su5hD"> re
</span></span><span class="line" line="12"><span class="sVHd0">from</span><span class="su5hD"> typing </span><span class="sVHd0">import</span><span class="su5hD"> AsyncGenerator</span><span class="sP7_E">,</span><span class="su5hD"> Callable</span><span class="sP7_E">,</span><span class="su5hD"> Awaitable
</span></span><span class="line" line="13"><span class="sVHd0">from</span><span class="su5hD"> pydantic </span><span class="sVHd0">import</span><span class="su5hD"> BaseModel</span><span class="sP7_E">,</span><span class="su5hD"> Field
</span></span><span class="line" line="14"><span class="sVHd0">import</span><span class="su5hD"> asyncio
</span></span><span class="line" line="15"><span emptyLinePlaceholder>
</span></span><span class="line" line="16"><span class="sbsja">class</span><span class="sbgvK"> Pipe</span><span class="sP7_E">:
</span></span><span class="line" line="17"><span class="sbsja">    class</span><span class="sbgvK"> Valves</span><span class="sP7_E">(</span><span class="sbgvK">BaseModel</span><span class="sP7_E">):
</span></span><span class="line" line="18"><span class="s_hVV">        DEEPSEEK_API_BASE_URL</span><span class="sP7_E">:</span><span class="sZMiF"> str</span><span class="smGrS"> =</span><span class="slqww"> Field</span><span class="sP7_E">(
</span></span><span class="line" line="19"><span class="s99_P">            default</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">https://api.deepseek.com/v1</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="20"><span class="s99_P">            description</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">DeepSeek API的基础请求地址</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="21"><span class="sP7_E">        )
</span></span><span class="line" line="22"><span class="s_hVV">        DEEPSEEK_API_KEY</span><span class="sP7_E">:</span><span class="sZMiF"> str</span><span class="smGrS"> =</span><span class="slqww"> Field</span><span class="sP7_E">(
</span></span><span class="line" line="23"><span class="s99_P">            default</span><span class="smGrS">=</span><span class="sjJ54">""</span><span class="sP7_E">,</span><span class="s99_P"> description</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">用于身份验证的DeepSeek API密钥，可从控制台获取</span><span class="sjJ54">"
</span></span><span class="line" line="24"><span class="sP7_E">        )
</span></span><span class="line" line="25"><span class="s_hVV">        DEEPSEEK_API_MODEL</span><span class="sP7_E">:</span><span class="sZMiF"> str</span><span class="smGrS"> =</span><span class="slqww"> Field</span><span class="sP7_E">(
</span></span><span class="line" line="26"><span class="s99_P">            default</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">deepseek-reasoner</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="27"><span class="s99_P">            description</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">API请求的模型名称，默认为 deepseek-reasoner</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="28"><span class="sP7_E">        )
</span></span><span class="line" line="29"><span class="s_hVV">        DEEPSEEK_MODEL_DISPLAY_NAME</span><span class="sP7_E">:</span><span class="sZMiF"> str</span><span class="smGrS"> =</span><span class="slqww"> Field</span><span class="sP7_E">(
</span></span><span class="line" line="30"><span class="s99_P">            default</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">deepseek-reasoner-fix</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="31"><span class="s99_P">            description</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">模型名称，默认为 deepseek-reasoner-fix</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="32"><span class="sP7_E">        )
</span></span><span class="line" line="33"><span emptyLinePlaceholder>
</span></span><span class="line" line="34"><span class="sbsja">    def</span><span class="sptTA"> __init__</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">):
</span></span><span class="line" line="35"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="smGrS"> =</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">Valves</span><span class="sP7_E">()
</span></span><span class="line" line="36"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="skxfh">data_prefix</span><span class="smGrS"> =</span><span class="sjJ54"> "</span><span class="s_sjI">data:</span><span class="sjJ54">"
</span></span><span class="line" line="37"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="skxfh">emitter</span><span class="smGrS"> =</span><span class="s39Yj"> None
</span></span><span class="line" line="38"><span emptyLinePlaceholder>
</span></span><span class="line" line="39"><span class="sbsja">    def</span><span class="sGLFI"> pipes</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">):
</span></span><span class="line" line="40"><span class="sVHd0">        return</span><span class="sP7_E"> [
</span></span><span class="line" line="41"><span class="sP7_E">            {
</span></span><span class="line" line="42"><span class="sjJ54">                "</span><span class="s_sjI">id</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="sP7_E">.</span><span class="swQdS">DEEPSEEK_API_MODEL</span><span class="sP7_E">,
</span></span><span class="line" line="43"><span class="sjJ54">                "</span><span class="s_sjI">name</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="sP7_E">.</span><span class="swQdS">DEEPSEEK_MODEL_DISPLAY_NAME</span><span class="sP7_E">,
</span></span><span class="line" line="44"><span class="sP7_E">            }
</span></span><span class="line" line="45"><span class="sP7_E">        ]
</span></span><span class="line" line="46"><span emptyLinePlaceholder>
</span></span><span class="line" line="47"><span class="sbsja">    async</span><span class="sbsja"> def</span><span class="sGLFI"> pipe</span><span class="sP7_E">(
</span></span><span class="line" line="48"><span class="smCYv">        self</span><span class="sP7_E">,</span><span class="sFwrP"> body</span><span class="sP7_E">:</span><span class="sZMiF"> dict</span><span class="sP7_E">,</span><span class="sFwrP"> __event_emitter__</span><span class="sP7_E">:</span><span class="su5hD"> Callable</span><span class="sP7_E">[[</span><span class="sZMiF">dict</span><span class="sP7_E">],</span><span class="su5hD"> Awaitable</span><span class="sP7_E">[</span><span class="s39Yj">None</span><span class="sP7_E">]]</span><span class="smGrS"> =</span><span class="s39Yj"> None
</span></span><span class="line" line="49"><span class="sP7_E">    )</span><span class="sP7_E"> -></span><span class="su5hD"> AsyncGenerator</span><span class="sP7_E">[</span><span class="sZMiF">str</span><span class="sP7_E">,</span><span class="s39Yj"> None</span><span class="sP7_E">]:
</span></span><span class="line" line="50"><span class="s2W-s">        """</span><span class="sithA">主处理管道（已移除缓冲）</span><span class="s2W-s">"""
</span></span><span class="line" line="51"><span class="su5hD">        thinking_state </span><span class="smGrS">=</span><span class="sP7_E"> {</span><span class="sjJ54">"</span><span class="s_sjI">thinking</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="smGrS"> -</span><span class="srdBf">1</span><span class="sP7_E">}</span><span class="sutJx">  # 使用字典来存储thinking状态
</span></span><span class="line" line="52"><span class="s_hVV">        self</span><span class="sP7_E">.</span><span class="skxfh">emitter</span><span class="smGrS"> =</span><span class="su5hD"> __event_emitter__
</span></span><span class="line" line="53"><span emptyLinePlaceholder>
</span></span><span class="line" line="54"><span class="sutJx">        # 验证配置
</span></span><span class="line" line="55"><span class="sVHd0">        if</span><span class="smGrS"> not</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="sP7_E">.</span><span class="swQdS">DEEPSEEK_API_KEY</span><span class="sP7_E">:
</span></span><span class="line" line="56"><span class="sVHd0">            yield</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="slqww">dumps</span><span class="sP7_E">({</span><span class="sjJ54">"</span><span class="s_sjI">error</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">未配置API密钥</span><span class="sjJ54">"</span><span class="sP7_E">},</span><span class="s99_P"> ensure_ascii</span><span class="smGrS">=</span><span class="s39Yj">False</span><span class="sP7_E">)
</span></span><span class="line" line="57"><span class="sVHd0">            return
</span></span><span class="line" line="58"><span emptyLinePlaceholder>
</span></span><span class="line" line="59"><span class="sutJx">        # 准备请求参数
</span></span><span class="line" line="60"><span class="su5hD">        headers </span><span class="smGrS">=</span><span class="sP7_E"> {
</span></span><span class="line" line="61"><span class="sjJ54">            "</span><span class="s_sjI">Authorization</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sbsja"> f</span><span class="s_sjI">"Bearer </span><span class="srdBf">{</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="sP7_E">.</span><span class="swQdS">DEEPSEEK_API_KEY</span><span class="srdBf">}</span><span class="s_sjI">"</span><span class="sP7_E">,
</span></span><span class="line" line="62"><span class="sjJ54">            "</span><span class="s_sjI">Content-Type</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">application/json</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="63"><span class="sP7_E">        }
</span></span><span class="line" line="64"><span emptyLinePlaceholder>
</span></span><span class="line" line="65"><span class="sVHd0">        try</span><span class="sP7_E">:
</span></span><span class="line" line="66"><span class="sutJx">            # 模型ID提取
</span></span><span class="line" line="67"><span class="su5hD">            model_id </span><span class="smGrS">=</span><span class="su5hD"> body</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">model</span><span class="sjJ54">"</span><span class="sP7_E">].</span><span class="slqww">split</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">.</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="srdBf"> 1</span><span class="sP7_E">)[</span><span class="smGrS">-</span><span class="srdBf">1</span><span class="sP7_E">]
</span></span><span class="line" line="68"><span class="su5hD">            payload </span><span class="smGrS">=</span><span class="sP7_E"> {</span><span class="smGrS">**</span><span class="su5hD">body</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">model</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="su5hD"> model_id</span><span class="sP7_E">}
</span></span><span class="line" line="69"><span emptyLinePlaceholder>
</span></span><span class="line" line="70"><span class="sutJx">            # 处理消息以防止连续的相同角色
</span></span><span class="line" line="71"><span class="su5hD">            messages </span><span class="smGrS">=</span><span class="su5hD"> payload</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">messages</span><span class="sjJ54">"</span><span class="sP7_E">]
</span></span><span class="line" line="72"><span class="su5hD">            i </span><span class="smGrS">=</span><span class="srdBf"> 0
</span></span><span class="line" line="73"><span class="sVHd0">            while</span><span class="su5hD"> i </span><span class="smGrS"><</span><span class="sptTA"> len</span><span class="sP7_E">(</span><span class="slqww">messages</span><span class="sP7_E">)</span><span class="smGrS"> -</span><span class="srdBf"> 1</span><span class="sP7_E">:
</span></span><span class="line" line="74"><span class="sVHd0">                if</span><span class="su5hD"> messages</span><span class="sP7_E">[</span><span class="su5hD">i</span><span class="sP7_E">][</span><span class="sjJ54">"</span><span class="s_sjI">role</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> ==</span><span class="su5hD"> messages</span><span class="sP7_E">[</span><span class="su5hD">i </span><span class="smGrS">+</span><span class="srdBf"> 1</span><span class="sP7_E">][</span><span class="sjJ54">"</span><span class="s_sjI">role</span><span class="sjJ54">"</span><span class="sP7_E">]:
</span></span><span class="line" line="75"><span class="sutJx">                    # 插入具有替代角色的占位符消息
</span></span><span class="line" line="76"><span class="su5hD">                    alternate_role </span><span class="smGrS">=</span><span class="sP7_E"> (
</span></span><span class="line" line="77"><span class="sjJ54">                        "</span><span class="s_sjI">assistant</span><span class="sjJ54">"</span><span class="sVHd0"> if</span><span class="su5hD"> messages</span><span class="sP7_E">[</span><span class="su5hD">i</span><span class="sP7_E">][</span><span class="sjJ54">"</span><span class="s_sjI">role</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> ==</span><span class="sjJ54"> "</span><span class="s_sjI">user</span><span class="sjJ54">"</span><span class="sVHd0"> else</span><span class="sjJ54"> "</span><span class="s_sjI">user</span><span class="sjJ54">"
</span></span><span class="line" line="78"><span class="sP7_E">                    )
</span></span><span class="line" line="79"><span class="su5hD">                    messages</span><span class="sP7_E">.</span><span class="slqww">insert</span><span class="sP7_E">(
</span></span><span class="line" line="80"><span class="slqww">                        i </span><span class="smGrS">+</span><span class="srdBf"> 1</span><span class="sP7_E">,
</span></span><span class="line" line="81"><span class="sP7_E">                        {</span><span class="sjJ54">"</span><span class="s_sjI">role</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="slqww"> alternate_role</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">content</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">[Unfinished thinking]</span><span class="sjJ54">"</span><span class="sP7_E">},
</span></span><span class="line" line="82"><span class="sP7_E">                    )
</span></span><span class="line" line="83"><span class="su5hD">                i </span><span class="smGrS">+=</span><span class="srdBf"> 1
</span></span><span class="line" line="84"><span emptyLinePlaceholder>
</span></span><span class="line" line="85"><span class="sutJx">            # yield json.dumps(payload, ensure_ascii=False)
</span></span><span class="line" line="86"><span emptyLinePlaceholder>
</span></span><span class="line" line="87"><span class="sutJx">            # 发起API请求
</span></span><span class="line" line="88"><span class="sVHd0">            async</span><span class="sVHd0"> with</span><span class="su5hD"> httpx</span><span class="sP7_E">.</span><span class="slqww">AsyncClient</span><span class="sP7_E">(</span><span class="s99_P">http2</span><span class="smGrS">=</span><span class="s39Yj">True</span><span class="sP7_E">)</span><span class="sVHd0"> as</span><span class="su5hD"> client</span><span class="sP7_E">:
</span></span><span class="line" line="89"><span class="sVHd0">                async</span><span class="sVHd0"> with</span><span class="su5hD"> client</span><span class="sP7_E">.</span><span class="slqww">stream</span><span class="sP7_E">(
</span></span><span class="line" line="90"><span class="sjJ54">                    "</span><span class="s_sjI">POST</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="91"><span class="sbsja">                    f</span><span class="s_sjI">"</span><span class="srdBf">{</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">valves</span><span class="sP7_E">.</span><span class="swQdS">DEEPSEEK_API_BASE_URL</span><span class="srdBf">}</span><span class="s_sjI">/chat/completions"</span><span class="sP7_E">,
</span></span><span class="line" line="92"><span class="s99_P">                    json</span><span class="smGrS">=</span><span class="slqww">payload</span><span class="sP7_E">,
</span></span><span class="line" line="93"><span class="s99_P">                    headers</span><span class="smGrS">=</span><span class="slqww">headers</span><span class="sP7_E">,
</span></span><span class="line" line="94"><span class="s99_P">                    timeout</span><span class="smGrS">=</span><span class="srdBf">300</span><span class="sP7_E">,
</span></span><span class="line" line="95"><span class="sP7_E">                )</span><span class="sVHd0"> as</span><span class="su5hD"> response</span><span class="sP7_E">:
</span></span><span class="line" line="96"><span class="sutJx">                    # 错误处理
</span></span><span class="line" line="97"><span class="sVHd0">                    if</span><span class="su5hD"> response</span><span class="sP7_E">.</span><span class="skxfh">status_code</span><span class="smGrS"> !=</span><span class="srdBf"> 200</span><span class="sP7_E">:
</span></span><span class="line" line="98"><span class="su5hD">                        error </span><span class="smGrS">=</span><span class="sVHd0"> await</span><span class="su5hD"> response</span><span class="sP7_E">.</span><span class="slqww">aread</span><span class="sP7_E">()
</span></span><span class="line" line="99"><span class="sVHd0">                        yield</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">_format_error</span><span class="sP7_E">(</span><span class="slqww">response</span><span class="sP7_E">.</span><span class="skxfh">status_code</span><span class="sP7_E">,</span><span class="slqww"> error</span><span class="sP7_E">)
</span></span><span class="line" line="100"><span class="sVHd0">                        return
</span></span><span class="line" line="101"><span emptyLinePlaceholder>
</span></span><span class="line" line="102"><span class="sutJx">                    # 流式处理响应
</span></span><span class="line" line="103"><span class="sVHd0">                    async</span><span class="sVHd0"> for</span><span class="su5hD"> line </span><span class="sVHd0">in</span><span class="su5hD"> response</span><span class="sP7_E">.</span><span class="slqww">aiter_lines</span><span class="sP7_E">():
</span></span><span class="line" line="104"><span class="sVHd0">                        if</span><span class="smGrS"> not</span><span class="su5hD"> line</span><span class="sP7_E">.</span><span class="slqww">startswith</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">data_prefix</span><span class="sP7_E">):
</span></span><span class="line" line="105"><span class="sVHd0">                            continue
</span></span><span class="line" line="106"><span emptyLinePlaceholder>
</span></span><span class="line" line="107"><span class="sutJx">                        # 截取 JSON 字符串
</span></span><span class="line" line="108"><span class="su5hD">                        json_str </span><span class="smGrS">=</span><span class="su5hD"> line</span><span class="sP7_E">[</span><span class="sptTA">len</span><span class="sP7_E">(</span><span class="s_hVV">self</span><span class="sP7_E">.</span><span class="skxfh">data_prefix</span><span class="sP7_E">)</span><span class="sP7_E"> :]
</span></span><span class="line" line="109"><span emptyLinePlaceholder>
</span></span><span class="line" line="110"><span class="sutJx">                        # 去除首尾空格后检查是否为结束标记
</span></span><span class="line" line="111"><span class="sVHd0">                        if</span><span class="su5hD"> json_str</span><span class="sP7_E">.</span><span class="slqww">strip</span><span class="sP7_E">()</span><span class="smGrS"> ==</span><span class="sjJ54"> "</span><span class="s_sjI">[DONE]</span><span class="sjJ54">"</span><span class="sP7_E">:
</span></span><span class="line" line="112"><span class="sVHd0">                            return
</span></span><span class="line" line="113"><span emptyLinePlaceholder>
</span></span><span class="line" line="114"><span class="sVHd0">                        try</span><span class="sP7_E">:
</span></span><span class="line" line="115"><span class="su5hD">                            data </span><span class="smGrS">=</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="slqww">loads</span><span class="sP7_E">(</span><span class="slqww">json_str</span><span class="sP7_E">)
</span></span><span class="line" line="116"><span class="sVHd0">                        except</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="skxfh">JSONDecodeError</span><span class="sVHd0"> as</span><span class="su5hD"> e</span><span class="sP7_E">:
</span></span><span class="line" line="117"><span class="sutJx">                            # 格式化错误信息，这里传入错误类型和详细原因（包括出错内容和异常信息）
</span></span><span class="line" line="118"><span class="su5hD">                            error_detail </span><span class="smGrS">=</span><span class="sbsja"> f</span><span class="s_sjI">"解析失败 - 内容：</span><span class="srdBf">{</span><span class="su5hD">json_str</span><span class="srdBf">}</span><span class="s_sjI">，原因：</span><span class="srdBf">{</span><span class="su5hD">e</span><span class="srdBf">}</span><span class="s_sjI">"
</span></span><span class="line" line="119"><span class="sVHd0">                            yield</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">_format_error</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">JSONDecodeError</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="slqww"> error_detail</span><span class="sP7_E">)
</span></span><span class="line" line="120"><span class="sVHd0">                            return
</span></span><span class="line" line="121"><span emptyLinePlaceholder>
</span></span><span class="line" line="122"><span class="su5hD">                        choice </span><span class="smGrS">=</span><span class="su5hD"> data</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">choices</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sP7_E"> [{}])[</span><span class="srdBf">0</span><span class="sP7_E">]
</span></span><span class="line" line="123"><span emptyLinePlaceholder>
</span></span><span class="line" line="124"><span class="sutJx">                        # 结束条件判断
</span></span><span class="line" line="125"><span class="sVHd0">                        if</span><span class="su5hD"> choice</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">finish_reason</span><span class="sjJ54">"</span><span class="sP7_E">):
</span></span><span class="line" line="126"><span class="sVHd0">                            return
</span></span><span class="line" line="127"><span emptyLinePlaceholder>
</span></span><span class="line" line="128"><span class="sutJx">                        # 状态机处理
</span></span><span class="line" line="129"><span class="su5hD">                        state_output </span><span class="smGrS">=</span><span class="sVHd0"> await</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">_update_thinking_state</span><span class="sP7_E">(
</span></span><span class="line" line="130"><span class="slqww">                            choice</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">delta</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sP7_E"> {}),</span><span class="slqww"> thinking_state
</span></span><span class="line" line="131"><span class="sP7_E">                        )
</span></span><span class="line" line="132"><span class="sVHd0">                        if</span><span class="su5hD"> state_output</span><span class="sP7_E">:
</span></span><span class="line" line="133"><span class="sVHd0">                            yield</span><span class="su5hD"> state_output  </span><span class="sutJx"># 直接发送状态标记
</span></span><span class="line" line="134"><span class="sVHd0">                            if</span><span class="su5hD"> state_output </span><span class="smGrS">==</span><span class="sjJ54"> "</span><span class="s_sjI"><think></span><span class="sjJ54">"</span><span class="sP7_E">:
</span></span><span class="line" line="135"><span class="sVHd0">                                yield</span><span class="sjJ54"> "</span><span class="s_hVV">\n</span><span class="sjJ54">"
</span></span><span class="line" line="136"><span emptyLinePlaceholder>
</span></span><span class="line" line="137"><span class="sutJx">                        # 内容处理并立即发送
</span></span><span class="line" line="138"><span class="su5hD">                        content </span><span class="smGrS">=</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">_process_content</span><span class="sP7_E">(</span><span class="slqww">choice</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">delta</span><span class="sjJ54">"</span><span class="sP7_E">])
</span></span><span class="line" line="139"><span class="sVHd0">                        if</span><span class="su5hD"> content</span><span class="sP7_E">:
</span></span><span class="line" line="140"><span class="sVHd0">                            if</span><span class="su5hD"> content</span><span class="sP7_E">.</span><span class="slqww">startswith</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI"><think></span><span class="sjJ54">"</span><span class="sP7_E">):
</span></span><span class="line" line="141"><span class="su5hD">                                match </span><span class="smGrS">=</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">match</span><span class="sP7_E">(</span><span class="sbsja">r</span><span class="sjJ54">"</span><span class="stzsN">^</span><span class="sQRbd"><think></span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="slqww"> content</span><span class="sP7_E">)
</span></span><span class="line" line="142"><span class="sVHd0">                                if</span><span class="su5hD"> match</span><span class="sP7_E">:
</span></span><span class="line" line="143"><span class="su5hD">                                    content </span><span class="smGrS">=</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">sub</span><span class="sP7_E">(</span><span class="sbsja">r</span><span class="sjJ54">"</span><span class="stzsN">^</span><span class="sQRbd"><think></span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> ""</span><span class="sP7_E">,</span><span class="slqww"> content</span><span class="sP7_E">)
</span></span><span class="line" line="144"><span class="sVHd0">                                    yield</span><span class="sjJ54"> "</span><span class="s_sjI"><think></span><span class="sjJ54">"
</span></span><span class="line" line="145"><span class="sVHd0">                                    await</span><span class="su5hD"> asyncio</span><span class="sP7_E">.</span><span class="slqww">sleep</span><span class="sP7_E">(</span><span class="srdBf">0.1</span><span class="sP7_E">)
</span></span><span class="line" line="146"><span class="sVHd0">                                    yield</span><span class="sjJ54"> "</span><span class="s_hVV">\n</span><span class="sjJ54">"
</span></span><span class="line" line="147"><span emptyLinePlaceholder>
</span></span><span class="line" line="148"><span class="sVHd0">                            elif</span><span class="su5hD"> content</span><span class="sP7_E">.</span><span class="slqww">startswith</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI"></think></span><span class="sjJ54">"</span><span class="sP7_E">):
</span></span><span class="line" line="149"><span class="su5hD">                                match </span><span class="smGrS">=</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">match</span><span class="sP7_E">(</span><span class="sbsja">r</span><span class="sjJ54">"</span><span class="stzsN">^</span><span class="sQRbd"></think></span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="slqww"> content</span><span class="sP7_E">)
</span></span><span class="line" line="150"><span class="sVHd0">                                if</span><span class="su5hD"> match</span><span class="sP7_E">:
</span></span><span class="line" line="151"><span class="su5hD">                                    content </span><span class="smGrS">=</span><span class="su5hD"> re</span><span class="sP7_E">.</span><span class="slqww">sub</span><span class="sP7_E">(</span><span class="sbsja">r</span><span class="sjJ54">"</span><span class="stzsN">^</span><span class="sQRbd"></think></span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> ""</span><span class="sP7_E">,</span><span class="slqww"> content</span><span class="sP7_E">)
</span></span><span class="line" line="152"><span class="sVHd0">                                    yield</span><span class="sjJ54"> "</span><span class="s_sjI"></think></span><span class="sjJ54">"
</span></span><span class="line" line="153"><span class="sVHd0">                                    await</span><span class="su5hD"> asyncio</span><span class="sP7_E">.</span><span class="slqww">sleep</span><span class="sP7_E">(</span><span class="srdBf">0.1</span><span class="sP7_E">)
</span></span><span class="line" line="154"><span class="sVHd0">                                    yield</span><span class="sjJ54"> "</span><span class="s_hVV">\n</span><span class="sjJ54">"
</span></span><span class="line" line="155"><span class="sVHd0">                            yield</span><span class="su5hD"> content
</span></span><span class="line" line="156"><span emptyLinePlaceholder>
</span></span><span class="line" line="157"><span class="sVHd0">        except</span><span class="sZMiF"> Exception</span><span class="sVHd0"> as</span><span class="su5hD"> e</span><span class="sP7_E">:
</span></span><span class="line" line="158"><span class="sVHd0">            yield</span><span class="s_hVV"> self</span><span class="sP7_E">.</span><span class="slqww">_format_exception</span><span class="sP7_E">(</span><span class="slqww">e</span><span class="sP7_E">)
</span></span><span class="line" line="159"><span emptyLinePlaceholder>
</span></span><span class="line" line="160"><span class="sbsja">    async</span><span class="sbsja"> def</span><span class="sGLFI"> _update_thinking_state</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="sFwrP"> delta</span><span class="sP7_E">:</span><span class="sZMiF"> dict</span><span class="sP7_E">,</span><span class="sFwrP"> thinking_state</span><span class="sP7_E">:</span><span class="sZMiF"> dict</span><span class="sP7_E">)</span><span class="sP7_E"> -></span><span class="sZMiF"> str</span><span class="sP7_E">:
</span></span><span class="line" line="161"><span class="s2W-s">        """</span><span class="sithA">更新思考状态机（简化版）</span><span class="s2W-s">"""
</span></span><span class="line" line="162"><span class="su5hD">        state_output </span><span class="smGrS">=</span><span class="sjJ54"> ""
</span></span><span class="line" line="163"><span emptyLinePlaceholder>
</span></span><span class="line" line="164"><span class="sutJx">        # 状态转换：未开始 -> 思考中
</span></span><span class="line" line="165"><span class="sVHd0">        if</span><span class="su5hD"> thinking_state</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">thinking</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> ==</span><span class="smGrS"> -</span><span class="srdBf">1</span><span class="smGrS"> and</span><span class="su5hD"> delta</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">reasoning_content</span><span class="sjJ54">"</span><span class="sP7_E">):
</span></span><span class="line" line="166"><span class="su5hD">            thinking_state</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">thinking</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="srdBf"> 0
</span></span><span class="line" line="167"><span class="su5hD">            state_output </span><span class="smGrS">=</span><span class="sjJ54"> "</span><span class="s_sjI"><think></span><span class="sjJ54">"
</span></span><span class="line" line="168"><span emptyLinePlaceholder>
</span></span><span class="line" line="169"><span class="sutJx">        # 状态转换：思考中 -> 已回答
</span></span><span class="line" line="170"><span class="sVHd0">        elif</span><span class="sP7_E"> (
</span></span><span class="line" line="171"><span class="su5hD">            thinking_state</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">thinking</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> ==</span><span class="srdBf"> 0
</span></span><span class="line" line="172"><span class="smGrS">            and</span><span class="smGrS"> not</span><span class="su5hD"> delta</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">reasoning_content</span><span class="sjJ54">"</span><span class="sP7_E">)
</span></span><span class="line" line="173"><span class="smGrS">            and</span><span class="su5hD"> delta</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">content</span><span class="sjJ54">"</span><span class="sP7_E">)
</span></span><span class="line" line="174"><span class="sP7_E">        ):
</span></span><span class="line" line="175"><span class="su5hD">            thinking_state</span><span class="sP7_E">[</span><span class="sjJ54">"</span><span class="s_sjI">thinking</span><span class="sjJ54">"</span><span class="sP7_E">]</span><span class="smGrS"> =</span><span class="srdBf"> 1
</span></span><span class="line" line="176"><span class="su5hD">            state_output </span><span class="smGrS">=</span><span class="sjJ54"> "</span><span class="s_hVV">\n</span><span class="s_sjI"></think></span><span class="s_hVV">\n\n</span><span class="sjJ54">"
</span></span><span class="line" line="177"><span emptyLinePlaceholder>
</span></span><span class="line" line="178"><span class="sVHd0">        return</span><span class="su5hD"> state_output
</span></span><span class="line" line="179"><span emptyLinePlaceholder>
</span></span><span class="line" line="180"><span class="sbsja">    def</span><span class="sGLFI"> _process_content</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="sFwrP"> delta</span><span class="sP7_E">:</span><span class="sZMiF"> dict</span><span class="sP7_E">)</span><span class="sP7_E"> -></span><span class="sZMiF"> str</span><span class="sP7_E">:
</span></span><span class="line" line="181"><span class="s2W-s">        """</span><span class="sithA">直接返回处理后的内容</span><span class="s2W-s">"""
</span></span><span class="line" line="182"><span class="sVHd0">        return</span><span class="su5hD"> delta</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">reasoning_content</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> ""</span><span class="sP7_E">)</span><span class="smGrS"> or</span><span class="su5hD"> delta</span><span class="sP7_E">.</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">content</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> ""</span><span class="sP7_E">)
</span></span><span class="line" line="183"><span emptyLinePlaceholder>
</span></span><span class="line" line="184"><span class="sbsja">    def</span><span class="sGLFI"> _format_error</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="sFwrP"> status_code</span><span class="sP7_E">:</span><span class="sZMiF"> int</span><span class="sP7_E">,</span><span class="sFwrP"> error</span><span class="sP7_E">:</span><span class="sZMiF"> bytes</span><span class="sP7_E">)</span><span class="sP7_E"> -></span><span class="sZMiF"> str</span><span class="sP7_E">:
</span></span><span class="line" line="185"><span class="sutJx">        # 如果 error 已经是字符串，则无需 decode
</span></span><span class="line" line="186"><span class="sVHd0">        if</span><span class="sptTA"> isinstance</span><span class="sP7_E">(</span><span class="slqww">error</span><span class="sP7_E">,</span><span class="sZMiF"> str</span><span class="sP7_E">):
</span></span><span class="line" line="187"><span class="su5hD">            error_str </span><span class="smGrS">=</span><span class="su5hD"> error
</span></span><span class="line" line="188"><span class="sVHd0">        else</span><span class="sP7_E">:
</span></span><span class="line" line="189"><span class="su5hD">            error_str </span><span class="smGrS">=</span><span class="su5hD"> error</span><span class="sP7_E">.</span><span class="slqww">decode</span><span class="sP7_E">(</span><span class="s99_P">errors</span><span class="smGrS">=</span><span class="sjJ54">"</span><span class="s_sjI">ignore</span><span class="sjJ54">"</span><span class="sP7_E">)
</span></span><span class="line" line="190"><span emptyLinePlaceholder>
</span></span><span class="line" line="191"><span class="sVHd0">        try</span><span class="sP7_E">:
</span></span><span class="line" line="192"><span class="su5hD">            err_msg </span><span class="smGrS">=</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="slqww">loads</span><span class="sP7_E">(</span><span class="slqww">error_str</span><span class="sP7_E">).</span><span class="slqww">get</span><span class="sP7_E">(</span><span class="sjJ54">"</span><span class="s_sjI">message</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="slqww"> error_str</span><span class="sP7_E">)[:</span><span class="srdBf">200</span><span class="sP7_E">]
</span></span><span class="line" line="193"><span class="sVHd0">        except</span><span class="sZMiF"> Exception</span><span class="sVHd0"> as</span><span class="su5hD"> e</span><span class="sP7_E">:
</span></span><span class="line" line="194"><span class="su5hD">            err_msg </span><span class="smGrS">=</span><span class="su5hD"> error_str</span><span class="sP7_E">[:</span><span class="srdBf">200</span><span class="sP7_E">]
</span></span><span class="line" line="195"><span class="sVHd0">        return</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="slqww">dumps</span><span class="sP7_E">(
</span></span><span class="line" line="196"><span class="sP7_E">            {</span><span class="sjJ54">"</span><span class="s_sjI">error</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sbsja"> f</span><span class="s_sjI">"HTTP </span><span class="srdBf">{</span><span class="slqww">status_code</span><span class="srdBf">}</span><span class="s_sjI">: </span><span class="srdBf">{</span><span class="slqww">err_msg</span><span class="srdBf">}</span><span class="s_sjI">"</span><span class="sP7_E">},</span><span class="s99_P"> ensure_ascii</span><span class="smGrS">=</span><span class="s39Yj">False
</span></span><span class="line" line="197"><span class="sP7_E">        )
</span></span><span class="line" line="198"><span emptyLinePlaceholder>
</span></span><span class="line" line="199"><span class="sbsja">    def</span><span class="sGLFI"> _format_exception</span><span class="sP7_E">(</span><span class="smCYv">self</span><span class="sP7_E">,</span><span class="sFwrP"> e</span><span class="sP7_E">:</span><span class="sZMiF"> Exception</span><span class="sP7_E">)</span><span class="sP7_E"> -></span><span class="sZMiF"> str</span><span class="sP7_E">:
</span></span><span class="line" line="200"><span class="s2W-s">        """</span><span class="sithA">异常格式化保持不变</span><span class="s2W-s">"""
</span></span><span class="line" line="201"><span class="su5hD">        err_type </span><span class="smGrS">=</span><span class="sZMiF"> type</span><span class="sP7_E">(</span><span class="slqww">e</span><span class="sP7_E">).</span><span class="s_hVV">__name__
</span></span><span class="line" line="202"><span class="sVHd0">        return</span><span class="su5hD"> json</span><span class="sP7_E">.</span><span class="slqww">dumps</span><span class="sP7_E">({</span><span class="sjJ54">"</span><span class="s_sjI">error</span><span class="sjJ54">"</span><span class="sP7_E">:</span><span class="sbsja"> f</span><span class="s_sjI">"</span><span class="srdBf">{</span><span class="slqww">err_type</span><span class="srdBf">}</span><span class="s_sjI">: </span><span class="srdBf">{</span><span class="sZMiF">str</span><span class="sP7_E">(</span><span class="slqww">e</span><span class="sP7_E">)</span><span class="srdBf">}</span><span class="s_sjI">"</span><span class="sP7_E">},</span><span class="s99_P"> ensure_ascii</span><span class="smGrS">=</span><span class="s39Yj">False</span><span class="sP7_E">)
</span></span></code></pre><ul><li>函数配置中，Api URL、Api Key、模型名字和上个教程一样，输入阿里云百炼对应的即可。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/13.png"></img></figure><ul><li>启用该函数</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/07.png"></img></figure><h2 id="_4-设置新模型">4. 设置新模型</h2><ul><li>前往 <code>管理员设置</code> -> <code>模型</code>，将新的 <code>deepseek-r1-fix</code> 设为启用，还可手动修改 Logo 图片等。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/14.png"></img></figure><ul><li>然后就可以在对话中看到思考过程了，出现「正在思考」的时候，可以点击旁边的箭头展开思考过程。</li></ul><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/15.png"></img></figure><h2 id="_5-联网搜索-error-searching-的问题">5. 联网搜索 Error searching 的问题</h2><p>目前使用下来发现一个问题，配置了 Pipeline 之后，虽然能够正常显示深度思考过程了，但是使用「联网搜索」会出现 <code>Error searching</code> 的问题。</p><p><del>目前发现一个临时解决方案，就是去 <code>管理员设置</code> -> <code>界面</code> 中关闭 <code>网页搜索关键词生成</code>。初步判断是经过了 Pipeline 的时候，会导致联网搜索的关键词生成出现问题，从而传到搜索引擎的是空的内容，导致搜索失败。临时关闭搜索关键词生成，导致的问题是用户输入内容会直接作为搜索引擎的搜索关键词，不过这个倒能接受。</del></p><p>2025-02-18 更新：发现一个完美的解决方案，在 <code>管理员设置</code> -> <code>界面</code> -> <code>外部模型</code> 中选择一个其他模型（非配置了 Pipeline 的这个模型，例如 <code>qwen-max-latest</code> 或 <code>deepseek-v3</code>）即可，尽量不要用 <code>deepseek-r1</code>，会比较慢。</p><figure><img src="https://hadb.me/static/posts/2025/20250212.display-deepseek-r1-thinking/16.png"></img></figure><hr></hr><img src="https://hadb.me/static/reward-code.jpg" alt="赞赏码"></img>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[关于何时解放台湾的思考]]></title>
        <id>/posts/2025/thoughts-on-when-to-liberate-taiwan</id>
        <link href="https://hadb.me/posts/2025/thoughts-on-when-to-liberate-taiwan"/>
        <updated>2025-04-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天一早就看到新闻，东部战区又在台岛周边开展军演，大家对“温水煮青蛙”式的军演已经习以为常。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250401.thoughts-on-when-to-liberate-taiwan/cover.jpg" alt="封面" /><p>今天一早就看到新闻，东部战区又在台岛周边开展军演，大家对“温水煮青蛙”式的军演已经习以为常。</p><p>能不能打得起来，什么时候打，有各种各样的讨论。最近看了很多内容，我想从其中看到的一个角度，搜集一些资料，进行一些思考，供大家参考。</p><p>台湾问题是一个跨度很长的问题，国家对于台湾问题的战略，必然也是一个周期很长的战略，我们很难去根据现有信息去准确预测一个时间点，但从一些公开信息，可以看到一些策略上的变化。</p><h2 id="台湾问题白皮书的变化">台湾问题白皮书的变化</h2><p>关于台湾问题，国台办分别在1993年9月、2000年2月、2022年8月发表过三份白皮书，查阅地址：<a href="https://www.mfa.gov.cn/web/ziliao_674904/zt_674979/dnzt_674981/qtzt/twwt/twwtbps/" rel="nofollow">台湾问题白皮书</a>。</p><p>三份白皮书的发布背景和核心内容大致如下：</p><h3 id="_1-1993年8月台湾问题与中国的统一">1. 1993年8月：《台湾问题与中国的统一》</h3><ul><li>背景：两岸关系开始改善，1992年“九二共识”达成，1993年汪辜会谈举行。</li><li>核心内容：首次系统阐述台湾问题的历史由来和“一国两制”统一方案，强调通过和平方式实现统一。</li></ul><h3 id="_2-2000年2月一个中国的原则与台湾问题">2. 2000年2月：《一个中国的原则与台湾问题》</h3><ul><li>背景：李登辉提出“两国论”，陈水扁上台，两岸关系趋于紧张。</li><li>核心内容：强调“一个中国”原则是两岸关系的政治基础，反对任何形式的“台独”。</li></ul><h3 id="_3-2022年8月台湾问题与新时代中国统一事业">3. 2022年8月：《台湾问题与新时代中国统一事业》</h3><ul><li>背景：两岸关系进一步紧张，民进党拒绝承认“九二共识”，美国加强对台支持。</li><li>核心内容：强调统一是历史必然，并与新时代发展目标结合，重申“一国两制”，但对“台独”采取更强硬立场。</li></ul><h3 id="三份白皮书对比">三份白皮书对比</h3><table><thead><tr><th>年份</th><th>背景</th><th>核心内容</th><th>态度</th></tr></thead><tbody><tr><td>1993</td><td>两岸刚开始接触</td><td>“一国两制”，和平统一</td><td>相对温和，提出台湾可保留军队</td></tr><tr><td>2000</td><td>台湾政局变动，出现“两国论”</td><td>“一个中国”，反对“台独”</td><td>更强硬，不排除非和平方式</td></tr><tr><td>2022</td><td>国际局势复杂，民进党拒绝“九二共识”</td><td>统一是必然，反对外部干涉</td><td>更自信，不再提台湾可保留军队</td></tr></tbody></table><p>从三份白皮书的对比中，我们可以看到，国家对于台湾问题的态度是随着国际局势和两岸关系的变化而变化的，越来越强硬了。</p><p>近几年没有再公布新的白皮书，但从一些公开信息中，也可以看到一些新的变化。</p><p>虽然说“和平统一、一国两制”是解决台湾问题的基本方针，是实现祖国统一的最佳方式，但2024年、2025年的政府工作报告中，涉台内容都只是强调“坚定不移推进祖国统一大业”，已连续两年未提及“和平统一”。<a href="https://news.cctv.com/2025/03/12/ARTIHOmZRIB7Sr5CzzM0GAfA250312.shtml" rel="nofollow">参考</a></p><h2 id="与他国联合声明的变化">与他国联合声明的变化</h2><p>发表于2025年2月9日的《经济学人》杂志的一篇文章《<a href="https://www.economist.com/international/2025/02/09/chinas-stunning-new-campaign-to-turn-the-world-against-taiwan" rel="nofollow">China’s stunning new campaign to turn the world against Taiwan</a>》，详细分析了中国近期在全球范围内推动各国支持其对台湾主权主张的外交行动。</p><p>根据文章内容，以下是主要观点的概述：</p><ul><li>全球支持的增加：目前，已有70个国家正式支持中国对台湾的主权主张，并认可中国为实现统一所采取的“所有”措施。这些国家主要分布在亚洲、欧洲、非洲、大洋洲和拉丁美洲，其中97%位于全球南方，包括南非、埃及和巴基斯坦等国。</li><li>外交措辞的变化：许多国家在与中国的联合声明中，开始采用更为明确支持中国统一努力的措辞。例如，斯里兰卡在2025年1月的联合声明中，首次表示“坚定支持中国政府为实现国家统一所做的所有努力”，取代了2024年声明中较为模糊的支持中国“维护主权和领土完整”的表述。</li><li>非洲国家的集体支持：2024年9月，53个非洲国家在北京举行的峰会上签署声明，承认台湾是中国领土的一部分，并“坚定支持”中国的统一努力。这与2021年峰会期间未明确提及台湾的立场形成鲜明对比。</li><li>中国的战略意图：中国的这一外交攻势旨在为其对台湾的施压行动争取全球支持，包括可能实施的封锁或检查措施。美国官员表示，中国领导人习近平已指示军方在2027年前具备攻台能力。</li><li>对西方制裁的预防：通过确保全球多数国家认可其行动的合法性，中国希望在台湾问题上避免遭受类似俄罗斯在乌克兰问题上所面临的国际制裁和孤立。</li></ul><p>无法去一一验证数据的真实性，我去外交部官网查阅了近几年的一些联合声明，有关台湾问题的部分列举如下：</p><ul><li>2025年03月28日，《<a href="https://www.mfa.gov.cn/web/zyxw/202503/t20250328_11584034.shtml" rel="nofollow">中华人民共和国和孟加拉人民共和国联合新闻稿</a>》：双方强调，联合国大会第2758号决议权威性不容质疑和挑战。孟方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。孟方反对“台独”。孟方在涉及中国核心利益的问题上支持中方，支持中方为捍卫国家主权和领土完整所作努力。</li><li>2025年02月06日，《<a href="https://www.mfa.gov.cn/web/zyxw/202502/t20250206_11550291.shtml" rel="nofollow">中华人民共和国和文莱达鲁萨兰国关于深化战略合作伙伴关系、推进中文命运共同体建设的联合声明</a>》：双方重申联合国大会第2758号决议的重要性。文方重申坚持一个中国政策，认为台湾是中华人民共和国领土不可分割的一部分。文莱支持两岸关系和平发展和中国国家统一。</li><li>2025年02月06日，《<a href="https://www.mfa.gov.cn/web/zyxw/202502/t20250206_11550130.shtml" rel="nofollow">中华人民共和国和巴基斯坦伊斯兰共和国联合声明</a>》：双方强调联合国大会第2758号决议权威性不容质疑和挑战。巴方重申坚定奉行一个中国原则，认为台湾是中华人民共和国领土不可分割的一部分、台湾问题是中国核心利益中的核心，坚定支持中方为实现国家统一所作的一切努力，坚决反对任何形式的“台独”。</li><li>2025年02月05日，《<a href="https://www.mfa.gov.cn/web/zyxw/202502/t20250205_11549624.shtml" rel="nofollow">中华人民共和国和吉尔吉斯共和国关于深化新时代全面战略伙伴关系的联合声明</a>》：吉方重申恪守一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。吉方反对任何利用台湾问题干涉中国内政的图谋，反对任何形式的“台湾独立”，坚定支持中国政府实现国家统一。</li><li>2025年01月16日，《<a href="https://www.mfa.gov.cn/web/zyxw/202501/t20250116_11536630.shtml" rel="nofollow">中华人民共和国和斯里兰卡民主社会主义共和国联合声明</a>》：双方重申联合国大会第2758号决议权威性。斯方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中华人民共和国领土不可分割的一部分。斯方坚定支持中国政府为实现国家统一所作的一切努力，反对任何形式的“台湾独立”。</li><li>2024年12月03日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202412/t20241218_11497221.shtml" rel="nofollow">中华人民共和国和尼泊尔联合声明</a>》：尼方忆及联合国大会第2758号决议，重申坚定奉行一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。尼方坚定支持中方为实现国家统一所作努力，反对“台湾独立”。</li><li>2024年11月26日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497348.shtml" rel="nofollow">中华人民共和国和萨摩亚独立国联合声明</a>》：萨方坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，认同中国政府为实现国家统一所做的努力。</li><li>2024年11月21日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497520.shtml" rel="nofollow">中华人民共和国和巴西联邦共和国关于携手构建更公正世界和更可持续星球的中巴命运共同体的联合声明</a>》：巴方重申坚持一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。巴方支持中国为实现国家和平统一所作努力。</li><li>2024年11月15日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497859.shtml" rel="nofollow">中华人民共和国和秘鲁共和国关于深化全面战略伙伴关系的联合声明</a>》：双方重申恪守《联合国宪章》的宗旨和原则，尊重各国国家主权、领土完整和核心利益。秘方重申坚定恪守一个中国原则。</li><li>2024年11月09日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202412/t20241218_11497865.shtml" rel="nofollow">中华人民共和国和印度尼西亚共和国关于推进全面战略伙伴关系和中印尼命运共同体建设的联合声明</a>》：印尼重申一贯坚定奉行一个中国原则，该原则在联合国大会第2758号决议中得到确认，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，印尼坚定支持中国政府为实现国家和平统一所作的努力。</li><li>2024年11月01日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497713.shtml" rel="nofollow">中华人民共和国与斯洛伐克共和国关于建立战略伙伴关系的联合声明</a>》：斯方重申奉行一个中国政策，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，反对任何干涉中国内政、主权和领土（包括台湾在内）完整的企图。</li><li>2024年10月16日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202412/t20241218_11497591.shtml" rel="nofollow">中华人民共和国和巴基斯坦伊斯兰共和国联合声明</a>》：双方强调联合国大会第2758号决议权威性不容质疑和挑战。巴方重申坚定奉行一个中国原则，台湾是中华人民共和国领土不可分割的一部分，坚定支持中方为实现国家统一所作的一切努力，坚决反对任何形式的“台独”。</li><li>2024年10月14日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497563.shtml" rel="nofollow">中华人民共和国和越南社会主义共和国联合声明</a>》：越方重申坚定奉行一个中国政策，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，支持两岸关系和平发展和中国统一大业，坚决反对任何形式的“台独”分裂活动，不同台湾发展任何形式的官方关系。</li><li>2024年10月12日，《<a href="https://www.mfa.gov.cn/web/zyxw/202412/t20241218_11497128.shtml" rel="nofollow">中华人民共和国和老挝人民民主共和国联合声明</a>》：老方重申坚定奉行一个中国原则，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。反对任何损害中国主权和领土完整的言行，反对任何形式的“台独”分裂活动，反对外部势力以任何借口干涉中国内政，支持中国为实现国家统一所作的一切努力。</li><li>2024年09月06日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202409/t20240906_11486777.shtml" rel="nofollow">中华人民共和国和刚果共和国关于深化全面战略合作伙伴关系、构建高水平中刚命运共同体的联合声明</a>》：刚方重申坚定奉行一个中国原则，认为世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，重申不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作一切努力。</li><li>2024年09月05日，《<a href="https://www.mfa.gov.cn/web/zyxw/202409/t20240905_11486084.shtml" rel="nofollow">中华人民共和国和圣多美和普林西比民主共和国关于建立战略伙伴关系的联合声明</a>》：圣普方重申坚定奉行一个中国原则，认为世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，重申不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年09月05日，《<a href="https://www.mfa.gov.cn/web/zyxw/202409/t20240905_11486032.shtml" rel="nofollow">中华人民共和国和卢旺达共和国关于共同推动落实三大全球倡议的联合声明</a>》：卢方重申坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，支持中国政府为实现国家统一所作的一切努力，认为中国同任何国家一样，有权自主处理其内部主权事务。</li><li>2024年09月04日，《<a href="https://www.mfa.gov.cn/web/zyxw/202409/t20240904_11485222.shtml" rel="nofollow">中华人民共和国和塞内加尔共和国关于深化全面战略合作伙伴关系、构建高水平中塞命运共同体的联合声明</a>》：塞方重申，坚定不移奉行一个中国原则，认为世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，重申不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作一切努力。</li><li>2024年09月04日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202409/t20240904_11484700.shtml" rel="nofollow">中华人民共和国和尼日利亚联邦共和国关于建立全面战略伙伴关系、构建高水平中尼命运共同体的联合声明</a>》：尼方坚定奉行一个中国原则，认为世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台湾独立”，反对干涉中国内政，坚定支持中国政府为实现国家统一所作努力。</li><li>2024年09月04日，《<a href="https://www.mfa.gov.cn/web/zyxw/202409/t20240904_11484698.shtml" rel="nofollow">中华人民共和国和津巴布韦共和国关于深化和提升全面战略合作伙伴关系、构建高水平中津命运共同体的联合声明</a>》：津方强调无条件支持一个中国原则始终是津外交政策的鲜明标志，认为世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，重申不同台湾进行任何形式的官方往来，支持中国政府为实现国家统一所作的一切努力。</li><li>2024年09月03日，《<a href="https://www.mfa.gov.cn/web/zyxw/202409/t20240903_11483773.shtml" rel="nofollow">中华人民共和国和南非共和国关于建立新时代全方位战略合作伙伴关系的联合声明</a>》：南非政府重申奉行一个中国政策，承认世界上只有一个中国，中华人民共和国政府是代表中国的唯一合法政府，台湾是中国不可分割的一部分。南非支持中国政府为实现国家统一所作努力。</li><li>2024年08月20日，《<a href="https://www.mfa.gov.cn/web/zyxw/202408/t20240820_11477053.shtml" rel="nofollow">中华人民共和国和斐济共和国联合声明</a>》：斐方坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。</li><li>2024年08月20日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202408/t20240820_11477050.shtml" rel="nofollow">中华人民共和国和越南社会主义共和国关于进一步加强全面战略合作伙伴关系、推进中越命运共同体建设的联合声明</a>》：越方重申坚定奉行一个中国政策，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。支持两岸关系和平发展和中国统一大业，坚决反对任何形式的“台独”分裂活动，不同台湾发展任何形式的官方关系。</li><li>2024年07月29日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202407/t20240729_11462593.shtml" rel="nofollow">中华人民共和国和东帝汶民主共和国关于深化全面战略伙伴关系的联合声明</a>》：双方强调联合国大会第2758号决议的权威性不容置疑。东帝汶重申将坚定不移地奉行一个中国原则，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚决反对任何形式的“台湾独立”，不与台湾建立任何形式的官方关系，不进行任何形式的官方往来，支持中国政府为实现国家统一所作的一切努力。</li><li>2024年07月12日，《<a href="https://www.mfa.gov.cn/web/zyxw/202407/t20240712_11453165.shtml" rel="nofollow">中华人民共和国和所罗门群岛联合声明</a>》：所方坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府；坚决反对任何形式的“台湾独立”，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年07月12日，《<a href="https://www.mfa.gov.cn/web/zyxw/202407/t20240712_11453163.shtml" rel="nofollow">中华人民共和国和瓦努阿图共和国联合声明</a>》：瓦方坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府；坚定支持中国政府为实现国家统一所作的努力，反对“台湾独立”。</li><li>2024年07月10日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202407/t20240711_11451950.shtml" rel="nofollow">中华人民共和国和孟加拉人民共和国关于建立全面战略合作伙伴关系的联合声明</a>》：孟方重申坚定奉行一个中国原则，中华人民共和国政府代表全中国，台湾是中国的一部分。孟方在涉及中国核心利益的问题上支持中方，支持中方为捍卫国家主权和领土完整所作努力。</li><li>2024年07月10日，《<a href="https://www.mfa.gov.cn/web/zyxw/202407/t20240710_11451714.shtml" rel="nofollow">中华人民共和国和几内亚比绍共和国关于建立战略伙伴关系的联合声明</a>》：几内亚比绍重申坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，声明不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年07月05日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202407/t20240705_11449082.shtml" rel="nofollow">中华人民共和国和塔吉克斯坦共和国关于发展新时代全面战略合作伙伴关系的联合声明</a>》：塔方重申坚定不移恪守一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，反对任何形式的“台湾独立”，坚定支持中方为维护主权和领土完整，实现国家统一所作的一切努力。</li><li>2024年07月03日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202407/t20240703_11446766.shtml" rel="nofollow">中华人民共和国和阿塞拜疆共和国关于建立战略伙伴关系的联合声明</a>》：阿方坚定奉行一个中国原则，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台独”，支持两岸关系和平发展和中国政府为实现国家统一所作的努力。</li><li>2024年07月03日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202407/t20240703_11446684.shtml" rel="nofollow">中华人民共和国和哈萨克斯坦共和国联合声明</a>》：哈方坚定支持一个中国原则，即世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。哈方反对任何形式的“台独”，反对外部势力干涉，重申不同台湾开展任何形式的官方往来，支持中国政府为实现国家统一所作的一切努力。</li><li>2024年06月22日，《<a href="https://www.mfa.gov.cn/web/wjbzhd/202406/t20240622_11439939.shtml" rel="nofollow">中华人民共和国和尼日利亚联邦共和国政府间委员会首次全会联合声明</a>》：尼方认为世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台湾独立”，反对外部势力干涉中国内政，支持中国政府为实现国家统一所作努力。</li><li>2024年06月20日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202406/t20240620_11439039.shtml" rel="nofollow">中华人民共和国政府和马来西亚政府关于深化提升全面战略伙伴关系、共建中马命运共同体的联合声明</a>》：马来西亚重申两国领导人1974年5月31日签署的联合公报原则，坚定奉行一个中国政策，承认台湾是中华人民共和国领土不可分割的一部分，支持中国实现国家统一，不支持任何“台独”主张。</li><li>2024年06月07日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202406/t20240609_11415901.shtml" rel="nofollow">中华人民共和国和巴基斯坦伊斯兰共和国联合声明</a>》：双方强调联合国大会第2758号决议权威性不容质疑和挑战。巴方重申坚定奉行一个中国原则，台湾是中华人民共和国领土不可分割的一部分，坚定支持中方为实现国家统一所作的一切努力，坚决反对任何形式的“台湾独立”。</li><li>2024年06月02日，《<a href="https://www.mfa.gov.cn/web/zyxw/202406/t20240602_11368960.shtml" rel="nofollow">中华人民共和国和阿拉伯联合酋长国联合声明</a>》：阿方强调继续坚定恪守一个中国原则，台湾是中国不可分割的一部分，支持中方在涉及自身主权和领土完整问题上所秉持的立场，支持实现中国统一，反对外部势力干涉中国内政。</li><li>2024年05月31日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202405/t20240531_11368597.shtml" rel="nofollow">中华人民共和国和巴林王国关于建立全面战略伙伴关系的联合声明</a>》：巴方强调坚定恪守一个中国原则，支持中国维护主权和领土完整，同时也继续坚定支持中方在核心利益问题上的立场。</li><li>2024年05月31日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202405/t20240531_11367022.shtml" rel="nofollow">中华人民共和国和突尼斯共和国关于建立战略伙伴关系的联合声明</a>》：突方重申遵守1971年10月25日通过的联大第2758号决议，该决议强调一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，突方支持中国在其全境行使主权，支持中国为实现国家统一和捍卫核心利益所作努力。</li><li>2024年05月30日，《<a href="https://www.mfa.gov.cn/web/zyxw/202405/t20240530_11314441.shtml" rel="nofollow">中华人民共和国和阿拉伯埃及共和国关于深化全面战略伙伴关系的联合声明</a>》：埃方强调继续坚定恪守一个中国原则，台湾是中国不可分割的一部分，支持中方在涉及自身主权和领土完整问题上所秉持的立场，支持实现中国统一，反对外部势力干涉中国内政。</li><li>2024年05月28日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202405/t20240528_11313275.shtml" rel="nofollow">中华人民共和国和赤道几内亚共和国关于建立全面战略合作伙伴关系的联合声明</a>》：赤几方重申坚定奉行一个中国原则，认为世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年05月16日，《<a href="https://www.mfa.gov.cn/web/zyxw/202405/t20240516_11305860.shtml" rel="nofollow">中华人民共和国和俄罗斯联邦在两国建交75周年之际关于深化新时代全面战略协作伙伴关系的联合声明</a>》：俄方重申恪守一个中国原则，承认台湾是中华人民共和国不可分割的一部分，反对任何形式的“台独”，坚定支持中方维护国家主权和领土完整、实现国家统一的举措。</li><li>2024年05月10日，《<a href="https://www.mfa.gov.cn/web/zyxw/202405/t20240510_11302174.shtml" rel="nofollow">中华人民共和国和匈牙利关于建立新时代全天候全面战略伙伴关系的联合声明</a>》：匈牙利政府坚定奉行一个中国原则，重申世界上只有一个中国，中华人民共和国政府是代表中国的唯一合法政府。匈牙利反对任何形式的破坏中国国家统一的分裂行为。</li><li>2024年05月09日，《<a href="https://www.mfa.gov.cn/web/zyxw/202405/t20240509_11301439.shtml" rel="nofollow">中华人民共和国和塞尔维亚共和国关于深化和提升全面战略伙伴关系、构建新时代中塞命运共同体的联合声明</a>》：塞方重申坚定支持一个中国原则，世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府，反对任何形式的“台湾独立”，反对干涉中国内政，重申不同台湾当局进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年04月10日，《<a href="https://www.mfa.gov.cn/web/zyxw/202404/t20240410_11279506.shtml" rel="nofollow">中华人民共和国和密克罗尼西亚联邦联合声明</a>》：密方坚定奉行一个中国原则，承认世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府；坚决反对任何形式的“台湾独立”，坚定支持中国实现国家统一。</li><li>2024年03月29日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202403/t20240329_11273744.shtml" rel="nofollow">中华人民共和国和斯里兰卡民主社会主义共和国联合声明</a>》：斯方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，支持中国政府为维护主权和领土完整所作的一切努力，反对任何形式的“台湾独立”。</li><li>2024年03月26日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202403/t20240326_11271182.shtml" rel="nofollow">中华人民共和国和瑙鲁共和国联合声明</a>》：瑙方重申坚定恪守一个中国原则，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分；坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年03月15日，《<a href="https://www.mfa.gov.cn/web/zyxw/202403/t20240315_11261416.shtml" rel="nofollow">中华人民共和国和安哥拉共和国关于建立全面战略合作伙伴关系的联合声明</a>》：安方重申坚定奉行一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚定支持中国为实现国家统一所作的一切努力，坚定支持不干涉内政原则。</li><li>2024年02月28日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202402/t20240228_11251588.shtml" rel="nofollow">中华人民共和国和塞拉利昂共和国关于深化全面战略合作伙伴关系的联合声明</a>》：塞方重申坚定奉行一个中国原则，认为中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分；坚决反对任何形式的“台湾独立”，反对外部势力干涉中国内政，重申不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2024年01月26日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202401/t20240126_11233786.shtml" rel="nofollow">中华人民共和国和肯尼亚共和国外交部长联合声明</a>》：肯方重申世界上只有一个中国，台湾是中国领土不可分割的一部分，中华人民共和国政府是代表全中国的唯一合法政府。一个中国原则是公认的国际关系基本准则和国际社会普遍共识。</li><li>2024年01月25日，《<a href="https://www.mfa.gov.cn/web/zyxw/202401/t20240125_11232777.shtml" rel="nofollow">中华人民共和国和乌兹别克斯坦共和国关于新时代全天候全面战略伙伴关系的联合声明</a>》：乌方坚定支持一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的台湾独立，重申不同台湾进行任何形式的官方往来，坚定支持两岸关系和平发展，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2023年12月20日，《<a href="https://www.mfa.gov.cn/web/zyxw/202312/t20231220_11207478.shtml" rel="nofollow">中华人民共和国和尼加拉瓜共和国关于建立战略伙伴关系的联合声明</a>》：尼方坚定奉行一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分；坚决反对任何形式的“台湾独立”，重申不同台湾进行任何形式的官方往来，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2023年12月13日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202312/t20231213_11201756.shtml" rel="nofollow">中华人民共和国和越南社会主义共和国关于进一步深化和提升全面战略合作伙伴关系、构建具有战略意义的中越命运共同体的联合声明</a>》：越方重申坚定奉行一个中国政策，承认台湾是中国领土不可分割的一部分，坚决反对任何形式的“台独”分裂活动，支持不干涉各国内政原则，不同台湾发展任何形式的官方关系。</li><li>2023年11月23日，《<a href="https://www.mfa.gov.cn/web/zyxw/202311/t20231124_11186179.shtml" rel="nofollow">中华人民共和国和乌拉圭东岸共和国关于建立全面战略伙伴关系的联合声明</a>》：乌拉圭重申恪守一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，支持中国政府为实现国家统一所作努力。乌方重申支持中方在涉港等核心利益问题上的正当立场，支持中方维护国家主权安全的努力。</li><li>2023年10月25日，《<a href="https://www.mfa.gov.cn/web/zyxw/202310/t20231025_11168329.shtml" rel="nofollow">中华人民共和国和哥伦比亚共和国关于建立战略伙伴关系的联合声明</a>》：哥伦比亚政府重申恪守一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚定支持中国政府为实现国家统一所作的努力。</li><li>2023年10月20日，《<a href="https://www.mfa.gov.cn/web/zyxw/202310/t20231021_11165407.shtml" rel="nofollow">中华人民共和国和斯里兰卡民主社会主义共和国联合声明</a>》：斯方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，支持中国政府为维护主权和领土完整所作的一切努力，反对任何形式的“台湾独立”。</li><li>2023年10月18日，《<a href="https://www.mfa.gov.cn/web/zyxw/202310/t20231018_11163274.shtml" rel="nofollow">中华人民共和国和印度尼西亚共和国关于深化全方位战略合作的联合声明</a>》：印尼重申一贯坚定奉行一个中国政策，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国不可分割的一部分，坚定支持中国政府为实现国家和平统一所作的努力。</li><li>2023年10月17日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202310/t20231018_11162574.shtml" rel="nofollow">中华人民共和国和巴布亚新几内亚独立国联合声明</a>》：巴新方重申，坚定奉行其一贯坚持的一个中国政策，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚决反对“台独”，支持中华人民共和国政府为实现国家统一所作的一切努力。</li><li>2023年10月17日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202310/t20231017_11162386.shtml" rel="nofollow">中华人民共和国与埃塞俄比亚联邦民主共和国关于建立全天候战略伙伴关系的联合声明</a>》：埃塞俄比亚政府重申坚持一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，支持中方为实现国家统一所作的一切努力。</li><li>2023年10月17日，《<a href="https://www.mfa.gov.cn/web/zyxw/202310/t20231017_11162352.shtml" rel="nofollow">中华人民共和国和智利共和国联合声明</a>》：智方重申坚定奉行一个中国原则，支持中国和平统一，坚决反对任何形式的“台独”。</li><li>2023年09月26日，《<a href="https://www.mfa.gov.cn/web/zyxw/202309/t20230926_11149957.shtml" rel="nofollow">中华人民共和国和尼泊尔联合声明</a>》：尼方重申坚定奉行一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对“台湾独立”。</li><li>2023年09月23日，《<a href="https://www.mfa.gov.cn/web/zyxw/202309/t20230923_11148672.shtml" rel="nofollow">中华人民共和国和东帝汶民主共和国关于建立全面战略伙伴关系的联合声明</a>》：东方重申坚定奉行一个中国原则，承认世界上只有一个中国，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台湾独立”，不与台湾建立任何形式的官方关系，不进行任何形式的官方往来，支持中国政府为实现国家统一所作的努力。</li><li>2023年09月22日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202309/t20230922_11148442.shtml" rel="nofollow">中华人民共和国和阿拉伯叙利亚共和国关于建立战略伙伴关系的联合声明</a>》：叙方坚定奉行一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国不可分割的一部分，支持中国维护国家主权、统一和领土完整，坚决反对任何势力干涉中国内政，支持中国政府为实现国家统一所作的一切努力。</li><li>2023年09月15日，《<a href="https://www.mfa.gov.cn/web/zyxw/202309/t20230915_11143613.shtml" rel="nofollow">中华人民共和国和赞比亚共和国关于建立全面战略合作伙伴关系的联合声明</a>》：赞方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚定支持中国政府为实现国家统一所作的努力。</li><li>2023年09月14日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202309/t20230914_11142477.shtml" rel="nofollow">中华人民共和国和委内瑞拉玻利瓦尔共和国关于建立全天候战略伙伴关系的联合声明</a>》：委方重申恪守一个中国原则的坚定立场，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2023年09月01日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202309/t20230901_11137094.shtml" rel="nofollow">中华人民共和国和贝宁共和国关于建立战略伙伴关系的联合声明</a>》：贝方重申坚定奉行一个中国原则，认为台湾是中国不可分割的一部分，反对任何损害中国主权和领土完整的言行，坚定支持中国政府为实现国家统一所作的努力。</li><li>2023年08月23日，《<a href="https://www.mfa.gov.cn/web/zyxw/202308/t20230823_11130465.shtml" rel="nofollow">中华人民共和国和南非共和国联合声明</a>》：南非重申坚定奉行一个中国政策。</li><li>2023年07月31日，《<a href="https://www.mfa.gov.cn/web/zyxw/202307/t20230731_11120026.shtml" rel="nofollow">中华人民共和国与格鲁吉亚关于建立战略伙伴关系的联合声明</a>》：格方坚定奉行一个中国原则。</li><li>2023年07月31日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202307/t20230731_11120009.shtml" rel="nofollow">中华人民共和国和圭亚那合作共和国联合声明</a>》：圭方重申坚定支持一个中国原则。</li><li>2023年07月18日，《<a href="https://www.mfa.gov.cn/web/zyxw/202307/t20230718_11114859.shtml" rel="nofollow">中华人民共和国和阿尔及利亚民主人民共和国联合声明</a>》：阿方重申坚持一个中国原则，重申台湾是中国领土不可分割的一部分，反对任何形式的“台湾独立”。</li><li>2023年07月10日，《<a href="https://www.mfa.gov.cn/web/zyxw/202307/t20230710_11110974.shtml" rel="nofollow">中华人民共和国和所罗门群岛关于建立新时代相互尊重、共同发展的全面战略伙伴关系的联合声明</a>》：所方重申，坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚决反对任何形式的“台独”，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2023年06月28日，《<a href="https://www.mfa.gov.cn/web/zyxw/202306/t20230628_11104932.shtml" rel="nofollow">中华人民共和国和新西兰关于全面战略伙伴关系的联合声明</a>》：新西兰重申坚持一个中国政策。</li><li>2023年06月14日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202306/t20230614_11097775.shtml" rel="nofollow">中华人民共和国和巴勒斯坦国关于建立战略伙伴关系的联合声明</a>》：巴方坚定奉行一个中国原则，支持中国维护国家主权、统一和领土完整，坚决反对任何势力干涉中国内政；重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，坚决反对任何形式的“台独”，重申不同台湾进行任何形式的官方往来，支持中国政府为实现国家统一所作的一切努力。</li><li>2023年06月12日，《<a href="https://www.mfa.gov.cn/web/zyxw/202306/t20230612_11095432.shtml" rel="nofollow">中华人民共和国和洪都拉斯共和国联合声明</a>》：洪都拉斯政府支持联合国大会第2758号决议，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。洪方坚定支持并遵守一个中国原则，坚决反对任何有悖于这一原则的行为，反对任何形式的“台湾独立”，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2023年05月26日，《<a href="https://www.mfa.gov.cn/web/zyxw/202305/t20230526_11084472.shtml" rel="nofollow">中华人民共和国和刚果民主共和国关于建立全面战略合作伙伴关系的联合声明</a>》：刚方重申坚定奉行一个中国原则，认为台湾是中国不可分割的一部分，反对任何损害中国主权和领土完整的言行。</li><li>2023年05月18日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202305/t20230518_11079722.shtml" rel="nofollow">中华人民共和国和乌兹别克斯坦共和国联合声明</a>》：乌方坚定支持一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。重申不同台湾进行任何形式的官方往来，坚定支持两岸关系和平发展和中国政府为实现国家统一所作的一切努力。</li><li>2023年05月18日，《<a href="https://www.mfa.gov.cn/web/zyxw/202305/t20230518_11079610.shtml" rel="nofollow">中华人民共和国和塔吉克斯坦共和国联合声明</a>》：塔方坚定恪守一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。塔方坚决反对任何形式的“台独”，重申不同台湾进行任何形式的官方往来，支持中国政府为实现国家统一所作的一切努力。</li><li>2023年05月17日，《<a href="https://www.mfa.gov.cn/web/zyxw/202305/t20230517_11079124.shtml" rel="nofollow">中华人民共和国和哈萨克斯坦共和国联合声明</a>》：哈方坚定奉行一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台独”，重申不同台湾进行任何形式的官方往来，支持中国政府为实现国家和平统一所作的一切努力。</li><li>2023年04月19日，《<a href="https://www.mfa.gov.cn/web/zyxw/202304/t20230419_11061924.shtml" rel="nofollow">中华人民共和国和加蓬共和国关于建立全面战略合作伙伴关系的联合声明</a>》：加方重申坚定奉行一个中国原则，认为台湾是中国不可分割的一部分，反对任何损害中国主权和领土完整的言行，坚定支持中国政府为实现国家统一所作的努力。</li><li>2023年04月14日，《<a href="https://www.mfa.gov.cn/web/zyxw/202304/t20230414_11059627.shtml" rel="nofollow">中华人民共和国和巴西联邦共和国关于深化全面战略伙伴关系的联合声明</a>》：巴方重申坚定奉行一个中国原则，中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。巴方重申领土完整原则，支持两岸关系和平发展。</li><li>2023年04月07日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202304/t20230407_11056239.shtml" rel="nofollow">中华人民共和国和法兰西共和国联合声明</a>》：法国重申坚持一个中国政策。</li><li>2023年03月22日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202303/t20230322_11046188.shtml" rel="nofollow">中华人民共和国和俄罗斯联邦关于深化新时代全面战略协作伙伴关系的联合声明</a>》：俄方重申恪守一个中国原则，承认台湾是中国领土不可分割的一部分，反对任何形式的“台独”，坚定支持中方维护本国主权和领土完整的举措。</li><li>2023年03月02日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202303/t20230302_11033872.shtml" rel="nofollow">中华人民共和国和白俄罗斯共和国关于新时代进一步发展两国全天候全面战略伙伴关系的联合声明</a>》：白方恪守一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，重申台湾是中国领土不可分割的一部分，反对任何形式的“台独”，支持中国政府为实现国家统一所作的一切努力，支持中方维护国家安全和领土完整、保障公民权利的立场。</li><li>2023年02月16日，《<a href="https://www.mfa.gov.cn/web/zyxw/202302/t20230216_11025836.shtml" rel="nofollow">中华人民共和国和伊朗伊斯兰共和国联合声明</a>》：伊方将继续奉行一个中国政策。</li><li>2023年02月11日，《<a href="https://www.mfa.gov.cn/web/zyxw/202302/t20230211_11023942.shtml" rel="nofollow">中华人民共和国和柬埔寨王国关于构建新时代中柬命运共同体的联合声明</a>》：柬方重申恪守一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分；强调台湾问题是中国内政，任何外部势力无权干涉。柬方反对任何利用台湾问题干涉中国内政、围堵遏制中国的图谋，反对任何形式的“台独”分裂活动，坚定支持中国政府为实现国家统一所作的一切努力，绝不同台湾发展任何形式的官方关系。</li><li>2023年01月06日，《<a href="https://www.mfa.gov.cn/web/zyxw/202301/t20230106_11003592.shtml" rel="nofollow">中华人民共和国和土库曼斯坦联合声明</a>》：土方坚定奉行一个中国原则，坚决反对任何形式的“台独”，支持两岸关系和平发展和中国政府为实现国家统一所做的一切努力。</li><li>2023年01月05日，《<a href="https://www.mfa.gov.cn/web/zyxw/202301/t20230105_11001029.shtml" rel="nofollow">中华人民共和国和菲律宾共和国联合声明</a>》：菲律宾重申恪守一个中国政策。</li><li>2022年12月09日，《<a href="https://www.mfa.gov.cn/web/zyxw/202212/t20221209_10988250.shtml" rel="nofollow">中华人民共和国和沙特阿拉伯王国联合声明</a>》：沙方重申恪守一个中国原则。</li><li>2022年12月02日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202212/t20221202_10984212.shtml" rel="nofollow">关于进一步深化中老命运共同体建设的联合声明</a>》：老方重申坚定奉行一个中国原则，反对任何损害中国主权和领土完整的言行，反对任何形式的“台独”分裂活动，反对外部势力以任何借口干涉中国内政，支持中方维护核心利益，支持中国为实现国家统一所作的一切努力。</li><li>2022年11月28日，《<a href="https://www.mfa.gov.cn/web/zyxw/202211/t20221128_10981908.shtml" rel="nofollow">中华人民共和国和蒙古国关于新时代推进全面战略伙伴关系的联合声明</a>》：蒙方重申坚定奉行一个中国原则，支持中方在台湾、涉港、涉疆、涉藏问题上的立场。</li><li>2022年11月25日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202211/t20221125_10980884.shtml" rel="nofollow">中华人民共和国和古巴共和国关于深化新时代中古关系的联合声明</a>》：古方重申无条件恪守一个中国原则的坚定立场，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分；强调台湾问题是中国内政，任何一方无权干涉。古方坚决反对任何利用台湾问题干涉中国内政的图谋，坚定支持中国政府为实现国家统一所作的一切努力。</li><li>2022年11月19日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202211/t20221119_10978190.shtml" rel="nofollow">中华人民共和国和泰王国关于构建更为稳定、更加繁荣、更可持续命运共同体的联合声明</a>》：泰方坚定奉行一个中国政策，承认台湾是中国不可分割的一部分，承认中华人民共和国政府是代表全中国的唯一合法政府。泰方支持中国“一国两制”方针。</li><li>2022年11月16日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202211/t20221117_10976699.shtml" rel="nofollow">中华人民共和国和印度尼西亚共和国联合声明</a>》：印尼重申坚定奉行一个中国政策，继续支持两岸关系和平发展与中国和平统一。中国重申坚定支持印尼维护国家统一和领土完整的努力。</li><li>2022年11月03日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202211/t20221103_10800012.shtml" rel="nofollow">中华人民共和国和坦桑尼亚联合共和国关于建立全面战略合作伙伴关系的联合声明</a>》：坦方重申坚定奉行一个中国原则，认为台湾是中国不可分割的一部分，反对任何损害中国主权和领土完整的言行。</li><li>2022年11月02日，《<a href="https://www.mfa.gov.cn/web/zyxw/202211/t20221102_10799301.shtml" rel="nofollow">中华人民共和国和巴基斯坦伊斯兰共和国联合声明</a>》：巴方坚定奉行一个中国政策，在台湾、南海、涉港、涉疆和涉藏等问题上支持中国。</li><li>2022年11月02日，《<a href="https://www.mfa.gov.cn/web/zyxw/202211/t20221102_10795594.shtml" rel="nofollow">关于进一步加强和深化中越全面战略合作伙伴关系的联合声明</a>》：越方重申坚定奉行一个中国政策，支持两岸和平发展与中国统一大业，坚决反对任何形式的“台独”分裂活动并一贯支持不干涉各国内政原则，不同台湾发展任何官方关系。</li><li>2022年09月16日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202209/t20220916_10766853.shtml" rel="nofollow">中华人民共和国和白俄罗斯共和国关于建立全天候全面战略伙伴关系的联合声明</a>》：白方重申奉行一个中国原则，承认中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分，反对任何形式的“台湾独立”，支持中国政府为实现国家统一所作的一切努力。</li><li>2022年09月15日，《<a href="https://www.mfa.gov.cn/web/zyxw/202209/t20220915_10766621.shtml" rel="nofollow">中华人民共和国和乌兹别克斯坦共和国联合声明</a>》：乌方坚定奉行一个中国政策，重申中华人民共和国政府是代表全中国的唯一合法政府，台湾是中国领土不可分割的一部分。</li><li>2022年09月15日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202209/t20220915_10766226.shtml" rel="nofollow">中华人民共和国和哈萨克斯坦共和国建交30周年联合声明</a>》：哈方坚定支持一个中国原则，重申中华人民共和国政府是代表全中国的唯一合法政府。这也是联合国大会第2758号决议确定的原则。台湾是中国领土不可分割的一部分，哈方反对任何形式的“台湾独立”，支持两岸关系和平发展以及中国政府为实现国家和平统一所作的一切努力。</li><li>2022年03月20日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202203/t20220320_10653575.shtml" rel="nofollow">中华人民共和国外交部和阿尔及利亚民主人民共和国外交部联合声明</a>》：阿方表示恪守一个中国政策。</li><li>2022年02月06日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202202/t20220206_10639500.shtml" rel="nofollow">中华人民共和国和巴基斯坦伊斯兰共和国联合声明</a>》：巴方坚定奉行一个中国政策，在台湾、南海、涉港、涉疆和涉藏等问题上支持中国。</li><li>2022年02月06日，《<a href="https://www.mfa.gov.cn/web/zyxw/202202/t20220206_10639499.shtml" rel="nofollow">中华人民共和国政府和蒙古国政府联合声明</a>》：蒙方重申坚定奉行一个中国原则，支持中方在台湾、涉港、涉疆、涉藏问题上的立场。</li><li>2022年02月06日，《<a href="https://www.mfa.gov.cn/web/zyxw/202202/t20220206_10639419.shtml" rel="nofollow">中华人民共和国和阿根廷共和国关于深化中阿全面战略伙伴关系的联合声明</a>》：阿方重申坚持一个中国原则。</li><li>2022年02月05日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/1179_674909/202202/t20220205_10639294.shtml" rel="nofollow">中华人民共和国和巴布亚新几内亚独立国联合声明</a>》：巴布亚新几内亚重申坚定奉行一个中国原则，认为涉台、涉疆、涉港、涉藏问题是中国内政，在其“广交友、不树敌”外交政策下，重申不干涉其他主权国家内政的原则。</li><li>2022年02月05日，《<a href="https://www.mfa.gov.cn/web/ziliao_674904/zt_674979/ywzt_675099/2020/kjgzbdfyyq_699171/202202/t20220205_10639272.shtml" rel="nofollow">中华人民共和国和厄瓜多尔共和国关于深化中厄全面战略伙伴关系的联合声明</a>》：厄方重申坚定奉行一个中国原则，支持中国政府为完成国家统一所作努力。</li><li>2022年02月04日，《<a href="https://www.mfa.gov.cn/web/zyxw/202202/t20220204_10638953.shtml" rel="nofollow">中华人民共和国和俄罗斯联邦关于新时代国际关系和全球可持续发展的联合声明</a>》：俄方重申恪守一个中国原则，承认台湾是中国领土不可分割的一部分，反对任何形式的“台独”。</li><li>……再往前的声明就不一一列举了</li></ul><p>可以看到，不同国家联合声明中关于一个中国以及台湾问题的表述有所不同，主要是以下这些条目的组合：</p><ul><li>坚持一个中国原则</li><li>承认中华人民共和国政府是代表全中国的唯一合法政府</li><li>台湾是中国领土不可分割的一部分</li><li>反对任何形式的“台湾独立”</li><li>不同台湾进行任何形式的官方往来</li><li>坚定支持中方为实现国家统一所作的努力</li><li>坚定支持中方为实现国家统一所作的一切努力</li></ul><p>部分国家发布的新的联合声明中，关于台湾问题的表述与之前的联合声明相比，明确增加了“支持中方为实现国家统一所作的努力/一切努力”等外交措辞。例如斯里兰卡、尼泊尔、巴基斯坦、巴西、刚果等。</p><h2 id="总结思考">总结思考</h2><p>解决台湾问题，有哪几个核心前提？正在进行哪些努力？</p><ol><li>军事方面：单独打台湾早已不是问题，关键是要有足够的军事力量来威慑美国和日本等外部势力的干预。造航母、歼-20、六代机、各种军演等，都是在军事方面的努力。</li><li>外交方面：避免在国际上形成对中国的孤立，需要名正言顺、需要争取更多国家的支持。上文中列举的多国的联合声明等就是正在进行中的外交努力。</li><li>经济方面：需要能抵挡美欧的经济制裁。一带一路、大金砖合作、去美元化等，都是在减少经济上对西方依赖所做的努力。</li></ol><p>这几个方面的动作都在同时进行，本文主要从政策和外交方面的一些变化，给大家分析台湾问题的现状和未来可能的走向带来一些参考。</p><p>随着国力增强、国际动作的不断频繁，台湾问题解决的时间点也在不断接近。可以预见，祖国统一已经不再是一件遥远的事情了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[一次 HomeLab 灾难级事故的复盘]]></title>
        <id>/posts/2025/homelab-disaster-postmortem</id>
        <link href="https://hadb.me/posts/2025/homelab-disaster-postmortem"/>
        <updated>2025-07-07T00:00:00.000Z</updated>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250707.homelab-disaster-postmortem/cover.jpg" alt="封面" /><h2 id="时间线">时间线</h2><ul><li><strong>2025-07-07 09:33</strong>: TP-LINK 主路由设备上线告警（上次离线原因：设备重启）</li><li><strong>2025-07-07 09:34</strong>: 收到群晖异常关机的邮件通知（收到该通知说明群晖已经重启过了，实际重启时间会更早一点）</li><li><strong>2025-07-07 09:36</strong>: 尝试登录群晖 DSM，发现域名解析有问题，无法登录；尝试 ToDesk 远程连接家里的 PC，发现不在线（未开机）</li><li><strong>2025-07-07 09:40</strong>: 收到 Uptime Kuma 监控服务的各种告警通知，多项服务不可用</li><li><strong>2025-07-07 09:52</strong>: 通过 TP-LINK 商用云平台远程查看主路由，发现可连接，但由于之前为了 IPTV 改为了光猫的子路由（非桥接），无法查看到公网 IP；尝试通过电信的小翼管家查看公网 IP，发现没有入口可查</li><li><strong>2025-07-07 09:54</strong>: 尝试通过群晖的 QuickConnect 远程访问，发现之前被我关闭了</li><li><strong>2025-07-07 10:30</strong>: 查看自己写的 bots 服务代码（含 ddns 功能），请求失败时，有 backoff 策略，首次失败休眠 1 分钟，然后再失败休眠 10 分钟，再失败休眠 1 小时，决定再等一小时看看</li><li><strong>2025-07-07 11:00</strong>: 在 TP-LINK 主路由管理页面尝试通过网络唤醒服务唤醒家里的 PC，发现无法唤醒（事后发现之前记录的网卡 MAC 不对）</li><li><strong>2025-07-07 11:30</strong>: 通过米家控制办公桌的智能插座电源重启，尝试唤醒 PC，未成功；打算通过控制机柜的智能插座重启，实现所有服务的重启，但还打算再等等 bots 的 ddns 能否生效</li><li><strong>2025-07-07 11:44</strong>: 等了 2 个多小时了，感觉 bots 服务可能已经不在运行，再等下去也没用了，经过深思熟虑决定重启整个机柜电源</li><li><strong>2025-07-07 11:44</strong>: 通过米家控制智能插座关闭电源，发现状态未更新，再次点击发现操作失败，此时发现智能插座设备已离线，意识到机柜一旦断电，所有米家设备也无法控制了，再也无法打开</li><li><strong>2025-07-07 11:50</strong>: 出发回家，准备手动重启机柜电源</li><li><strong>2025-07-07 12:49</strong>: 到家，手动开启机柜智能插座电源</li><li><strong>2025-07-07 12:50</strong>: 打开 PC，发现 主板 PCI-E 设备唤醒是 Enabled</li><li><strong>2025-07-07 12:51</strong>: 进入 PC 系统，发现网卡的允许设备唤醒也是启用的，但网卡 MAC 地址和之前配置的不一样，原因后面详述</li><li><strong>2025-07-07 12:53</strong>: 通过 PC 内网登录 portainer，发现 bots 容器处于 stopped 状态（Stopped for 3 hours with exit code 127），finished 时间为 09:33:52</li><li><strong>2025-07-07 12:54</strong>: 手动重新启动 bots 容器，正常启动</li><li><strong>2025-07-07 12:55</strong>: bots 服务已正常更新域名解析，手机切换到蜂窝测试，已经可正常访问</li><li><strong>2025-07-07 13:01</strong>: 出门赶回公司</li></ul><h2 id="原因分析">原因分析</h2><ul><li><strong>导火索</strong>：家里异常断电（TP-LINK 和群晖都在机柜里，他俩同时重启，可断定机柜掉电了；光猫在弱电箱里，查看光猫的启动时间，也在同一时间重启过，可判断是全屋断电了）</li><li><strong>直接原因</strong>：自建的 DDNS 服务在光猫重启后公网 IP 发生变化的情况下未更新解析，导致所有服务无法远程访问</li><li><strong>根本原因</strong>：包含了 DDNS 服务的 bots 容器在宿主机重启后未能重启成功，经过分析发现因为 bots 容器启动过程中挂载了群晖中的一个目录，用来更新 clash 的配置文件，但是群晖启动会比 bots 容器所在的宿主机慢，可能导致了启动失败</li><li><strong>处理慢的原因（多种补救措施失效）</strong>：
<ul><li>家里的 PC 未开机，无法通过 ToDesk 远程连接处理（之前几次类似问题都是通过 ToDesk 远程修复）</li><li>家里没人，无法帮忙手动启动 PC</li><li>PC 的远程唤醒功能失效，原因是网卡 MAC 地址记录不正确，这是因为之前记录的是一个虚拟网卡的 MAC，上次去掉了虚拟网卡，直接走的物理网卡，但是忘记记录 MAC 地址</li><li>群晖的 QuickConnect 远程访问服务失效，之前感觉用不到被我手动关闭了</li></ul></li><li><strong>故障升级原因</strong>：由于多个补救方案失效，尝试通过机柜断电重启的方式补救，结果所有设备断电，断绝了任何远程补救的可能</li></ul><h2 id="改进措施">改进措施</h2><ul><li>✅ 购买 UPS，确保机柜设备在短暂断电时能够继续供电，避免意外断电导致的服务中断（07-08 更新: 已购买山特 SANTAK TG-BOX850 UPS）</li><li>✅ 提升 DDNS 服务的核心程度，从 bots 项目中独立出来，减少其他依赖（07-08 更新: 已完成）</li><li>✅ 启用群晖的 QuickConnect 服务， DDNS 失效后可连接到群晖上进行一些处理</li><li>✅ 确保 PC 的网络远程唤醒功能正常，可通过远程连接到 PC 解决问题</li><li>✅ 部署一个 Cloudflare Tunnel 容器，作为 DDNS 失效后的备用方案</li><li>✅ 把机柜的米家插座从米家 APP 首页移除，避免误操作关闭电源，吸取教训，以后不要再给机柜断电了</li></ul><h2 id="经验教训">经验教训</h2><ul><li>之前出现过一次机柜断电后 DDNS 服务不可用导致无法访问的问题，当时通过 ToDesk 远程连接到 PC，然后通过内网重启了 bots 服务解决了问题，但应该更进一步，看看为什么 bots 服务没有自动重启成功，从而可以避免这次的事故</li><li>核心的服务需要保障高可用，例如公网访问这件事，除了自建的 DDNS 之外，还需要通过 QuickConnect、Cloudflare Tunnel 等多种手段保证可用性</li><li>任何情况下都不要尝试给整个机柜断电这种操作，应该优先考虑其他补救措施</li></ul>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[删除群晖 Synology 证书设置中自定义的服务]]></title>
        <id>/posts/2025/delete-service-of-synology</id>
        <link href="https://hadb.me/posts/2025/delete-service-of-synology"/>
        <updated>2025-07-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[今天在给群晖增加一个自带的 DDNS 服务以实现在自建的 DDNS 挂掉的情况下还有备用方案。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2025/20250708.delete-service-of-synology/cover.png" alt="封面" /><p>今天在给群晖增加一个自带的 DDNS 服务以实现在自建的 DDNS 挂掉的情况下还有备用方案。</p><p>在配置证书的时候，发现设置列表中有一个我之前自定义的服务，也不记得是在哪里设置的了，但是找不到地方删除。</p><figure><img src="https://hadb.me/static/posts/2025/20250708.delete-service-of-synology/01.png"></img></figure><p>经过一番探寻，发现在这个文件中：</p><pre><code>/usr/syno/etc/certificate/_archive/INFO
</code></pre><p>可以看到所有的服务列表，找到对应的服务的 JSON 对象，删掉保存即可。</p><figure><img src="https://hadb.me/static/posts/2025/20250708.delete-service-of-synology/02.png"></img></figure>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[优化自托管 Langfuse 的 ClickHouse 资源占用]]></title>
        <id>/posts/2026/reduce-clickhouse-resource-usage-for-self-hosted-langfuse</id>
        <link href="https://hadb.me/posts/2026/reduce-clickhouse-resource-usage-for-self-hosted-langfuse"/>
        <updated>2026-01-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近在研究 Langfuse，用 docker 部署了一套，但是发现空载的情况下 ClickHouse CPU 占用也不低，并且磁盘持续在写入，一不注意，7 天已经写入了 200 多 GB。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2026/20260104.reduce-clickhouse-resource-usage-for-self-hosted-langfuse/cover.png" alt="封面" /><p>最近在研究 Langfuse，用 docker 部署了一套，但是发现空载的情况下 ClickHouse CPU 占用也不低，并且磁盘持续在写入，一不注意，7 天已经写入了 200 多 GB。</p><figure><img src="https://hadb.me/static/posts/2026/20260104.reduce-clickhouse-resource-usage-for-self-hosted-langfuse/01.png"></img></figure><p>通过如下命令发现，大部分磁盘占用都是 <code>trace_log</code> 表造成的：</p><pre><code><span class="line" line="1"><span class="sw1J6">SELECT</span><span class="sw1J6"> table</span><span class="su5hD">, formatReadableSize(</span><span class="sw1J6">size</span><span class="su5hD">) </span><span class="sw1J6">as</span><span class="sw1J6"> size</span><span class="su5hD">, </span><span class="sw1J6">rows</span><span class="sw1J6"> FROM</span><span class="su5hD"> (
</span></span><span class="line" line="2"><span class="sw1J6">    SELECT
</span></span><span class="line" line="3"><span class="sw1J6">        table</span><span class="su5hD">,
</span></span><span class="line" line="4"><span class="sw1J6">        database</span><span class="su5hD">,
</span></span><span class="line" line="5"><span class="sptTA">        sum</span><span class="su5hD">(bytes) </span><span class="sw1J6">AS</span><span class="sw1J6"> size</span><span class="su5hD">,
</span></span><span class="line" line="6"><span class="sptTA">        sum</span><span class="su5hD">(</span><span class="sw1J6">rows</span><span class="su5hD">) </span><span class="sw1J6">AS</span><span class="sw1J6"> rows
</span></span><span class="line" line="7"><span class="sw1J6">    FROM</span><span class="s_hVV"> system</span><span class="su5hD">.</span><span class="s_hVV">parts
</span></span><span class="line" line="8"><span class="sw1J6">    WHERE</span><span class="su5hD"> active
</span></span><span class="line" line="9"><span class="sw1J6">    GROUP BY</span><span class="sw1J6"> table</span><span class="su5hD">, </span><span class="sw1J6">database
</span></span><span class="line" line="10"><span class="sw1J6">    ORDER BY</span><span class="sw1J6"> size</span><span class="sw1J6"> DESC
</span></span><span class="line" line="11"><span class="su5hD">)
</span></span></code></pre><p>于是便联想到之前的一次 ClickHouse 优化经历，决定关闭各种 trace 日志，来减少 ClickHouse 的资源占用。</p><p>网上大部分教程没有提到 <code>background_schedule_pool_log</code>，但我发现它也会不断产生，也可以关闭。</p><pre><code><span class="line" line="1"><span class="sP7_E"><</span><span class="sQzsp">clickhouse</span><span class="sP7_E">>
</span></span><span class="line" line="2"><span class="sP7_E">    <</span><span class="sQzsp">profiles</span><span class="sP7_E">>
</span></span><span class="line" line="3"><span class="sP7_E">        <</span><span class="sQzsp">default</span><span class="sP7_E">>
</span></span><span class="line" line="4"><span class="sP7_E">            <</span><span class="sQzsp">log_queries</span><span class="sP7_E">></span><span class="su5hD">0</span><span class="sP7_E"></</span><span class="sQzsp">log_queries</span><span class="sP7_E">>
</span></span><span class="line" line="5"><span class="sP7_E">            <</span><span class="sQzsp">log_query_threads</span><span class="sP7_E">></span><span class="su5hD">0</span><span class="sP7_E"></</span><span class="sQzsp">log_query_threads</span><span class="sP7_E">>
</span></span><span class="line" line="6"><span class="sP7_E">        </</span><span class="sQzsp">default</span><span class="sP7_E">>
</span></span><span class="line" line="7"><span class="sP7_E">    </</span><span class="sQzsp">profiles</span><span class="sP7_E">>
</span></span><span class="line" line="8"><span class="sP7_E">    <</span><span class="sQzsp">logger</span><span class="sP7_E">>
</span></span><span class="line" line="9"><span class="sP7_E">        <</span><span class="sQzsp">level</span><span class="sP7_E">></span><span class="su5hD">warning</span><span class="sP7_E"></</span><span class="sQzsp">level</span><span class="sP7_E">>
</span></span><span class="line" line="10"><span class="sP7_E">        <</span><span class="sQzsp">console</span><span class="sP7_E">></span><span class="su5hD">true</span><span class="sP7_E"></</span><span class="sQzsp">console</span><span class="sP7_E">>
</span></span><span class="line" line="11"><span class="sP7_E">    </</span><span class="sQzsp">logger</span><span class="sP7_E">>
</span></span><span class="line" line="12"><span class="sP7_E">    <</span><span class="sQzsp">asynchronous_metric_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="13"><span class="sP7_E">    <</span><span class="sQzsp">backup_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="14"><span class="sP7_E">    <</span><span class="sQzsp">error_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="15"><span class="sP7_E">    <</span><span class="sQzsp">metric_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="16"><span class="sP7_E">    <</span><span class="sQzsp">query_thread_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="17"><span class="sP7_E">    <</span><span class="sQzsp">query_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="18"><span class="sP7_E">    <</span><span class="sQzsp">query_views_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="19"><span class="sP7_E">    <</span><span class="sQzsp">part_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="20"><span class="sP7_E">    <</span><span class="sQzsp">session_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="21"><span class="sP7_E">    <</span><span class="sQzsp">text_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="22"><span class="sP7_E">    <</span><span class="sQzsp">trace_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="23"><span class="sP7_E">    <</span><span class="sQzsp">crash_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="24"><span class="sP7_E">    <</span><span class="sQzsp">opentelemetry_span_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="25"><span class="sP7_E">    <</span><span class="sQzsp">zookeeper_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="26"><span class="sP7_E">    <</span><span class="sQzsp">processors_profile_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="27"><span class="sP7_E">    <</span><span class="sQzsp">background_schedule_pool_log</span><span class="s9AJx"> remove</span><span class="sP7_E">=</span><span class="sjJ54">"</span><span class="s_sjI">1</span><span class="sjJ54">"</span><span class="sP7_E"> />
</span></span><span class="line" line="28"><span class="sP7_E"></</span><span class="sQzsp">clickhouse</span><span class="sP7_E">>
</span></span></code></pre><p>将上述配置挂载到 ClickHouse 容器的 <code>/etc/clickhouse-server/config.d/logs.xml</code> 后，重启容器即可。</p><figure><img src="https://hadb.me/static/posts/2026/20260104.reduce-clickhouse-resource-usage-for-self-hosted-langfuse/02.png"></img></figure><p>整个世界清净了。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[2025 年度回顾]]></title>
        <id>/posts/2026/2025-annual-review</id>
        <link href="https://hadb.me/posts/2026/2025-annual-review"/>
        <updated>2026-01-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[这篇年度回顾从 4 号就建了文档了，陆续写了点，拖了一周多才写完。直接进入正题，回顾下我的 2025 年。]]></summary>
        <content type="html"><![CDATA[<img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/cover.jpg" alt="封面" /><p>这篇年度回顾从 4 号就建了文档了，陆续写了点，拖了一周多才写完。直接进入正题，回顾下我的 2025 年。</p><h2 id="新成就入坑-3d-打印机">✅ 新成就：入坑 3D 打印机</h2><p>去年的年度回顾中最后写到，希望每年可以入坑一个新的产品或领域，当时计划的是 3D 打印机，今年做到了。在经过一段时间的调研，以及刚好拓竹发布了 P2S，又无巧不巧的有个同事在公司抽奖中了一台。体验了下之后，我被打印效果惊艳了，毫不犹豫就下单了。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/03.jpg" alt="拓竹 P2S 3D 打印机"></img><figcaption>拓竹 P2S 3D 打印机</figcaption></figure><p>截至目前已经打印了 164 盘，打了各类家用的小工具、小配件以及儿子的玩具等。其中有意思的项目有：QNAS 的 6 盘位 NAS，未来战士太空头盔、料盘盒堆叠收纳、定制的自行车篮、戴森 Airwrap 收纳支架、自弹式 TF 卡收纳盒、电池收纳盒、咖啡杯支架、咖啡机配件盒、咖啡杯沥水盘、铝型材连接辅助治具、剥瓜子神器、以及内嵌了磁吸和 IC 卡的手机壳等。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/01.jpg" alt="一些打印的项目"></img><figcaption>一些打印的项目</figcaption></figure><p>不得不提的是拓竹的 MakerWorld 确实是非常棒的 3D 打印社区，我愿称之为 3D 打印界的 GitHub。在调研 3D 打印机的过程中，为了防止买回来积灰，我已提前调研了 3D 建模软件，最终选择了 Shapr3D，并且和儿子一起学习使用它来设计简单的模型。3D 打印机到手之后，我已经用 Shapr3D 设计了非常多的自定义模型，并且从中获得了非常多的创造的乐趣。</p><p>这里再提一下我为什么会选 Shapr3D。主要是他的界面设计比较现代化，并且支持跨平台同步，可以在 iPad/Mac/Windows 上使用，并且同步自己设计的模型。这一点非常适合我这种会在多个设备上使用的情况。当然 Shapr3D 价格特别贵，我是在淘宝买的教育版。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/02.png" alt="Shapr3D 中自己建模的一些模型"></img><figcaption>Shapr3D 中自己建模的一些模型</figcaption></figure><p>购买拓竹的 3D 打印机是今年给我带来幸福感最强的一笔消费，非常值得，所以放在第一位写。我给身边很多朋友都推荐了 3D 打印机，确实三四千块钱的东西可以给你带来特别多的 DIY 的乐趣，手工爱好者的福音。</p><h2 id="新成就第一次自主设计制作铝型材支架">✅ 新成就：第一次自主设计制作铝型材支架</h2><p>今年另一件新成就就是解锁了铝型材的设计和制作。由于 3D 打印机放在客厅确实有点占地方，放在阳台的洗衣机旁刚好可以利用那部分空间。但是缺少一个合适的支架。网上成品支架的大小很难严丝合缝完美嵌入，想要定制的话成本也很高，并且稳定性也不一定好。于是在研究了铝型材的的制作之后，自己用 MayCAD 设计，从嘉立创自助下单，回来自己组装。最终完美嵌入了阳台旁的空间，并且稳定性特别好。整个过程获得的成就感也是特别高。</p><p>这里要安利下嘉立创的铝型材，支持自定义加工，各种孔位、切割都可以自主设置，特别方便，并且价格也很透明。很适合 DIY 爱好者。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/04.jpg" alt="铝型材支架"></img><figcaption>铝型材支架</figcaption></figure><h2 id="新成就第一次看大马戏">✅ 新成就：第一次看大马戏</h2><p>今年 4 月，跟老婆儿子一起去湖州龙之梦玩，第一次看了“大马戏”，杂技表演非常震撼，尤其是高空表演，非常精彩。现场的沉浸感也特别强，一次不错的亲子活动体验。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/05.jpg" alt="大马戏表演"></img><figcaption>大马戏表演</figcaption></figure><h2 id="今年的理财成果">💰 今年的理财成果</h2><p>2025 年度，我的基金账户年收益率是 11.23%，尽管没有跑赢沪深 300，但因为我一直是有一半左右稳健资产，所以整体收益率还是挺满意的。</p><figure><img src="https://hadb.me/static/posts/2026/20260109.2025-annual-review/06.png" alt="理财收益率"></img><figcaption>理财收益率</figcaption></figure><p>今年的投资分布，一半的稳健基金（主要是易方达增强回报债券），一半风险类基金，如下：</p><ul><li>013308 恒生科技ETF</li><li>005669 前海开源公共事业股票</li><li>020670 上证科创板芯片</li><li>012733 人工智能ETF</li><li>000307 黄金ETF</li></ul><p>这些分布也是有我的思考，恒生科技是传统互联网，前海开源公共事业是一些电力、能源的基建行业，芯片、人工智能是 AI 行业，黄金是避险资产。其中人工智能 ETF 年底已清仓，因为部分持仓公司和芯片 ETF 有重叠，并且目前人工智能 ETF 的持仓公司中，并不是行业内的头部。行业头部要么没上市，要么不在 A 股上市。</p><h2 id="咖啡-vlog从入门到放弃">❌ 咖啡 Vlog：从入门到放弃</h2><p>年初的时候，计划开始拍摄制作咖啡的 Vlog，买了挂脖神器，一共坚持了 30 天，发布了 30 条视频，但是剪辑过程还是太费时间，并且最终播放效果平平，一篇几百的播放量，确实有点打击积极性。并且制作咖啡的 Vlog，内容太过单一，每天基本都是同样的内容，缺乏新鲜感。最终我还是放弃了这个计划。但是还是坚持每天做咖啡，没有了记录和分享的压力之后，反而更加能够享受做咖啡和喝咖啡的过程了。</p><h2 id="外包维护最后两个传统的-web-项目也不维护了">❌ 外包维护：最后两个传统的 Web 项目也不维护了</h2><p>先发表一句感慨，我的 PC 互联网的时代结束了。</p><p>这个感慨来自于，我之前仍在维护中的最后两个传统的 Web 类项目，今年也都不续约了。</p><p>最近在清理这两个旧项目的服务器、存储 Bucket、CDN 配置等。其中有个项目是我刚毕业接的项目，至今已维护超过了 10 年，甚至技术栈还是 ASP.NET MVC + SQLServer。为此我甚至还一直保留着一台 Windows 的服务器。</p><p>今年终于可以彻底消除这些旧时代的印记了，对我来说确实是一件很值得感慨的事。</p><h2 id="关于-ai-的思考">🤔 关于 AI 的思考</h2><p>今年是 AI 高速发展的一年，见证了 AI 发展的很多事。年初还在因为 DeepSeek 官网太慢用阿里云百炼 + Open WebUI 打造专属的 DeepSeek-R1，到后面 Agent 模式的颠覆，Vibe Coding 已经完全改变了我写代码的方式。</p><p>当 AI 可以在几分钟内就能完成你几个小时甚至几天才能完成的工作甚至还做得更好时，你会发现，你之前的努力变得那么不值一提。甚至你多年来积累的经验、能力，在以前会被认为是技术护城河的东西，也被爆破，只剩一地废墟。这种信念的崩塌，是我今年思想上比较困惑和不知所措的地方。</p><p>幸运的是，我能在工作之余找到时间去放空下来思考一些事。有机会少做事甚至不做事。</p><p>我有时在想，在 AI 时代，我应该做什么。如果一个初学者就可以借助 AI 来完成困难的任务，那还需要我做什么呢？或者说，我还有必要做那些事情吗？再换一个角度思考，我们现在做的事，一年后还有意义吗？如果没意义，还需要去做吗？</p><h2 id="关于要不要做的思考">🤔 关于要不要做的思考</h2><p>人性总是会为了自己不想做的事情找借口，也会为了自己想做的事情粉饰。少说多做，当你不得不做一件事的时候，就不要以你想不想做作为出发点，而是应该基于当前的现状、条件、资源思考怎么把手头的事做好。</p><p>当然，如果有权限决定做不做的话，也需要不断基于当前的形势、条件不断判断是否应该继续做下去，及时止损、及时调整。</p><p>人的思维、判断应该是要不断基于现状更新而变化的。我们处在一个技术更迭日新月异的时代，很有可能今天你做的事，明天就被颠覆了。</p><p>这里，我想分享下我们公司的技术老板今年 OKR 中的几个大 O：</p><ul><li>做相信的事</li><li>做确定的事</li><li>做困难的事</li><li>做更少的事</li></ul><p>我觉得总结得非常到位，值得学习。</p><h2 id="关于组织管理的思考">🤔 关于组织管理的思考</h2><p>今年在公司也经历了一些组织调整，一个公司达到一定的规模，都会或多或少遇到一些大公司病。顶层的想法和决策，一层层执行下来，很难达到预期，往往会打折扣，甚至会变样。即便是老板亲自挂帅要完成的事，在一层层执行下来，也会遇到各种各样的问题。老板会觉得下面人在糊弄他，下面人会觉得老板难伺候，工作的重点会变成面向老板工作。大公司会有很多利益团体，每个利益团体会有各自的考量。团队之间的协作也会变得很复杂，效率也会变得很低。</p><p>很欣慰看到公司做了一些变化和调整，尝试通过更小的团队来闭环完成一些事，来提升效率和执行力。未必能成功，但至少值得尝试。团队小了，决策链路是短了，但对于决策者判断力的要求就变高了。</p><p>最近在朋友圈看到一个有意思的观点：组织内部，判断力是可以被制度、团队和时代红利托住的，这也就是为何很多人能做个很出色的高管但创业开公司就不行。即便是一个草包的管理者，如果能够不断听取团队的意见，不断去修正自己天马行空不着边际的想法和判断，在组织的制度和团队努力的托举下，也是可以做出正确的事情。</p><h2 id="关于世界局势的思考">🤔 关于世界局势的思考</h2><p>随着年龄渐长，我现在也越来越会关心世界局势。也会喜欢基于了解到的信息去分析和判断。在这个 AI 突飞猛进的时代，世界局势也在发生着翻天覆地的变化。前几次的工业革命，像蒸汽时代、电气时代，都是伴随着重大的世界格局变化的。而当下绝对是百年未有之大变局。</p><p>我国一直以来一个悬而未决的问题，就是台湾问题了。越来越多的迹象表明，这个问题解决的时间点越来越接近了。美国近期动作频频，已经给中国打了很多样，给了中国很多抄作业的案例。</p><p>我一直在想，如果台海战争发生了，对经济的影响会是什么样的。我现在的资产都在股市和基金里，还是需要早做准备，提前做一些配置调整。</p><h2 id="关于成就感">🤔 关于成就感</h2><p>以前我一直以为我喜欢写代码，是因为写代码的时候能够不断获得反馈获得成就感。但我现在发现，成就感并不是来自于写代码这件事本身。写代码这件事底层其实是创造，通过一堆代码创造出一些功能，而创造带来了成就感。但 AI Coding 的崛起，让这种功能的创造变得非常廉价，从而让自己在写代码这件事上获得的成就感的阈值变高了，你得通过完成一些更复杂的功能和需求，才能获得同样的成就感。</p><p>于是今年，我尝试通过更多别的方式进行创造，并且从中获得了蛮多的成就感。例如入坑了 3D 打印，制作 6 盘位 NAS，制作铝型材支架，把废旧 iPad 改造成屏幕，把旧的 MacBook Pro 改造成“无头骑士”等等。这些创造的过程，让我获得了相当多的成就感。而这些，是目前 AI 无法替我完成的事。</p><p>未来，当 AI/机器人能够完成越来越多的创造性工作时，留给人类能够做的事就越来越少了。当 AI/机器人能够为你更快更好地完成一些事情时，你还需要做它干啥呢？就好比洗衣机、洗碗机、扫地机器人、自动驾驶这些技术的出现，大大减少了人类在洗衣、洗碗、扫地、开车这些事上需要付出的时间和精力。</p><p>好的方面是，我们可以把自己的时间和精力放在一些更有意义的事情上，更多地去享受生活；坏的方面是，我们可能会变得越来越无所事事，无事可做，甚至被淘汰。越来越多的人沉迷在短视频、短剧的奶头乐中，就是一个很好的例子。</p><p>我们需要不断为自己找点有意义有趣的事情做，为自己创造一点价值来获得成就感。我希望每年回顾这一年的时候，能够回忆起自己做过的一些有意义的事情。积累多了，在年老的时候去回顾一生的时候，能够自豪地说，我这一生是有意义的，我这一生是活得有价值的，我这辈子没白活。</p><p>我们需要不断认清自己，并非每个人都能做出非凡的成就，我们作为时代的螺丝钉，做好自己的事情，无愧于自己就可以了。</p><h2 id="最后">📝 最后</h2><p>最后，我给自己 2026 年的目标就是：不设目标。先停下来观察和思考一下，这个时代在怎么变化，看看能不能找到一个方向，为自己职业生涯的下一个阶段做一些规划。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 VSCode 开发 Kotlin]]></title>
        <id>/posts/2026/use-vscode-for-kotlin</id>
        <link href="https://hadb.me/posts/2026/use-vscode-for-kotlin"/>
        <updated>2026-01-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[之前有些后端项目用的 Kotlin + Spring Boot，IDE 用的 IDEA 开发的。但是 IDEA 中的 GitHub Copilot 插件实在太弱智，打算换成 VSCode 来开发 Kotlin。]]></summary>
        <content type="html"><![CDATA[<p>之前有些后端项目用的 Kotlin + Spring Boot，IDE 用的 IDEA 开发的。但是 IDEA 中的 GitHub Copilot 插件实在太弱智，打算换成 VSCode 来开发 Kotlin。</p><p>网上几乎没有教程（可能确实没人这么干），踩了不少坑，试了很多插件，花了快一天时间才终于搞好。</p><p>首先，需要安装 VSCode 的 <a href="https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack" rel="nofollow">Java Extension Pack</a> 插件包，这个插件包包含了 Java 开发所需的各种插件。</p><p>然后，还需要安装 <a href="https://github.com/Kotlin/kotlin-lsp/releases" rel="nofollow">kotlin-lsp</a> 插件，这个是 Kotlin 官方的插件，提供了 Kotlin 的语法高亮、格式化、跳转等功能。需要注意的是，这个插件尚未发布到 VSCode 市场，需要手动下载并安装。另外，该插件当前只支持 Gradle 项目，不支持 Maven 项目。如果是 Maven 项目，得转换成 Gradle 项目才能使用该插件。</p><p>VSCode 配置：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">[kotlin]</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> {
</span></span><span class="line" line="3"><span class="s39Yj">    "</span><span class="sZMiF">editor.defaultFormatter</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">JetBrains.kotlin</span><span class="sjJ54">"
</span></span><span class="line" line="4"><span class="sP7_E">  }
</span></span><span class="line" line="5"><span class="sP7_E">}
</span></span></code></pre><p>F5 调试用的 <code>launch.json</code> 配置：</p><pre><code><span class="line" line="1"><span class="sP7_E">{
</span></span><span class="line" line="2"><span class="s39Yj">  "</span><span class="sseR_">version</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">0.2.0</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="3"><span class="s39Yj">  "</span><span class="sseR_">configurations</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [
</span></span><span class="line" line="4"><span class="sP7_E">    {
</span></span><span class="line" line="5"><span class="s39Yj">      "</span><span class="sZMiF">type</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">java</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="6"><span class="s39Yj">      "</span><span class="sZMiF">name</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">SpringBoot</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="7"><span class="s39Yj">      "</span><span class="sZMiF">classPaths</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sP7_E"> [</span><span class="sjJ54">"</span><span class="s_sjI">$Auto</span><span class="sjJ54">"</span><span class="sP7_E">,</span><span class="sjJ54"> "</span><span class="s_sjI">${workspaceFolder}/build/libs/*</span><span class="sjJ54">"</span><span class="sP7_E">],
</span></span><span class="line" line="8"><span class="s39Yj">      "</span><span class="sZMiF">request</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">launch</span><span class="sjJ54">"</span><span class="sP7_E">,
</span></span><span class="line" line="9"><span class="s39Yj">      "</span><span class="sZMiF">mainClass</span><span class="s39Yj">"</span><span class="sP7_E">:</span><span class="sjJ54"> "</span><span class="s_sjI">net.yuanfen.op.xams.ApplicationKt</span><span class="sjJ54">"
</span></span><span class="line" line="10"><span class="sP7_E">    }
</span></span><span class="line" line="11"><span class="sP7_E">  ]
</span></span><span class="line" line="12"><span class="sP7_E">}
</span></span></code></pre><p>另外，<code>kotlin-lsp</code> 插件暂不支持 <code>Gradle Kotlin DSL</code> （<a href="https://github.com/Kotlin/kotlin-lsp/issues/55" rel="nofollow">Issue#55</a>）。</p><p>尝试了 <code>ktfmt</code>、<code>ktfmt-gradle</code>、<code>Spotless Gradle</code> 等插件，最终都因为各种原因放弃了。</p><p>另外还遇到一个 <code>kotlin-lsp</code> 的奇怪 bug，就是插件一直报错和各种警告：</p><pre><code><span class="line" line="1"><span class="s_sjI">Error</span><span class="su5hD"> while resolving </span><span class="s39Yj">org.jetbrains.kotlin.fir.declarations.impl.FirValueParameterImpl</span><span class="su5hD"> from SEALED_CLASS_INHERITORS to ANNOTATION_ARGUMENTS current declaration phase SEALED_CLASS_INHERITORS origin: Source session: class </span><span class="s39Yj">org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSourcesSession</span><span class="su5hD"> module data: class </span><span class="s39Yj">org.jetbrains.kotlin.analysis.low.level.api.fir.projectStructure.LLFirModuleData</span><span class="su5hD"> KaModule: class </span><span class="s39Yj">org.jetbrains.kotlin.idea.base.fir.projectStructure.modules.source.KaSourceModuleImpl</span><span class="su5hD"> platform: JVM (</span><span class="s39Yj">1</span><span class="su5hD">.</span><span class="s39Yj">8</span><span class="su5hD">)
</span></span><span class="line" line="2"><span emptyLinePlaceholder>
</span></span><span class="line" line="3"><span emptyLinePlaceholder>
</span></span><span class="line" line="4"><span class="s2YIT">WARNING</span><span class="su5hD">: package </span><span class="s39Yj">sun.awt.windows</span><span class="su5hD"> not in </span><span class="s39Yj">java.desktop
</span></span><span class="line" line="5"><span class="s2YIT">WARNING</span><span class="su5hD">: package </span><span class="s39Yj">sun.awt.X11</span><span class="su5hD"> not in </span><span class="s39Yj">java.desktop
</span></span><span class="line" line="6"><span class="s2YIT">WARNING</span><span class="su5hD">: package </span><span class="s39Yj">com.sun.java.swing.plaf.gtk</span><span class="su5hD"> not in </span><span class="s39Yj">java.desktop
</span></span><span class="line" line="7"><span class="s2YIT">WARNING</span><span class="su5hD"> - #</span><span class="s39Yj">c.i.i.p.PluginManager</span><span class="su5hD"> - Plugin descriptor for plugin </span><span class="s_sjI">'intellij.kotlin.searching.xml'</span><span class="su5hD"> has declared element </span><span class="s_sjI">'visibility'</span><span class="su5hD"> which has no effect there
</span></span><span class="line" line="8"><span class="s2YIT">WARNING</span><span class="su5hD"> - #</span><span class="s39Yj">c.j.l.a.f.i.c.d.i.LSInspectionDiagnosticProviderImpl</span><span class="su5hD"> - </span><span class="s39Yj">org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments</span><span class="su5hD">: Unable to get element context
</span></span></code></pre><p>比较了两个文件夹，同样的代码，一个报错，一个不报错，报错的那个文件夹换个名字就好了。怀疑是缓存的问题，困扰了很久，最终找到了缓存位置：</p><pre><code>/Users/bean/Library/Application Support/JetBrains/analyzer/workspaces/
</code></pre><p>删除该目录下的缓存文件夹，重启 VSCode 后问题解决。</p>]]></content>
        <author>
            <name>Bean</name>
        </author>
    </entry>
</feed>