普通视图

发现新文章,点击刷新页面。
昨天以前游钓四方的博客

晨跑,破六

2026年1月7日 20:12

熟悉我的朋友都知道,我是一个懒人。懒得说话,懒得吃饭

所以像我这样的人,按理说对运动是没什么兴趣的

但耐不住压力大,我急需一种方式来发泄情绪

毕竟,人是可以活活憋死的

杭州城内,我最爱的一条路:南山路

我之前的发泄方式主要是骑车,但现在碳轮坏了,暂时没钱换,于是便转向了登山徒步

不可否认,我非常喜欢这种方式,但徒步太耗时间了,我不可能每天都拨出大半天做这件事

至于跑步,我没想过。仅有的尝试也纯粹是出于好奇。可以说,这辈子我跑过的次数,一个巴掌就能数得过来

2025年12月31日,早晨七点。我实在憋不住了,穿上体能服直接冲了出去

跑出一公里后,我有些后悔没戴帽子和手套。苏堤两岸吹来的冷风冻得我头皮发麻

跑到岳湖附近时,想吐。我手撑着树干呕了几分钟,深感不适,当场掉头慢跑回家了,全程九公里

纯菜逼,回到家后,连饭都吃不下

第二天,也就是 2026 年的第一天。杭州下了一场暴雨,我便没出门

跑步的第二天,差点破六

如果不算年前的那次,从 1 月 2 号到今天,是我连续晨跑的第六天

以前我习惯通宵写代码,而现在为了凌晨能跑起来,作息也调整了

不管手头有什么几把事,统统往后排,每天 12 点前必须睡觉

 咖啡 + 牛奶 + 燕麦

由于晚饭吃得少(或者干脆不吃),凌晨醒来时明显的饿

所以出发前,我都会冲点麦片。这东西挺容易消耗,有饱腹感,即便吃半碗也不影响马上跑步

凌晨 6 点 13 分的湖滨路

插播一个有意思的小插曲:跑步的第二天,经过湖滨银泰时,我貌似和马云擦肩而过

当时我俩距离不过半米,几秒钟后我才反应过来,那个人很可能是他

毕竟马云的相貌还是很出众的,有一定辨识度,可惜当时光线差,他还戴着帽子

回家后我特意查了他近期的行程,啥也没搜到

但如果他当时在杭州,我敢说大概率是他

因为马云一直有户外运动的爱好,之前在杭州爬山、骑车也常被网友偶遇

雷峰夕照

闻子状雷峰,老僧挂偏裻。日日看西湖,一生看不足
—— 张岱《雷峰塔》

今天的天气极好,跑过苏堤时,恰好瞥见了西湖十景之七的“雷峰夕照”

可惜当时站位欠佳,视线被桥两侧的树木挡了大半

不过我也没打算专门跑到桥下去取景,毕竟我是出来跑步的,不是来搞摄影的

日志里的这些照片,每张的拍摄停留时间都不会超过 5 秒

因为我我习惯在跑步过程中提前打开相机,停下的瞬间,让远景对准参考线便按下快门,不过一瞬间

其中不乏有运动过程中拍下的照片,所以有些画面难免会有重影或模糊

实际上我这几天的跑步,都没怎么在中途停下过,也就是所谓的不间断

骑行更是如此,即便是两百公里,我也宁愿在车上慢踩,而不会选择停下

当然,凡事皆有意外

昨天跑得确实有点过火,到 11 公里时,左腿膝盖突然刺痛难忍

眼看只剩最后两公里,我实在不想放弃,便拖着左腿蹦着跑,那种刺痛感真是钻心刺骨,疼得我直咬牙

最后还是认怂了,不过我发现只要不刻意发力,刺痛感就不会有,于是快走了两公里。回到家一看配速:6:13,绝了

凌晨雨跑,环西湖

5 号那天是我人生第一次“雨跑”

刚出门,万松岭的路面就已经全湿了,手掌伸出去能感觉到细密的水滴

就这点降水量想挡住我跑步?门都没有

毕竟我衣服都穿好了,脱是不可能脱的

白堤

雨跑给我的真实感受其实就两个字:湿、重

汗水夹杂着雨水,上半身很快就湿透了,体感并不算舒服

我想,产生这种不适感,也许是因为跑步时注意力还不够集中

不像骑行,如果你行驶中分神,就有可能出事故

所以骑行时,人的感官会高度聚焦,从而忽略身体的琐碎不适

显然,我跑步还没达到那种境界

开水煮西蓝花

除了跑步,最近我还尝试着自己动手做饭

凌晨 5-6 点起床,跑完步刚好是早餐时间

到家后的第一件事通常是点根烟暖暖身,如果出汗多就洗个澡,然后从冰箱拿点能吃的搞一搞

我的早餐口味偏清淡。比如这盘西兰花,什么调料都不放,开水烫一下就可以出锅

鸡蛋 + 火腿 + 韭菜 + 上海青

南德调味料,河南的老乡应该都很熟悉,家乡话叫“南呆”

这大概是我早餐里口味最重的一道菜了,再油腻的我也吃不下

炒鸡蛋出锅时,高压锅里的米粥也熬好了。趁热再去微波炉里热个馒头,一顿饭就齐活了

大概就是这样

破六

尼康 z30 开箱小记

2025年12月2日 01:40

有些年头没摸相机了。上一部相机还是 2019 年买的“佳能 EOS 200D”,那时 Fooleap 还没有退网,我还在做程序员 《我的第一部单反相机》

一晃眼,六年过去了。一切都变了,我刚入圈时,Fooleap 还在为哄女朋友而苦恼,现在孩子都会打酱油了。当初认识的博友大多已停更,中间我也曾断过更。不过后来因为喜 欢上户外活动,又把博客捡了起来。现在的博客大部分内容被户外生活占据,也是我持续更新的动力

前天,我把 EXIF 样式重写了,但图中的照片画质实在太烂,这对于一个完美主义者来说是无法接受的。一时间想到了相机,早年那台佳能 200D 因为闲置太久早已出掉, 看了些博主评测,最终圈定了两款:“佳能 R50”和“尼康 Z30”。因为 R50 阉割了热靴接口,我选择了尼康 Z30

京东尼康旗舰店下的单,16-50mm 套机,价格是 4869 元。顺丰走了两天半,属实有点慢。

下单送了四样东西:

  • 64G 内存卡
  • 屏幕钢化膜
  • 座充
  • 相机包

如果不要这些赠品可以少 100 元,但想了想没必要,毕竟存储卡总是要买的,差价不大。等以后手头宽裕了再换更好的

上手的第一感觉:这相机真的很小!还没有三星手机大,感觉完全可以揣进兜里,便携性满分

实际试拍了不少照片,全程 M 档,不是过曝就是欠曝。晚上去西湖大道的桥上拍车流,又没控制好,画面还是过曝了

先这样吧,接着回 B 站看摄影教程去了

读苏轼有感(1)

2025年11月20日 00:29

今天看了一本关于苏轼的传记,其中有首诗词让我感到震惊的同时又有一种无力感

人生到处知何似?应似飞鸿踏雪泥

回想我这活了二十多年,不过在欲望的追求与失落之间摇摆,实在没有意思

难道说人,这种欲望下的产物,只能困于这悲苦之中不得解脱吗?

没有欲望的人还有人性吗?还是人吗?

大悲寺的和尚不食人间烟火,却求佛问道,这难道不是一种欲望吗?

西湖阅读初体验

2025年10月25日 02:30

昨天跑步回来后,大腿肌肉疼痛难忍,上下台阶非常吃力

毕竟很久没跑了,突然跑了十几公里,身体难免吃不消

今天早上照旧六点起床,一路上尽量不做停留,所以没有拍照

跑步路线和昨天不太一样

从断桥下来,沿北山街直行,右转进入环城西路,结果居然跑错了,跑到了延安路

坚持跑完全程回到家后,发现跑得有点超纲,距离比昨天还要长:13.73 km、1h 55m

不过以后不能这样了,我在想如何调整晨跑的时间

以我目前的能力,绕西湖一圈接近两个小时,实在太奢侈,毕竟上午还有其他事情要做

今天的早餐吃得比较晚,九点半才吃

眼看中午快到了,11:50了,我还是不饿,想着出去走走,但大腿是不允许的

索性拿了两本书去西湖坐坐:一本是尚未看完的“C3环球游记”,一本是未曾读过的“阅读的方法”

因为住在西湖边,这几天走了很多次。即便是中午,西湖边依旧非常冷,我特地穿了红色高领毛衣和一件轻薄款的带帽羽绒服

出门时,大街上不少异样眼光看我这入冬装扮,我也不当回事。我看到的路人大多穿的是短袖和轻薄外套

不知道小学生们在做什么户外活动,老师带队,人数大约有500人

在西湖水边找到空位的椅子不容易,唯一空着的椅子,还不能坐

终于抢到一个位置,坐下五分钟后,我旁边的那人走了,我独享了

低头看书,抬头便是西湖美景

自进了柳浪闻莺后,我便把羽绒服脱掉了,确实热

坐下十分钟后,北风打脸一般呼呼的吹,冻得我发抖。遮阳帽和羽绒服帽子都戴上了,还是不行

我只好侧身坐着看书,就这个姿势,坚持了两个小时

可乐喝得有些多了,到了下午一点,我想去小便,但卫生间距离有点远

如果我一走,这个位置肯定会被占,就这样憋到两点

是的,憋不住了,从卫生间回来后,没有再去西湖边,而是在柳浪闻莺公园坐着

这个地方离西湖大约两百米,四周树木茂密,风吹不到我这

虽然这里不冷,但小蚊虫实在太多,到了三点左右,我便回家了

我住在山脚下,地方比较潮湿,小蚊子很多,这两天夜里都是蒙着头睡的

 另外,还买了一个护腰椅,BKT 加大版,以后坐在床上玩电脑会舒服一些了

二零二五 - 六七月读书随笔

2025年6月7日 16:16

书犹药也,善读之可以医愚

C3环球游记Ⅲ:加勒比没有那么远 | 徒步进藏 | 不去会死

二手海淘

这三本书买的有段时间了,在 端午骑行:倍鱼线 里我随口提过一嘴,算起来,另外两本书吃灰近四周了。最近没心情敲代码,索性把书捡了起来。

第一本看完的是《不去会死》,作者是小日子过得还不错的“石田裕辅”。

这本书还是从 Pluskid 博主 2022 书单里看到的,我一眼瞄到封面是自行车,当时就放下鼠标,开始在在各个平台寻找这本实体书。由于发行较早,市面上大都是二手货,而且是非彩印版本。唯一的全新版本是台版的,八月开售(繁体)。

我在手机上扒拉了很久,终于在某宝一个“倒江湖”的小商贩那里,以27元下单了(定价38)。我也没怀着捡漏的心态买这玩意,只希望它不缺页就行了。

端午傍晚回家,看到快递,拆包后看到实物,如获至宝啊!上海译文出版,上海书刊印刷!而且是全彩印刷版本,还是2012年5月第一版的第一批印刷!看这纸张和文字印刷质量,正版无疑,且无一缺页。

不去会死,在版编目(CIP)数据

不去会死

端午过后的第一天,六月一号下午,我背着单肩挎包,带着书,骑着我的公路车,溜达到义乌植物园,找了一个石凳子,一坐就是一下午,把这本书给炫完了。怎么说呢?这本书实在太他妈的精彩了!

想想看,打工三年攒了5400美金出发,期间还被土匪抢劫3000美金,就这样骑行环游世界七年半、横跨五大洲、八十七个国家!

p.s.不要问没钱怎么活,这大哥擅长钓鱼、自制鱼干

阿根廷,巴塔哥尼亚,1997年2月

遭遇土匪顶枪抢劫

在秘鲁·纳斯卡的沙漠公路骑行时,石田君惨遭绑架,连人带车被三个持枪土匪拖进沙漠深处。

当时土匪拿枪顶着他,把他的双手反绑在背后,脚也被绑了。土匪为了敛财,裤头都给他扒光了。他还以为土匪要强奸他,哈哈哈。就这样,这大哥还在讲条件,恳求留下自行车。

结果土匪把护照和全部财产的暗袋都抢走了,还有露营用品、药品和工具,所有装备洗劫一空。好在土匪太慌了,地上还散落一些杂物和掉落的二百美金。最重要的自行车,由于土匪来不及抢走也留下了。

事后他联系保险公司索赔,但是片警连抢劫证明都不给开,开口嘴就是“Money”。

后来他回忆说:“当时一点都不紧张。相反,土匪紧张得像热锅上的蚂蚁。”三个土匪还在他面前内讧,大吵一架,土匪跑之前还主动帮他穿好裤子。

秘鲁,在纳斯卡的沙漠遇到土匪后,1996年

清田君

除了主人公石田裕辅,让我印象深刻的还有"清田君",很有趣的一个人,特别是他那句:

啊啊,真好吃,真好吃啊!

起初他们在"乔治王子城"相识,石田裕辅这样描述他的:

  • 头发朝四面八方乱长
  • 头大得出奇,整个巨大的蘑菇头
  • 脸黑的像烤焦的面包
  • 眼睛细长锐利
  • 体格矮小粗壮
  • 罗圈腿

中途他们因路线分开过几次,幸运的是,每次都能在异国他乡相遇!

以至于后来清田君也遭遇土匪抢劫,还被枪打伤,一度悲惨,一路要饭.....

大树下看书,好不快活

卖香菇的老伯

骑行到波兰首都华沙时,石田君看中了当地特产蘑菇,纯吃货,天天煲汤

在骑行穿越森林小路时,他看到一个残障老伯在路边卖香菇,出于对老伯自力更生的感动,他拿出了一个兹罗提(波兰货币)铜板(相当于40日元),满脸笑容的指着蘑菇说:"给我一个兹罗提的份。"

老伯看他拿一个铜板,马上就变脸了,嘴里一直喊着:“Nie Nie!(不!)” 然后不停地说着什么。

由于波兰语他压根不懂,他以为老伯嫌他那点钱少。又掏出一张五兹罗提钞票,老伯当场就爆发怒吼:“Nie Nie!” 然后从衣袋里拿出自己的钱包,刻意在石田君面前取出几张钞票。

石田君看到老伯从钱包里取钞票,他心都凉了,以为老伯想要更多。就在这时,老伯不断的把香菇装进袋子里,嘴里还是不停用波兰语快速说着什么,石田君连忙阻止,但是老伯一点都没有停下来的动作。

这时候石田君脑海里冒出一个词:“Present?” 他带着疑问随口一说,老伯用力点头回应。

这时候石田君才明白,老伯不停的用波兰语表达的意思可能就是:

我怎么能从你这个贫困的旅客身上拿钱呢?

波兰,卖香菇的老伯,1997年10月

最近,我在网上查石田君的个人信息。它还有个人博客,2003年就开始运作了,日更级别!

石田ゆうすけのエッセイ蔵:https://yusukeishida.jugem.jp

五月初,他在日本·静冈骑行撞山,摔断八根肋骨和锁骨。这是康复之旅的第一张照片

徒步进藏

这是一个京城富姐(凡凡)从成都出发徒步拉萨的故事。纯徒步,不搭车!

大路书:

成都 - 拉萨,2160公里,3304800步(迪卡侬计步器统计),90天

90天:2014年5月2日 — 7月30日(休整15天)

2160公里:

成都大件路 → 雅安 → 泸定 → 康定 → 新都桥 → 雅江 → 理塘 → 巴塘 → 芒康 → 左贡 → 邦达 → 八宿 → 然乌 → 波密 → 通麦 → 鲁朗 → 八一 → 工布江达 → 墨竹工卡 → 达孜 → 拉萨布达拉宫

凡凡的背包有些重了,出发时的重量在40斤,属于重装范畴。我这小体格是接受不了,毕竟这是肩包,不是驼包。

书店不让带书

说到这我要吐槽一下,进书店不让带书?

我就问你,你新华书店作为国企,内地从事图书行业的老大哥,你这样做真的好吗?

是,不让带包,降低被盗风险。但就论书这东西,不看书的不会偷,看书的更不会偷。

你们这一刀切的政策是和谁学的?开在商场的西西弗人流量不比你大?人家敢说带包不让进吗?今天不让进,明天他就倒闭。

就你这还文化行业呢,呸,恶心!中国的国企都是他妈的社会寄生虫,从此以后不在新华书店买一本书。

义乌,新华书店(图书大厦店)

转战西西弗

自从离开上海,我所在的十八线小城市再也见不到西西弗了。算下来,我已经有一年半没有去过了。在上海那段时间,我几乎每天上午都会去西西弗,买一杯最便宜的咖啡,提着书包去占座。因为西西弗离我们公司非常近,公司在万象城五楼,而西西弗在三楼,所以我站在五楼的视角俯瞰,完全可以看着他们开门营业,每天必抢到黄金位置。

下午两点下班,我没有回家睡觉的欲望。我要么出去骑车去外滩,要么去西西弗看书,没有第三个选择。所以,坐电梯下3楼,一头扎进L333A-A号,直到听见 Going home。

其实,待在西西弗,我的目的不止是看书。在下班后,店里几乎每天都会给我带来福报,这个帝王蟹客人晚宴要用,那个东星斑规格不行。接到骚扰电话后,我就得先打电话联系档口,然后起身开车去江阳市场,所以西西弗是一个很好的选择。

义乌,西西弗,印悦城店

实话说,以我目前的经济,来西西弗有些奢侈了。一杯咖啡四十块,还非常难喝。

但是我在义乌找不到晚上六点后可以去看书的地方了,求推荐。

几年前在杭州林隐寺买的包,很适合装书

正在读 C3环球游记

最爱的姿势:躺着,用抱枕托着书

十月份看完的第一本书

2024年10月3日 20:31

为这本书,我近两天几乎废寝忘食,心中感慨颇多,最深刻的收获便是对人性与形式的深刻理解。许多事情在当时看来似乎是理所当然,但如今回首,才发现那时的自己是多么稚嫩,甚至有些可笑

微信读书 · 沧浪之水

这是我第一次尝试电子书,有声加文字的双重体验感觉不错,美中不足的是,机械式的发音让情感显得苍白,在这个时代,做个拟人化的语音合成也不难啊,腾讯读书这块业务还是小众,资源太少了,很多我想看的书都找不到,涉及敏感话题的搜都搜不到,哎

我的第一部单反相机

2019年4月20日 11:26

咱老百姓,今儿真啊么真高兴!嘿嘿,我的第一部单反相机"佳能EOS 200D 银色单反套机",今早9:18快递到了!纸箱镇楼! 受苦了宝贝...发货之前忘了指定快递服务,默认发了圆通。看这箱子样子,想起以前我去快递分拣兼职,活太累了管它什么物品都是暴力扔...

纸箱镇楼

大大小小拆完了就是这样样子了(32G SD卡、新秀丽蓝色双肩包、限量20年礼盒、Luce波点内胆包、EOS 200D机身、电池充电器\线 LC-E17C、锂电池LP-E17、相机宽背带 EW-400D、EF-s 18-55m f/4-5.6 IS STM)

所有的物品

看到这个狗头挺烦的,也怪自己吧...在天猫买件肯高UV镜55mm。买回来发现装不上...一看狗头58mm。

18-55mm狗头

为了业余摄影,还特地在博客开了个"摄影"分栏,感觉自己这次博客要大改了一下,需要做个几个相册,就那种卡片视图效果,不过我博客逻辑有点特殊,估计需要点时间,让我头疼的是图片,以后摄影照片一张5MB-10MB,就Github在国内这个接入速度有点吃不消,还是要想办法无备案DNS加速。

EOS 200D机身 + 18-55mm狗头合影

这里有点尴尬...本想来拍几张预热看看,没电来了,晚上准备骑行去广场玩玩

尴尬...刚买回来需要充电

装到内胆包

聊聊贾跃亭

2025年12月31日 23:22

跨年前夜,刷到贾跃亭发视频唱了一首北京北京,多少是想家了,希望他下周能回国吧

点开评论区,还是一个吊样:嘲讽和谩骂。我就纳闷了,骂他究竟能让你舒服多少?

有人指责他财务造假。说句不中听的,就这个环境,大到顶层建筑,小到微企个体,谁没做过一些违背良心的事?区别无非在于,有的你可以随便骂,有的你一句话就成了冒犯君主不敬之罪

还有人替供应商鸣不平,“他们收不到货款,哪里错了?” 真鸡巴扯淡,既然选择创业,就得承担风险。你作为下游供应链,和企业就是一根绳上的蚂蚱,这是何尝不是投资?而不是去餐馆吃饭,不满意就说菜难吃

就这一点,和那些把买房当理财的人有什么区别?
涨了炫耀,跌了骂娘,骂开发商、骂社会不公,甚至媒体曝光,集体诉讼。就好像风险只该由别人承担。这种逻辑,本身就很扯淡

看着这些评论,我只想笑
就单论一个人创业十余年,面对失败,面对别人的冷嘲热讽,仍选择向前走,你有这个魄力吗?

这还不算个爷们?

换位思考,把你放在贾跃亭的位置,给你三个选项,你会怎么做?你还会坚持自己的梦想吗?

  1. 回国,放下梦想,不能造车,改行谋生
  2. 打工,一辈子望到头,几乎没有还债的可能
  3. 出国,坚持梦想,继续创业,继续赌

我的博客 2025

2025年12月28日 08:19

从 2018 年 8 月 31 日开始,我们一起走过了 2675 个昼夜

你的评论,让博友之间距离更近

这一年,你写下了 233 条回复
世界偶尔沉默,而你选择了回应

你还记得吗? 1 月 7 日
你在“骑行郑州 · 四环”日志里,写下了今年首条回复
@小彦 初次落笔,幸好有你

不算太长短的字句,都留下痕迹

今年,你写了 48 篇日志,约 8.5 万字。这个数字占过去七年总和的 40%
相当于写完了两部呐喊

11 月是你的灵感高峰,相当于去年一整年的 60%
10 月 15 日,你写下了今年字数最多的一篇日志

全文 4000 +,字字惊心,你爽了吗?

热爱是唯一的通行证

这一年,你的博客收获了

12352 次页面访问
339 条访客评论

这些回声,将你的声音推向更远的地方

你的年度口头禅是:“实在太爽了!”

你最感兴趣的的领域是:骑行,全年提及 177 次,贯穿 28 篇日志

更有意思的是,你有 20 篇日志是在凌晨(00:00-05:00)完成的,你是真的不睡觉吗?

希望新的一年,你能遇见更多同频的人

2025 年,共有 103 位新朋友 闯入你的世界,TA 们通常在凌晨至中午出没

年度首席读者是 @obaby
TA 留下 37 条评论,真诚的人同路亦同心
特此颁发“年度最佳嘴替奖”:感谢这一年所有的欲言又止,都被你温柔接住

和你互动最多的是 @1900
聊得这么热乎,很难不让人怀疑:这到底是爱情的火花?还是那种“懂的都懂”的男上加男?

关于 2025:你想打包什么

这一年,真的要过去了

时间会被打包

记忆将会存档

最后,让我们用这一句年度金句,为你留下一份属于 2025 的真实源文件:

「时隔六年,再次为热爱脱皮。痛苦如影随形,却也因此更加坚定」

Photosuite:一个为博客而生的图片处理方案

2025年12月23日 07:58

每次给博客搞拆迁,最让人割舍不下的,往往不是瓦片和墙皮,而是那些被我用到形成肌肉记忆的家具。由于Jekyll 和 Astro 说着不同方言,导致家具根本拿不到“异地安置指标”,我只能含泪签字

Photosuite 正是在这样的背景下诞生的

它由 Vite + Typescript 开发,拥有我博客图像的核心能力,包括:

  • 灯箱

  • EXIF 展示

  • 图片说明

  • 图片路径自动补全

Example Picture

上面的图片及其所有样式,仅通过下面这一行最普通的 Markdown 语法生成,而且我只需要输入文件名:

![Example Picture](example-picture.jpg)

设计思路

Photosuite 利用 Remark 和 Rehype 插件生态,在 Markdown 编译阶段完成图片处理,避免在运行时增加负担:

- 构建期
  ├→ Remark:补全图片 URL
  └→ Rehype:读取图片 EXIF 并写入 HTML
  ↓
- 运行期
  ├→ photosuite(opts) 入口:按需动态 import
  ├→ glightbox 模块:把图变成可点击灯箱
  ├→ imageAlts 模块:用 alt 自动生成 caption
  └→ exif 模块:加载 EXIF 样式,清理空条

图片路径解析

如前所述,Photosuite 的首要职责是补全图片 URL

设计目标很简单:在 Markdown 中只写文件名,其余交给 Photosuite 处理

整体思路如下:

- 使用标准 Markdown 语法插入图片,只填写文件名
  例:![Example Picture](demo.jpg)

- 配置一个基础 URL 作为图片域名
  https://cdn.example.com/images

- 子目录来源有两种策略:
  a. 通过 Frontmatter 指定图片目录(默认)
     imageDir: 2025-12-22-photosuite

  b. 以当前 Markdown 文件名作为目录(去除后缀)
     2025-12-22-photosuite.md

- 最终生成的完整路径一致,例如:
  https://cdn.example.com/images/2025-12-22-photosuite/demo.jpg
  
- 稍作调整即可实现按年 / 月分类等更复杂的目录结构。

实现逻辑:

  1. 使用 Remark 插件遍历 Markdown AST 中的所有 image 节点
  2. 判断图片 URL 是否为“短链接”(无协议、非绝对路径、非显式相对路径 ../
  3. 按配置策略拼接完整 URL(域名 + 目录 + 文件名)
  4. 重写 AST 节点的 url 属性

EXIF 展示

EXIF 本身并不是 Photosuite 的核心创新点,毕竟,也不是我实现的

我只是在 HTML 生成之前,我通过 exiftool-vendored.js 提取图片参数,并将其以文本形式注入到 DOM 中,仅此而已,零 JS 成本、零性能开销

遍历 HTML AST → 找到 img → 解析图片 → 提取 EXIF → 重写节点结构

HTTP 图片的临时下载策略

function isHttpUrl(u: string): boolean {
  const x = new URL(u);
  return x.protocol === "http:" || x.protocol === "https:";
}

对于网络图片,Photosuite 不会直接让 exiftool 处理 URL,而是

  • 下载到 os.tmpdir()
  • 生成随机文件名
  • finally 中清理
const dl = await downloadToTemp(src);
filePath = dl.path;
cleanup = dl.cleanup;

可配置字段

exiftool-vendored.js 解析的 EXIF 数据非常多。这里 Photosuite 做了封装处理,除默认字段外,还可以任意搭配

默认展示字段:

['Model', 'LensModel', 'FocalLength', 'FNumber', 'ExposureTime', 'ISO', 'DateTimeOriginal']

通过 formatField 做语义化输出:

case 'FNumber':
  return `ƒ/${Number(value).toFixed(1)}`;
case 'ExposureTime':
  return value >= 1 ? `${value}s` : `1/${Math.round(1 / value)}s`;
case 'ISO':
  return `ISO ${value}`;

最终输出示例:

ILCE-7CM2 · E 28-200mm F2.8-5.6 A071 · 51.0 mm · ƒ/3.5 · 1/1250 · ISO 1000 · 2025/10/4

实际上,我最初我选择的是 MikeKovarik/exifr

它号称是速度最快、功能最全的 JavaScript EXIF 解析库
结果,我 Nikon z30 拍摄的照片它居然识别不出来机身?我尼康就低人一等吗!果断 PASS!

按需加载

Photosuite 的所有功能都是模块化设计的,样式同样如此

当你关闭某个功能时:

  • 对应的 JavaScript 不会加载

  • 相关 CSS 也不会出现在页面中

实现逻辑:

  1. 检查配置中的 scope(作用域选择器)是否存在于页面中

    • 若不存在,直接终止执行
  2. 根据功能开关配置:

    • enableLightbox, enableAlts, enableExif 并行、动态 import() 对应模块

DOM 标准化

Photosuite 设计了一套统一的 DOM 规范,供所有模块共享

核心思想是:

先把来源复杂的图片元素统一成稳定结构,再在此基础上扩展功能

开关 Photosuite 任意功能都会生成不同的结构,以下是所有功能开启时的最终结构:

<div class="photosuite-item">
	<div class="photosuite-exif"></div>
  <a class="glightbox" href="...">
    <img ... />
  </a>
  <div class="photosuite-caption">alt</div>
</div>

关键逻辑

export function ensurePhotosuiteContainer(el: Element): HTMLElement {
  let target: Element = el;

  // 如果 img 被 a.glightbox 包裹,则提升包裹层级
  if (
    el.tagName.toLowerCase() === "img" &&
    el.parentElement?.tagName.toLowerCase() === "a" &&
    el.parentElement.classList.contains("glightbox")
  ) {
    target = el.parentElement;
  }

  // 如果已经在 photosuite-item 中,直接复用
  const parent = target.parentElement as HTMLElement | null;
  if (parent?.classList.contains("photosuite-item")) {
    return parent;
  }

  // 创建统一容器
  const wrapper = document.createElement("div");
  wrapper.className = "photosuite-item";

  // 用 wrapper 替换 target
  parent?.replaceChild(wrapper, target);
  wrapper.appendChild(target);

  return wrapper;
}

有了这个保证之后,后续逻辑可以完全不关心图片来源

// 查找主体
container.querySelector("img");

// 添加 UI(caption)
container.appendChild(caption);

// 查询数据:有就显示,没有就清理(EXIF)
container.querySelector(".photosuite-exif");

安装

Photosuite 已发布至 npm,可直接安装:

pnpm add photosuite
# or
npm install photosuite
# or
yarn add photosuite

快速开始

配置 Photosuite 非常简单,以Astro为例:

import { defineConfig } from 'astro/config';
import photosuite from 'photosuite';
import "photosuite/dist/photosuite.css";

export default defineConfig({
  integrations: [
    photosuite({
      // [必填] 生效范围选择器
      // 建议限定在文章容器内,避免影响站点其他区域。支持多个选择器,用逗号分隔
      scope: '#main',
    })
  ]
});
import "photosuite/dist/photosuite.css";

Photosuite 基于 Vite + TypeScript 开发,理论上适用于多种架构

如果您在配置中遇到任何问题,欢迎联系我,为爱发电,无偿奉献

后续计划

  1. 引入 Lozad.js 实现图片懒加载
  2. 构建期获取图片尺寸,生成占位符,避免布局抖动
  3. 适配更多博客架构
  4. 进一步优化 Photosuite 的按需加载机制

相关资料

如果 Photosuite 对您有帮助,欢迎在 GitHub 点个 ⭐️
它不会让代码跑得更快,但会让我写得更勤快

PS:如果您正在使用 Photosuite
欢迎告诉我您的博客地址,我会把它展示在项目页面中

我的第一次 GitHub PR

2025年11月26日 23:23

我一直觉得 Artalk 的 ui 设计的很不协调,特别评论框下方的“ 评论数、通知中心"
这并非重要功能,但是官方没有做开关控制。实现后,感觉很实用,可以推一下

确定上游

$ git remote remove upstream
$ git remote add upstream https://github.com/ArtalkJS/Artalk.git
$ git remote -v
origin  git@github.com:achuanya/Artalk-ui.git (fetch)
origin  git@github.com:achuanya/Artalk-ui.git (push)
upstream        https://github.com/ArtalkJS/Artalk.git (fetch)
upstream        https://github.com/ArtalkJS/Artalk.git (push)

因为我 main 分支有非常多改动,比如评论框 UI 等等。那些我不想提交,这里必须创建一个干净的分支

创建分支

$ git fetch upstream
# 基于上游仓库创建一个干净分支
$ git checkout -b clean-pr-branch upstream/master
branch 'clean-pr-branch' set up to track 'upstream/master'.
Switched to a new branch 'clean-pr-branch'

cherry-pick 指定 commit

$ git cherry-pick 6302e4216cc5c1f34df49475ef99e81d883a2d79
CONFLICT (modify/delete): conf/artalk-ui-example.yml deleted in HEAD and modified in 6302e421 新增两个前端配置开关,用于控制列表头部显示).  Version 6302e421 (新增两个前端配置开关,用于控制列表头部显示) of conf/artalk-ui-example.yml left in tree.
Auto-merging conf/artalk.example.simple.yml
Auto-merging conf/artalk.example.yml
Auto-merging ui/artalk/src/defaults.ts
Auto-merging ui/artalk/src/style/list.scss
error: could not apply 6302e421... 新增两个前端配置开关,用于控制列表头部显示
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

解决冲突

$ git status
On branch clean-pr-branch
Your branch is up to date with 'upstream/master'.

You are currently cherry-picking commit 6302e421.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
        modified:   conf/artalk.example.simple.yml
        modified:   conf/artalk.example.yml
        modified:   conf/artalk.example.zh-CN.yml
        modified:   conf/artalk.example.zh-TW.yml
        modified:   ui/artalk/src/defaults.ts
        modified:   ui/artalk/src/list/list.ts
        modified:   ui/artalk/src/plugins/list/count.ts
        modified:   ui/artalk/src/style/list.scss
        modified:   ui/artalk/src/types/config.ts

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
        deleted by us:   conf/artalk-ui-example.yml

$ git rm conf/artalk-ui-example.yml
rm 'conf/artalk-ui-example.yml'

$ git cherry-pick --continue
[clean-pr-branch 0b5de4d3] 新增两个前端配置开关,用于控制列表头部显示
 Date: Wed Nov 26 02:23:21 2025 +0800
 9 files changed, 61 insertions(+), 3 deletions(-)
 
 $ git push origin clean-pr-branch
Enumerating objects: 39, done.
Counting objects: 100% (39/39), done.
Delta compression using up to 16 threads
Compressing objects: 100% (20/20), done.
Writing objects: 100% (20/20), 2.75 KiB | 281.00 KiB/s, done.
Total 20 (delta 18), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (18/18), completed with 18 local objects.
remote: This repository moved. Please use the new location:
remote:   git@github.com:achuanya/artalk-ui.git
remote:
remote: Create a pull request for 'clean-pr-branch' on GitHub by visiting:
remote:      https://github.com/achuanya/artalk-ui/pull/new/clean-pr-branch
remote:
To github.com:achuanya/Artalk-ui.git
 * [new branch]        clean-pr-branch -> clean-pr-branch

大致意思就是上游没有这个文件 conf/artalk-ui-example.yml 这是我复制的备份配置,用处不大,删了然后推走

使用 GitHub CLI 创建 PR

$ gh auth login
? Where do you use GitHub? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: C42D-217C
Press Enter to open https://github.com/login/device in your browser...
✓ Authentication complete.
- gh config set -h github.com git_protocol https
✓ Configured git protocol
✓ Logged in as achuanya

$ gh pr create --repo ArtalkJS/Artalk --base master --head achuanya:clean-pr-branch

Creating pull request for achuanya:clean-pr-branch into master in ArtalkJS/Artalk

? Title (required) 新增两个界面配置开关,用于控制:左侧“评论数”、右侧“通知中心” 的显示控制
? Body <Received>
? What's next? Submit
https://github.com/ArtalkJS/Artalk/pull/1113

地址都给出了,说明 PR 已经创建好了
不过 Artalk 官方已经断更几年了,审核过与否都不重要,就当玩了

Introducing Astro Lhasa 1.0

2025年11月25日 04:09

Astro Lhasa 是一款简洁、响应式、无障碍友好、并具备良好 SEO 的 Astro 博客主题

该主题基于 Astro Paper,并借鉴了好大哥 Fooleap 博客的布局设计

特性

  • [x] 极快的加载性能
  • [x] 内置 SEO 优化
  • [x] 多语言支持(i18n)
  • [x] 支持 Markdown、Mdx
  • [x] 无障碍支持(键盘/旁白)
  • [x] 响应式设计(适配手机到桌面)
  • [x] 本地模糊搜索
  • [x] 草稿文章与置顶
  • [x] GLightbox 图片灯箱
  • [x] 超链接将会在新标签页打开
  • [x] 自动生成 sitemap 与 RSS
  • [x] Expressive Code 代码高亮
  • [x] 全站懒加载滚动分页、返回顶部按钮
  • [x] 基于时间的明暗模式切换(自动适应系统主题)
  • [x] 深度适配 Artalk 评论系统(布局结构和主题配色)
  • [x] 自动展开和折叠的文章目录(可在文章中配置开关)
  • [x] 为 img 标签自动添加原始宽高,支持缓存
  • [x] 图片美化:EXIF 样式、Alt 标签(长条与角标)
  • [x] 图片懒加载,且 src 只需要输入文件名,编译时自动补全路径

Lighthouse 评分

项目结构

/
├── public/
│   ├── assets/
│   └── pagefind/ # auto-generated when build
├── posts/
│   ├── life
│   └── technology
│       └── 2025-11-25-introducing-astro-lhasa-1-0.mdx
├── src/
│   ├── assets/
│   │   ├── icons/
│   │   └── images/
│   ├── components/
│   ├── i18n/
│   ├── layouts/
│   ├── pages/
│   ├── styles/
│   ├── utils/
│   ├── config.ts
│   ├── constants.ts
│   └── content.config.ts
├── ecosystem.config.cjs
└── astro.config.ts

Astro 会根据 src/pages/ 中的 .astro、.md、.mdx 文件自动生成路由

静态资源(如图片)可以放在 public

博客文章都放在 posts 下,创建文件夹后,按照文件夹名自动分类文章

技术栈

主要框架 - Astro 5
语言与类型 - TypeScript
样式 - Tailwind CSS v4@tailwindcss/vite@tailwindcss/typography
代码高亮 - Astro Expressive Code(行号、可折叠段落)
Markdown/MDX 扩展 - remark-mathrehype-katexrehype-figurerehype-slugrehype-autolink-headingsrehype-external-linksrehype-wrap-all 静态搜索 - Pagefind Default UI
图片灯箱 - GLightbox
OG 图像 - Satori + Resvg(站点与文章均支持)
构建优化 - astro-compressor@zokki/astro-minify
友联订阅 - RSS Lhasa
评论系统 - Artalk ui
部署 - Docker/Nginx/Pm2(含 Dockerfiledocker-compose.ymlecosystem.config.cjs
代码规范 - ESLint 9 + Prettier 3

使用方法

启动项目:

pnpm install
pnpm run dev

也可以使用 Docker:

docker build -t astropaper .
docker run -p 4321:80 astropaper

Google 网站验证

你可以通过环境变量轻松在 Astro Lhasa 中添加 Google 网站验证 HTML 标签

# 在你的环境变量文件 (.env) 中
PUBLIC_GOOGLE_SITE_VERIFICATION=your-google-site-verification-value

命令

所有命令都在项目的根目录下,通过终端运行

Command Action
pnpm install 安装依赖
pnpm run dev 启动本地开发服务器,访问地址为 localhost:4321
pnpm run build 构建生产环境网站到 ./dist/
pnpm run preview 在部署之前预览本地构建
pnpm run format:check 检查代码格式是否符合 Prettier 标准
pnpm run format 使用 Prettier 格式化代码
pnpm run sync 为所有 Astro 模块生成 TypeScript 类型 Learn more
pnpm run lint 使用 ESLint 进行代码检查
docker compose up -d 使用 Docker 运行 Astro Lhasa,你可以通过在 dev 命令中提供的相同主机名和端口访问
docker compose run app npm install 你可以在 Docker 容器内运行上述任何命令
docker build -t astrolhasa . 构建 Docker 镜像
docker run -p 4321:80 astrolhasa 在 Docker 中运行 Astro Lhasa,网站将在 http://localhost:4321 上可访问
pm2 start ecosystem.config.cjs 使用 Pm2 启动 Astro Lhasa,访问地址为 localhost:4321

警告!Windows PowerShell 用户可能需要安装 concurrently,如果想在开发过程中运行诊断命令(例如:astro check --watch & astro dev)

License

本项目采用 Anti 996 License 授权协议发布,详情请查阅 LICENSE

ThinkPHP 6.0 下载安装与配置

2020年4月14日 15:28
  • ThinkPHP 6.0 要求 PHP >= 7.1.0
  • 6.0版本开始,必须通过Composer方式安装和更新,无法使用Git下载安装。

安装Composer

$ curl -sS https://getcomposer.org/installer | php

# 全局调用
$ sudo mv composer.phar /usr/local/bin/composer

 /usr/src [09:22:13]
achuan$ composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.10.5 2020-04-10 11:44:22

# 使用阿里云镜像
$ composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

安装

# 稳定版
$ composer create-project topthink/think tp6

# 开发版
$ composer create-project topthink/think=6.0.x-dev tp6

# 更新已安装的旧版本核心框架
$ composer update topthink/framework

配置

# apache配置
$ sudo vim /etc/httpd/conf/extra/httpd-vhosts.conf
<VirtualHost *:80>
    ServerName tp6.io
    DocumentRoot /home/achuan/language/php/tp6/public
    <Directory  "/home/achuan/language/php/tp6/">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

$ sudo vim /etc/hosts
127.0.0.1 tp6.io

# 不进行apache配置也可以测试运行 默认端口为:8000
$ php think run -p 1081

# 环境变量定义 可以更名为.env进行修改生效,该文件默认开启调试模式。
$ mv example.env .env

# 对外访问
$ sudo chmod -R 777 public

# 安装多应用模式扩展 think-multi-app
$ composer require topthink/think-multi-app
# 创建一个名为 index 的应用,返回Successed则成功
$ php think build index
# 多应用模式部署后,必须要删除controller目录,因为系统根据该目录作为判断是否为单应用
$ rm -rf controller

目录结构

单应用模式

www  WEB部署目录(或者子目录)
├─app           应用目录
│  ├─controller      控制器目录
│  ├─model           模型目录
│  ├─ ...            更多类库目录
│  │
│  ├─common.php         公共函数文件
│  └─event.php          事件定义文件
│
├─config                配置目录
│  ├─app.php            应用配置
│  ├─cache.php          缓存配置
│  ├─console.php        控制台配置
│  ├─cookie.php         Cookie配置
│  ├─database.php       数据库配置
│  ├─filesystem.php     文件磁盘配置
│  ├─lang.php           多语言配置
│  ├─log.php            日志配置
│  ├─middleware.php     中间件配置
│  ├─route.php          URL和路由配置
│  ├─session.php        Session配置
│  ├─trace.php          Trace配置
│  └─view.php           视图配置
│
├─view            视图目录
├─route                 路由定义目录
│  ├─route.php          路由定义文件
│  └─ ...   
│
├─public                WEB目录(对外访问目录)
│  ├─index.php          入口文件
│  ├─router.php         快速测试文件
│  └─.htaccess          用于apache的重写
│
├─extend                扩展类库目录
├─runtime               应用的运行时目录(可写,可定制)
├─vendor                Composer类库目录
├─.example.env          环境变量示例文件
├─composer.json         composer 定义文件
├─LICENSE.txt           授权说明文件
├─README.md             README 文件
├─think                 命令行入口文件

默认应用文件

├─app           应用目录
│  │
│  ├─BaseController.php    默认基础控制器类
│  ├─ExceptionHandle.php   应用异常定义文件
│  ├─common.php            全局公共函数文件
│  ├─middleware.php        全局中间件定义文件
│  ├─provider.php          服务提供定义文件
│  ├─Request.php           应用请求对象
│  └─event.php             全局事件定义文件

相关文档:

坐于顶,闻茶香,听古典,观夕阳

2025年11月17日 18:29

坐于顶,闻茶香,听古典,观夕阳

清晨跑西湖,傍晚之江路。生于闹市,独善其行。杭州给予的诱惑实在太多太多

论水,南至钱塘江,北望西湖边,海竿、矶竿一字排开,可摆长蛇阵。而萧山、滨江,溪河交织,水网密布,网工、电工竟遍地开花,什么叫国际大都市?这就是包容

论山,5A级西湖风景名胜区,群山环抱。行至十里琅珰,可品茶田古道。登临北高峰,云雾缭绕,下望灵隐寺,抬头便是财神庙。只要你想,自行车都可背进山峦之中,我爱死这个地方了

十里琅珰

今天只为观景,均速和野路不在考虑范围内,所以我穿了一双运动鞋出门,凤山门乘坐308转103到达龙井村山门“十里琅珰”

半山腰

周日的台阶,人挤人。终究还是没忍住,走了野路,要么说鞋子重要,手脚并用向上爬时,脚滑了好几次,每次落脚都没有安全感

龙井村

九溪

标毅线途经点,云栖竹径方向(野路)

下山后,来到了杭州的母亲河,矶钓,路亚的圣地:钱塘江(之江路)

钱塘江施工处,这钓位的含金量,谁懂啊!

六和塔

六和塔最近非常火,据文保所记载,这里是鲁智深听潮圆寂,武松出家的地方。乾隆曾六下江南,六次登临。是杭州地标建筑之一,建于宋,修于清,有着千年文化底蕴,是中国第一批全国重点文物保护单位

有意思的是六和塔的修建背景,塔基原址是吴越王钱弘俶的南果园,只因怀疑钱塘江大潮是河妖所施,便舍园建塔。天王盖地虎,宝塔镇河妖,用在此处再合适不过

钱塘江(之江路)

夜西湖

27 公里 1510 米!徒步新手毕业了

2025年11月15日 18:56

标毅线,又名“西湖群山标准毅行线路”,全长约25公里,累计爬升约1500米。它是杭州最著名的徒步登山路线,整条线路穿行于国家AAAAA级景区“杭州西湖风景名胜区”的山峦之中

该路线诞生于2002年6月1日。当时,杭州的户外爱好者借鉴香港“乐施毅行者”的众筹模式,策划并举办了首届西湖群山徒步比赛

此后,这条路线便被徒步登山爱好者们称为“标毅线”,并逐渐成为了杭州户外的一张名片,也被许多人视作户外爱好者的“毕业考核线路”

河坊街

早上六点我就起床了,洗漱、吃饭。把两个500ml的软水瓶灌满凉水。中午不打算下山,所以带足了吃的:三块芝士牛乳吐司、两个糯玉米、两个土鸡蛋、两块黑巧德芙、一包康比特盐丸,以及明胶软糖若干

走到鼓楼,看到卖早点的,感觉还是没吃饱,于是又吃了四个包子和一杯豆浆

起点,吴山广场山脚下捡的石头

半个月前,在还没接触徒步时,我看过一个电影《朝圣之路 The Way (2010)》 便也学着电影里的汤姆,在起点捡了一块石头

同志们,胜利的时候,请你们不要忘记我们 - 裘古怀烈士遗言

晨时的杭州

今天穿了套陆军体能服:上身07陆短袖 + 21圆边帽,下身17陆长裤 + 21陆战靴。我本以为天会冷些,还多穿了件蜂窝速干背心,结果艳阳高照,大约十公里后就热得脱掉了

杭州少年儿童公园

标毅线的常规起点在“老和云起”,而我住在凤凰山脚下,距离终点很近(吴山广场),所以我是反向而行,经过杭州少年儿童公园时,需要交15元的门票(常规路线不需要花这个钱)。在公园深处有上山的野路

在贵人阁处拍摄。图中远方的山峰应该就是:龙井、上天竺、狮峰、棋盘山、天马山、龙门山、北高峰等等,这些均在本次的行程范围内

九溪茶园

文碧峰

十里琅珰

“十里琅珰”是一条南北向的山脊线,也是杭州群山中视野最好的路段之一,可以尽览茶园风光和江湖两望

若时间紧张,可以通过:地铁、公交或社会车辆抵达梅家坞公交站,在此处就可以看到十里琅珰入口的牌坊

大路太平坦,不舒服,在林子里行走还能按按摩

西湖区方向

老和云起(北门)终点合影

还特意穿了一双厚毛袜,太闷了

起点,吴山广场山脚下捡的石头

临走的时候又带走了,拿回家放花坛也不错

下山后沿着非机动车道走了几公里,本想穿行去苏堤的,误打误撞又走进一座山

西湖最美转角,全天挤满人

逛了一圈,准备回家

今天的功臣,21式作战靴。几乎没有它不能踩的地方,唯一的缺点就是太闷了。若下次还有登山需求,我仍会选择它,但日常穿就算了。

我妈在双十一给我买了一双新鞋,一体成型三代。我大概已经有十年没穿过黑色鞋子了

新书也到货了。拿少许燕麦冲了杯咖啡,躺床上看会儿书

最近的活动

2025年11月14日 01:31

最近一直在徒步登山,早晚就去环西湖跑跑步,也对杭州的地貌了解差不多了,准备来一次新手毕业徒步

貌似战时防空洞

这是我在山顶遇到的山地大神,上一次看到骑车上山的博主叫:猛蛇过江,也是个屌人

出去一定要带卫生纸,狂奔下山,幸亏山脚下有公共卫生间...

迪卡侬的包有些大,不适合日常跑步。我买了一个国产奥尼捷的包,蛮小的,很适合跑步用,若在外不过夜,徒步爬山也方便

杭州越野跑公开赛

去杭州动物园逛了逛,感觉就那样,不如成都动物园

如果在杭州只能选择一条徒步路线,我只去十里琅珰,视野太开阔啦

穿骑行服跑步确实降维打击,装东西都方便了。今天右脚踝有些不舒服,但是破纪录了,可以花更短的时间来跑步了

自不做采购后,再也没去过超市和市场。今天我妈不上斑,陪她去盒马买菜,看到冰柜有卖就拿了点,还是那个屌味

饭后我妈给我拿了一个大壶,真漂亮啊,比上次那个青花瓷壶大两倍

明天我要去的是杭州的一个加强版标毅线,大概在28公里,爬升1600,也有可能是35公里

我准备了玉米、鸡蛋、巧克力、盐丸、面包,明天不打算下山了,一口气走完

喜得配发物资

2025年11月7日 18:57

我最近在找连队配发的体能服和奔雷帽,但网上仿品太多了,实在不容易

这不,要啥来啥,很偶然的一个机会得到了些硬货,一分没花

  • 07 低腰迷彩作训胶鞋,3520 配发品

鞋面是莱卡布,鞋底为橡胶,鞋子左右两侧和鞋头的关键受力部位都加大了灌胶面积,踩踏的感觉非常舒服,比我的耐克强多了

  • 07 WJ 短筒绒皮鞋,3515 配发品

上好的黄牛皮,鞋舌都是一体式的,鞋里是平剪维罗绒,底鞋是双密度橡胶底,鞋头还做了加固,整个版型非常硬

还配了山东起华的防伪码,扫了下,还是第一次验证

WJ 配发鞋垫

野战急救包

剪子 1 把 碘伏片 4 片
镊子 1 个 三角巾绷带 1 包
纱布 2 片 止血带 1 卷
OK绷 10 片 别针 2 个
弹性绷带 1 卷 合格证 1 个
胶带 1 卷 简易说明书 1 份
消毒片 10 片 收纳包 1 个

让博客的代码块更有表现力

2025年11月3日 23:59

我最初用的代码高亮插件是Shiki,由 Astro Paper 自带,时间久了,就感觉平平无奇的。它无法像 Git diff 那样,把增删的代码做出差异化表达

直到偶然遇见 Expressive Code,我才发现代码高亮还能这样玩

它的渲染效果蛮惊艳的,让冷冰冰的代码赋有张力:

func greet(id int, wg *sync.WaitGroup) {
	defer wg.Done()

	delay := time.Duration(rand.Intn(3000)) * time.Millisecond
	time.Sleep(delay)

	fmt.Printf("#%d Ping %v\n", id, delay)
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var wg sync.WaitGroup
  numGoroutines := 10
	numGoroutines := 5

	for i := 1; i <= numGoroutines; i++ {
		wg.Add(1)
		go greet(i, &wg)
	}

	wg.Wait()
}
$ go run main.go
#2 Ping 135ms
#4 Ping 694ms
#1 Ping 842ms
#5 Ping 1.662s
#3 Ping 1.862s
```go title="main.go" {17-20,22} del={14} ins={15,4-7} collapse={2-7} "rand.Seed(time.Now().UnixNano())"
func greet(id int, wg *sync.WaitGroup) {
	defer wg.Done()

	delay := time.Duration(rand.Intn(3000)) * time.Millisecond
	time.Sleep(delay)

	fmt.Printf("#%d Ping %v\n", id, delay)
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var wg sync.WaitGroup
  numGoroutines := 10
	numGoroutines := 5

	for i := 1; i <= numGoroutines; i++ {
		wg.Add(1)
		go greet(i, &wg)
	}

	wg.Wait()
}
```

而且 Expressive Code 对静态架构非常友好,只要你的博客支持 Rehype 插件,就可以安装使用

Docs:Expressive Code

功能 语法示例 说明
标题 title="app.ts" 显示编辑器标签标题
语法高亮(ANSI) ansi 渲染 ANSI 终端转义
行标注(高亮) {3,5-9} 高亮第 3 行与 5–9 行
行标注(新增) ins={4-6} 以“新增”样式高亮
行标注(删除) del={7,12} 以“删除”样式高亮
行标注 + 标签 ins={"Init":3-6} 为 3–6 行添加引用标签
内联标注(文本) ins="inserted" del="deleted" 高亮匹配到的片段
内联标注(正则) ins=/foo$.+$/ 用正则匹配片段
折叠区块 collapse={1-5,12-14} 折叠多段代码
行号开关 showLineNumbers 需安装行号插件
起始行号 startLineNumber=5 从第 5 行开始计数
自动换行 wrap 自动换行
悬挂缩进 wrap hangingIndent=2 换行时额外缩进 2 列
不保留缩进 wrap preserveIndent=false 换行后不缩进

上天竺

2025年11月3日 00:51

杭州寺庙非常多

据官方统计有 271 座,早些年甚至多达 500 座,号称:"东南佛国"

在这数百座寺庙中,林隐寺则是顶流

但要论到谁的香火最灵验?法喜寺是不二之选

当然,我既不信宗教,也不信命,只是逛一逛

观音第一灵感道场

据院内文献记载,法喜寺的历史可以追溯到一千多年前:晋天福初年(936年)

那时,它还只是一座很小的观音看经院,到了南宋时期才逐渐扩建,并改名为“天竺教寺”

清康熙年间,康熙南巡路过此地,赐名:"法喜寺"

虽几经更名,但以观音为主题的思想始终未变,因此,这里的牌额才写为:"观音第一灵感道场"

入三摩(ma)地

迎面的门楣:“入三摩地” 它的背后同样有题词,而且是康熙所写:“莫向外求”

观自在菩萨

没记错的话,这两只猫就住在方丈楼下的假山处

我看了半小时,它们也打了半小时

我觉得法喜寺的出圈是脱不开出片的

因为寺庙是依山而建,层层升高,到了顶处,站在石栏边,可以能俯瞰整座寺庙,房与房错落有致,瓦檐叠影,极有层次感

而且,它们很明白自己的长处,招募志愿者把守各个高地,只为维护黄金点位,让游客更好的拍照

当然,这里展现的图片并不是我所说的,今天穿太厚,很热,我拍不动了

船游西湖,徒步河坊街

2025年11月1日 01:06

下午三点左右,连日的降水终于停了,出门

沿着万松岭路下山,穿过长桥,我看到湖面上不少电动船。没体验过,试试

每小时 60 元,押金 100 元,可坐四人,并不算贵

西湖周边的公园湿气极重,几乎每棵树的阴面都覆满大面积的青苔

上次来河坊街还是 2021 大年初一

人民的椅子

纯铜雕塑,门口竖立着牌匾称:「人民的椅子」一时间,我竟分不清谁是人民

这个藏馆以铜雕为主,个人 IP 商业化为目的,均为朱炳仁个人作品,馆内可做交易

从头走到尾,再从尾回到头,我感觉现在的河坊街档口产品同质化非常严重

同品牌的金银饰品铺和小吃档口,一条街上竟能看到三家一模一样的,从门头装修到陈列布局,几乎毫无差别,地方也不管管

这里是鼓楼,是杭州的热门地标之一

鼓楼旁有个叫“大马弄”的菜市场,我个人认为,那才是杭州的 No.1

这地方可以说是杭州最后的老底子,有些烟火气

我建议去逛早市,这是多数游客不知道的地方,平台也很少推

在采购部工作时,大马弄也是常来的地方,可以买到不能说的秘密

在河坊街买了一盒明信片,有三十张。感觉质量略差,打算自费邮寄给博友

表妹要毕业了,给我邮寄来两份就业协议书,年轻人应该明白什么意思

打造全新 Artalk 评论界面

2025年10月26日 23:57

Artalk-uiArtalk v2.9.1 的一个分支,主要重构 ui

本次更新的主要功能如下:

  • 全新 Artalk 评论界面,评论框分段式折叠展开,响应式布局
  • 表单处展示动态头像,自动根据邮箱哈希计算,Artalk 后端可改 API
  • 扩展 Color 变量,尽力做到一切前端非硬编码
  • 优化 Markdown Preview 体验,修复 bug
  • 添加 Crypto-js 支持(Crypto-js

大多数其他功能,例如后端逻辑、前端接口等,基本保持不变

用法:

1.环境要求

  • Node.js(^ 20.17.0)
  • PNPM (^ 9.10.0)
  • Go(^1.22.7)
  • Make(任意)
  • Gcc (必须启用CGO_ENABLED=1)

对环境不熟悉的,直接复制下面一条龙命令,缺哪个装哪个

# Go + Make + Gcc
sudo apt update && sudo apt install -y make gcc && wget https://golang.google.cn/dl/go1.21.4.linux-amd64.tar.gz && sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.21.4.linux-amd64.tar.gz && for file in ~/.bashrc ~/.zshrc; do grep -q 'export GOROOT=/usr/local/go' "$file" || echo -e '\nexport GOROOT=/usr/local/go' >> "$file"; grep -q 'export GOPATH=$HOME/go' "$file" || echo 'export GOPATH=$HOME/go' >> "$file"; grep -q 'export PATH=\$PATH:\$GOROOT/bin:\$GOPATH/bin' "$file" || echo 'export PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> "$file"; grep -q 'export GOPROXY=https://goproxy.cn,direct' "$file" || echo 'export GOPROXY=https://goproxy.cn,direct' >> "$file"; done && SHELL_NAME=$(basename "$SHELL") && [ "$SHELL_NAME" = "zsh" ] && source ~/.zshrc || source ~/.bashrc

# Nvm + Node + pnpm + PM2
curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.39.7/install.sh | bash && export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" && for file in ~/.bashrc ~/.zshrc; do grep -q 'export NVM_DIR="$HOME/.nvm"' "$file" || echo -e '\nexport NVM_DIR="$HOME/.nvm"' >> "$file"; grep -q '\. "\$NVM_DIR/nvm.sh"' "$file" || echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> "$file"; grep -q '\. "\$NVM_DIR/bash_completion"' "$file" || echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> "$file"; done && SHELL_NAME=$(basename "$SHELL") && [ "$SHELL_NAME" = "zsh" ] && source ~/.zshrc || source ~/.bashrc && nvm install --lts && npm config set registry https://registry.npmmirror.com && npm install -g pnpm pm2 && pnpm config set registry https://registry.npmmirror.com

2.源码编译

全程 Linux 操作,如果你用 Windows,建议 MSYS2

git clone https://github.com/achuanya/Artalk-ui.git

  1. 根目录执行 pnpm i 安装依赖
  2. 找到 ./conf/artalk.example.yml 复制到根目录,重命名为 artalk.yml
  3. 运行 make build-frontend (会把嵌入的前端主程序和侧边栏前端程序放入 /public 目录)
  4. 执行 make build 构建后端
➜  Artalk-ui git:(master) make build
go build \
    	-ldflags "-s -w" \
        -o ./bin/artalk \
    	github.com/artalkjs/artalk/v2
    
➜  Artalk-ui git:(master) ls -lh ./bin/artalk                  
-rwxr-xr-x 1 lhasa lhasa 46M 10月 27 06:14 ./bin/artalk

3.使用 PM2 持久化运行

执行前,在根目录创建一个data目录,用于存储数据库文件

后端运行后,会在其中生成artalk.db文件

➜  Artalk-ui git:(master) pm2 start ./bin/artalk --name artalk -- server
[PM2] Spawning PM2 daemon with pm2_home=/home/lhasa/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/lhasa/code/Artalk-ui/bin/artalk in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ artalk             │ fork     │ 0    │ online    │ 0%       │ 22.7mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
➜  Artalk-ui git:(master) pm2 startup                         
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/lhasa/.nvm/versions/node/v22.21.0/bin /home/lhasa/.nvm/versions/node/v22.21.0/lib/node_modules/pm2/bin/pm2 startup systemd -u lhasa --hp /home/lhasa
➜  Artalk-ui git:(master) pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/lhasa/.pm2/dump.pm2

4. 创建管理员账号

一定要先运行后端,然后再创建管理员账号,否则会丢失数据

➜  Artalk-ui git:(master) ./bin/artalk admin
2025/10/27 06:32:23.291 INFO [core/gen.go:70] File Generated: /home/lhasa/.config/artalk/artalk.yml
--------------------------------
 Create admin account
--------------------------------
Enter Username: lhasa
Enter Email: haibao1027@gmail.com
Enter Password: 
Retype Password: 
--------------------------------
  Name: lhasa
  Mail: haibao1027@gmail.com
--------------------------------

Artalk CLI 命令

命令 描述 常见用法示例
server 启动 Artalk 服务端 ./bin/artalk server -c ./artalk.yml
admin 创建或修改管理员账号 ./bin/artalk admin
config 输出当前配置文件信息 ./bin/artalk config
export 导出数据(Artransfer 格式) ./bin/artalk export --out backup.json
import 导入数据(Artransfer 格式) ./bin/artalk import --in backup.json
gen 工具集合(如生成配置、密钥等) ./bin/artalk gen
upgrade 升级到最新版本 ./bin/artalk upgrade
version 输出版本信息 ./bin/artalk version
completion 生成 Shell 自动补全脚本 ./bin/artalk completion zsh
help 查看帮助信息 ./bin/artalk help
参数 描述 示例
-c, --config <path> 指定配置文件路径(默认 ./artalk.yml ./bin/artalk server -c /etc/artalk.yml
-w, --workdir <dir> 指定工作目录(默认 ./ ./bin/artalk -w /var/www/artalk server

5.集成到博客

该样式位于:/ui/artalk/dist

<!-- CSS -->
<link href="http://artalk.example.com:8080/dist/Artalk.css" rel="stylesheet" />

<!-- JS -->
<script src="http://artalk.example.com:8080/dist/Artalk.js"></script>

<!-- Artalk -->
<div id="Comments"></div>
<script>
Artalk.init({
  el:        '#Comments',               // 绑定元素的 Selector
  server:    'http://artalk.example.com:8080', // 后端地址
  site:      'Artalk 的博客',               // 你的站点名
  pageKey:   '/post/1',                   // 固定链接
  pageTitle: '关于引入 Artalk 的这档子事',   // 页面标题 (留空自动获取)
})
</script>

时隔四年,晨跑环西湖

2025年10月24日 00:01

凌晨六点,鸟鸣声和钢琴乐响起,即使不看表,我也知道现在几点了,因为这是我三个小时前定的闹钟

是的,昨晚又只睡了三个小时

$ git log -1
commit db8a16dab43cfb744b6a926ab4356a376030a147 (HEAD -> main, origin/main, origin/HEAD)
Author: achuanya <haibao1027@gmail.com>
Date:   Thu Oct 23 02:16:11 2025 +0800

    fix: 修正文章中的错别字“帮”为“棒”

确实有些困,但就这种强度而言,不算什么,早已习惯

今天是我回到杭州的第一个清晨,可不能偷懒

公路车暂时不能骑了,因为碳轮裂了。这段时间都不能骑车了

先憋会儿吧,打算换一对 DT Swiss 顶级圈刹轮组,大概要两万左右,所以这段时间只能跑步了

林隐寺·检票口

雷峰塔·检票口

住得近就是方便,跑到林隐寺两根烟的功夫

苏堤·南入口公园

鄙人不善于奔跑

西湖钓鱼区

上次我来西湖钓鱼区时,这里的钓法还没有现在这么夸张

可这次,我都看到有人已经摆龙门阵了,一人八根抛竿

哎,不禁感叹,是杭州对钓鱼政策的包容度高,还是对权力的包容度高?

众所周知,西湖区钓鱼证件不是随便就能拿到的,清一色红色背景

而且这里是苏堤,来往行人非常多,不知道那些被地方暴政一刀切的钓友看到,有何感想?

中央三令五申?向来是个笑话:“有__性,没人性”

我从义乌搬家到杭州了

2025年10月23日 02:07

还是想回杭州,自 2021 年末,前公司把我从杭州调到上海后,我再也没有回到杭州工作过

22 号中午吃完饭,我在街道上看到几辆货拉拉。上前敲了敲车窗问:“杭州去不去?”

他们都说太远、没回程单,纷纷拒绝了

回家的路上,我用货拉拉预约了一单

又和房东阿姨打了招呼,说今晚五六点左右把房子收拾好,请她来验收

收拾行李的时候,我以为东西不多

可当收纳到鞋子时,实在找不到地方放

我鞋子不多,只有五双,只好下楼到商店买了两个中号行李袋,一袋装鞋,一袋装书

大约一小时后,东西收拾完了:五个包,一个行李箱

打扫卫生中

把地面拖净后,我再次联系房东阿姨

和她坐在屋里聊聊天,得知这间房还没租出去

来看房的租客各种挑刺:不是嫌不朝阳,就是嫌贵

我这人是不挑的,躺哪都是睡

毕竟我冬天的桥洞、夏天的广场都睡过,适应能力堪比蟑螂

下午五点时,我已把行李搬到楼下

司机还有十分钟到,我不想等了,没啥意义

我给房东阿姨发了条消息:“剩余水电费等您儿子下班后抄下水表,照合同算吧,剩多少转我就行”

简单寒暄几句后,司机到了,我便走了

西湖·柳浪闻莺

看到这张图时,我已经到杭州了

把行李放到屋里,来不及收拾,我便跑了出去

我太期待这一刻了!

我爱西湖,爱这个有山有水的城市。在国内,没有比杭州更合适我的城市了

红色标点的万松岭路,便是我的住址

而且我住的地方特别棒,这里是万松岭路,距离西湖只有 1.4 公里

以至于,我每天都可以坐在西湖旁看书、跑步,太爽了!

房子呢,是靠山的自建房宾馆,比较老,设施也不全

不过我很满足,斯是陋室,惟吾德馨嘛

知乎答疑:个人博客内容常陷入同质化,如何找到独特风格?

2025年10月18日 19:04

写博客至今,已经快八年了

从最初的代码笔记,到后来记录生活,我想,有些话可以说说

首先,想让个人博客保持稳定的流量增长,其实是件很难的事

如果你是为了打造个人IP才写博客,那多半是在走一条弯路

技术博客这条赛道太窄,同质化严重,就像 1+1=2,当答案只有一个时,你再想打破这个“2”该有多难?

但突破差异化并非毫无空间,我建议从表达方式下手,而非内容本身

我很喜欢「云风的 BLOG」特别是他几十年如一日的写作,这样人可不多见。他写的内容并不是迎合他人,而是源自自己内心的思考,正如他的博客副标题所言:“思绪来得快去得也快,偶尔会在这里停留”

说到底,个人博客的灵魂在人,而不在技

在这个以文字为主导的地方,文字功底就是风格的根基

见字如见人,一个人如何写字,用怎样的语言去表达自己对世界的理解,往往能看出他的性格与气质

当然,我并非什么写作高手

对于我这样的文盲来说,写出一篇顺畅的文章都不容易

常常写完,还得靠 ChatGPT 帮我润色,不然标点都能错得离谱

但没关系,我觉得,写作的意义不只是被看见,更是认识自己

当你开始书写属于自己的语言的时候,我想,你就已经走在形成独立风格的路上了

最后再说一句:

风格不是你刻意去造出来的,而是你坚持写下去,慢慢留下来的痕迹


<a href="https://www.zhihu.com/question/1962140063472285501/answer/1962880004380681990" target="_blank">原文:个人博客内容常陷入同质化,如何找到独特风格? - 游钓四方的回答 - 知乎</a>

首次徒步爬山,误闯深山老林,徒手爬悬崖,两度滑落险些丧命

2025年10月15日 10:05

偶尔走点野路,也是一种出路

徒步去哪?

我准备离开义乌了,去杭州,25 号之前房租到期,在此之前再看看义乌

这座城市周边大多数山峰,只要骑车能走的,基本上都有我的车轮印

但某些自行车无法涉足的地方,我却从未去过。所以这次,我决定徒步

通过媒体了解到附近有几个徒步爬山的项目:仙华山、德胜岩、牛头山、大寒尖...

最终,我选择了大寒尖。距离我所在的地方大约四十公里,就这点距离,骑公路车都不够我热身的,但这次不一样,这是首次徒步爬山

出发前,我觉得头发太长,去万体广场理发,回家已是12:50。于是,我赶紧预约了滴滴顺风车,计划1:30点之前出发

背了一个 QUECHUA FH500 背包,容量 17 升,内含 2 升水袋,下楼跑去好想来购物,买 3 瓶怡宝、3 个面包和 1 包巧克力

结账出来,司机就到了。坐上车不久后才想起来,盐丸在家中没拿

羊印登寒山,天龙古道难

14:30 抵达 大寒尖-登山口

大寒尖位于义乌市南部的赤岸镇,海拔 925.6 米,是义乌境内第一制高点

登顶大寒尖的路线有很多种,据统计,至少有四五条,而我选择了“赤岸镇羊印村起步”的路线,是绝佳线路之一,途径天龙古寺,尚有一古道蜿蜒曲折,鲜为人知,当地人称“天龙古道”

现在有人疼了

神门

“神门”,这是道教文化中的象征性门户,代表人与天界、人与神灵之间的通道。也可以说是徒步通往山顶的门,心灵的门

写作时,我回想整个行程,没有看到关于道教的踪迹啊?倒是有一个天龙古寺,但是佛教和神门属实是王八看绿豆

兴致勃勃

太阳能长凳,支持USB和无线充电

政绩工程开始发力,太阳能长凳,支持USB和无线充电,是个好东西,不过呢,这放在登山口又有何用?放高了,领导看不见?

正道

正道,既天道。顺应天地,返照本心

鄙人在此立下Flag:

当初参与设计大寒尖羊印村路线的领导若不认同道教,我当场把这块石头吃掉

到达第一个可以遥望远处的点位

顽强的大树

这树的生命力简直太强了!放在这里警示十分合适,有意而为之?

非雨季一点水没有,还没有家里花洒降水大

还有五分钟就到了

这个物价在山上并不贵,毕竟搬运到山上需要成本

天龙山-山塘-总库容:1.6 万立方米

羊印村方向

无人售水点,大妈欢乐地

天龙古寺

寺因山得名,山因寺著称,建于明初,前身为天龙庵

据“浙江通志”记载,天龙庵内失火烧为灰烬,后又捐募重建为天龙古寺

清康熙乾隆年间香火最旺,常年有居士近百人,而今天我只见到一个人

解锁地图一份

天龙古寺-石门匾

2014年山塘重修,在塘底发现石门匾,它在这里出现也是有原因的

解放以后,官老爷搞政绩,把天龙古寺拆了扩容。1964 年开工,1972 年竣工,直到 2004 年才重建天龙古寺

误闯深山野林

天龙山方向

旱厕

至少有十年没见过旱厕了,小时候家里也有,屋外一个,屋内一个

分叉口

我最早走的是左边,半公里后看不到路,都是灌木丛就返回走了右边的路

直到从右边走出去我才知道,左边的野路也能过去

中间是根电线

这边还在修缮中,我看到了整捆的电线丢在灌木丛中,想必是专门放在这的

华南毛蕨,又名:大风寒、书子草

发现了毛蕨,长得太旺盛了,特漂亮。回家后查阅资料,原来这是华南毛蕨,又名:大风寒、书子草

它有多重价值,叶子味苦性寒,可入中药,同时也能制造氧气,市面上小棵的售价都要三十多,像图中这品相,估摸着能卖四五百元

非常干净的白跑鞋(即将变色)

蛮坚强的植物,你知道它叫什么吗?

看到这竹林,我不禁想起洛阳城外绿竹巷,想起了令狐冲和任盈盈,想起了清新普善咒。梦想有一天,我也能依山傍水,住在竹林深处,好不快活

误闯副本 “深林奇径”

三蹦子,羊印村方向(起点)

当我从灌木丛中出来,看到水泥路时,彻底打消了我在林中的所有疑虑,以为自己终于走上了“正道”

然而,这段路竟是我最后享福的时刻。紧接着,道路彻底消失,只剩下一望无际的灌木丛和几乎无法直立行走的陡峭山坡

倒了的树

手机丢了

四周都是灌木丛,丢手机之前拍的照

走到这里,我才发觉不对劲。后面的灌木丛没有那么深,可以轻松踩踏过去。更扯淡的是,我的手机马上就要丢了

处于高心率懵逼状态

穿越灌木丛后,又来到了一段上坡路,全是小溪和大石头

此时,我的腿上、鞋上爬满了小虫子,一些不知名的带刺植物贴在身上,还有蚊子在脖子边上嗡嗡作响,时不时亲我一下

由于需要上坡,我决定先把手机收起来,我穿着短裤,没有口袋,便将手机放进了背包附带的小腰包里,但手机只能竖着放进去2/3

就这样,我踩着石头,跳过几条小溪,大约 300 米后,音乐声呢?手机没电了?

低头一看腰包,手机没了!我立刻转身,扒开灌木丛寻找,又去小溪边查看

万幸,最终我在第一个小溪的石头下面找到了丢失的手机,没有掉进水里...

首次爬山,徒手爬悬崖

悬崖峭壁

左边的灌木丛就是悬崖,角度很大,望一眼深不见底。幸好通道不算太窄,足够容纳一个人通过,我担心发生意外,侧身横着走的

虽然心率很高,但这已经是登顶前最舒服的时刻了,因为接下来将开启地狱级试炼

我在休息

横穿悬崖峭壁后,我来到侧方,山路没了!他妈的灌木丛也没了!我环顾四周,脑子里只剩下两个选择:

  1. 原路返回

  2. 徒手爬悬崖

我在山脚下犹豫了一分钟,思考是否要爬上去。毕竟今天是我首次徒步爬山,没有任何攀爬经验

然而,当我爬到 1/3 处时就再也走不上去了。角度实在太大了,斜坡上满是树叶和山顶滑落的砂砾石子,特别滑,脚掌根本使不上力气!

既然脚掌无用,那就用臂力。出于求生本能,我下意识地抓住一切能抓住的东西,向上攀爬

图中的树看似相邻很近,实际上距离有 1 米,有的甚至 2 米,我用手试探性地抓住身边的石头和树根,微微用力拉扯,确认牢固后,快速向上调,再抓住大枝干

此时,腿部的力量几乎毫无作用,在没有树木依靠的情况下,我连蹲着都会滚下悬崖,可想而知角度有多大

就在这张图的下方,我还滑落两次,险些滚下悬崖。如果不是脚掌正好抵住了身后的树,我今天就不配在这里吹牛逼了

你看看图中,遥望灌木丛,遥望灌木丛,白茫茫一片,什么都没有,底下就是悬崖啊!

我在休息

我的细胳膊臂力实在不行,爬到这里已经是精疲力尽了,我休息了大约 2 分钟,汗如雨下,几乎睁不开眼睛

说实话,在这里掏出手机拍照,需要勇气

上山前,我担心出现意外,就把手机装进了背后拉链包里的背包,自然不必担心

但在这里,我必须脱下背包拿出手机,心里想着:回去必须写篇博客,吹牛逼

登顶后迷路

湿透的背包

湿透的我

WOW!WOW!WOW!

顺利登顶后,我的头脑是懵的,心率极高,加上太阳直射,热得我有点晕头转向

我脱下背包和帽子,挂在树干上,环顾四周转了一圈。我确定自己到达了一个山顶,但不知道是哪一个

当我意识到我所在的位置信息后,多少有些后怕。心想:彻底走错路了,我根本看不到生路啊!原路我根本下不去!

我随即掏出手机,看了看时间,此时已经四点多了,想想以外我想起以往的生活经验,五点左右天色就会变暗,六点天就完全黑了!

想到这,我开始焦急起来。我没有任何露营装备。我打开背包,检查水袋和瓶中的存水:两包面包、一包巧克力、水800毫升

在进入深山野林后,我喝完的瓶子一个都没扔,就怕出事,留着瓶子还有用。心想:可能真要派上用场了!

在环顾四周一圈后,实在看不到路,我不免有些心虚。我想起了“神秘园”,一位专门报道探险事故的抖音博主,像我这样的事件,他已经做过很多期了

在确认没有生路后,我拨打了119,虽然手机没有网络信号,但电话确实可以打通,这也是我这辈子第一次打 119

接通后,一个男生接了电话,直觉告诉我他年纪可能与我相仿。他问我什么事?

我说:“我在大寒尖迷路了,登顶了一个不知道叫什么山的山峰,周围看不到生路。您了解这片区域吗?我该如何寻找下山的方向?”

119 犹豫了半天没说话,回复道:“这事我们管不了,你打110。”无论我讲什么,他也只是轻描淡写地重复:“你打110。”

生路

找到生路,

挂断电话后,我不想再报警了,即便打了110,估计也是让我原地不动,等待救援

此时已接近五点,我必须尽快离开深山老林,因为我浑身湿透,天色一暗,必定会失温

我有过多次长途夜骑失温的经历,十分清楚后果是什么,所以留给我的时间已经不多了!

我首先走到最左边,到悬崖边看了看,无望后,又沿着悬崖,一直找到了最右边

下意识地抬了一下头,望向远处,我居然看到了大寒尖山顶的石台阶!低头,发现在我面前赫然是一条生路!

两山中间的一条细长野路

迷路方向

WOW!WOW!WOW!

穿过灌木丛成功来到水泥路时,我当时太他妈激动了!我还看到山下有人在台阶上行走

而我的右手边,就是最后的台阶,爬上去就是义乌的制高点,大寒尖!

迷路方向的对面,悬崖

望着登顶的台阶,我心里别提多高兴。但在进入深山野林之前,我是嗤之以鼻的

所以,当我得知自己迷路后,其实是有赌的成分,我觉得我可以闯出去,也不用走枯燥的台阶,这很有意思

其实,我本是个乖孩子,只是社会把我鞭打成了一个不安分的人

长大后,出于对传统文化的厌恶,按规则办事是我最为反感的

不走寻常路,面对各种未知的风险,会让我荷尔蒙上升,感觉自己还活着

最后的台阶

一直清理不干净的帽子

即将登顶

赶紧点火来一张

图中露出石壁的山峰,是我的来时路,更是我徒手爬山的地方,你是否还记得上章节,我侧身穿过的悬崖?

这是我在最后登顶时遇到的大哥,他体力非常好,我俩互相帮对方拍了几张照。离开山顶后,我们便分开了

下山后我把所拍视频发布在抖音,15 号下午大哥便刷到了我,缘分啊!

图中显示已行走 3.53 km。鬼知道我跑到深山野林,在地图上画了个圈,比直线距离都长

下山走台阶是最无聊的事情,只能低头,不能抬头

走乏了,我并不是累了,只是觉得下楼梯好枯燥

天龙古寺-公共卫生间

当我看到这个场景的时候,心里是什么滋味?谁能懂啊?

我明明来到了天龙古寺,还拍下了路牌和地图,但我没有仔细看,所以我根本不知道正常的路线是在房子后面...

天龙古寺-深林奇径方向

站在公共卫生间的水龙头旁,我简单冲洗了一下,把身上的脏东西擦掉。跨上背包,边下山,边干饭

面包、巧克力

傍晚时的天龙山山塘

傍晚时的山腰

傍晚时的亭子,我上山时曾在此闭目打坐五分钟,以控制高心率

这是我下山路上遇到的第一个灯,而这个灯却装在起点门口

起点石门

大寒尖-广场方向

跳舞的老年人

顺风车

下山时已是六点,天完全黑了。走到广场内,手机信号恢复满格

我打开滴滴顺风车,发布了半天都没有人接单。毕竟我在非常偏远的地方,用我常说的那句话就是:鸟不拉屎的地方

大约五分钟后,一名司机接了我的订单,通过在线消息说要 40 分钟左右才能赶到。我说我可以等,你过来吧。

等到了约定时间,司机还没来,我有些焦急,打开手机准备联系司机。卧槽!订单被取消了!

我白白等了 40 分钟!不过我发现好像是我的问题,六点以后手机自动开启了免打扰,不管是谁的电话都打不进来。通话记录显示,司机曾尝试联系我。

没办法,只能再找一辆车。我又打开滴滴,这次接单速度慢了很多,十分钟后才有人接单

在多次通话并增加了红包小费后,我等待了一个小时,司机终于到达登山口

视频素材

下山遇到天龙古寺

<video width="736" height="414" controls> <source src="3eea00c93f425185f086a60d98df8b07.mp4" type="video/mp4" /> 你的浏览器不支持 HTML5 视频播放。 </video>

徒手爬悬崖登顶后

<video width="736" height="414" controls> <source src="8dfe602755e253e2a5d113e1c328bb6a.mp4" type="video/mp4" /> 你的浏览器不支持 HTML5 视频播放。 </video>

复盘

天龙古寺,卫星地图

我是在下山后,在等车时看地图才知道,我误闯进的深山老林是难度最高的路线。在卫星地图中向左走的那条路,官方将其命名为:“深林奇径” Strava记录

夜骑:环岩口水库,遭翻车

2025年10月12日 04:51

又想去夜骑

傍晚六点,洗了个热水澡,换上骑行裤,带好装备便推车出了门

到厚富夜市吃碗八宝粥,八个蒸饺,一颗茶叶蛋,还没想好去哪儿

打开地图翻了翻,没想到北苑附近居然有依山傍水的地方“岩口水库”

照片上是平整的柏油路,非常近,不到20公里

王羲之后裔的村落

东石线

七点半,抵达东石线,整条路都没有灯光,路长大概几公里

石明堂

石明堂村,一个四面环山的小村庄

有意思的是,这里人自称是王羲之的后裔

相传王羲之长子当年从绍兴逃亡,携眷到伏龙西山定居(现在的石明堂村)

目前家族人数在村中占比95%,村礼堂还供着王羲之的画像

岩口水库故事线开始

上溪岩口湖骑行线

岩口水库是义乌最大的人工水库,当地人称小千岛湖,也是国家A级旅游景区

每逢3月23日,金华体育局都会以上溪桃花节和岩口水库为主题举办骑行赛事

我查阅了往年的赛事规则,估计不太好报名

湖边路窄,赛事限定600人,报名还不挤破头?

杨横线

这柏油路真不错,不止这一段,整个环湖几乎都是这样!

握紧车把,感受管胎在地面滑行,静音顺滑,没有一点碎石的碾压反馈!

在这样的路面骑行,就是一种享受!

不知名黑坑

穿过铜陵隧道,右拐上岭线,看到一个黑坑

我扫了一眼,有二十多人

哎,我也好久没钓鱼了,至于上次去黑坑是什么时候,我记不得了

意外翻车

上岭线

骑行到上岭线桥边,忽然感觉在进行踩踏时,大小腿施展不开

我没多想,抬头看一眼湖面,便靠边停车,点了根烟

看着车座和车把的高度差,这时才发现,车座高度不对,肯定降下去了,八成是上周螺丝没拧紧,导致颠簸下滑了

更糟糕的是,尾灯光线忽明忽暗,还便随着闪烁

我按了几下开关,没反应,得,这下尾灯怕是要没电了

车座还能凑合,可没尾灯夜骑,那是拿命骑啊!

之前,我从河南骑到上海,夜行赶路就吃过大亏,当时就是这个尾灯

白天耽误了时间,为了赶路到宾馆,我在国道骑行到凌晨,尾灯亏电

天色黑到伸手不见五指,后面半挂车时不时来个擦肩而过,我心跳声比车声还响!

更扯淡的是,尾灯是 Micro USB 接口

上海工作时,在黄浦TREK旗舰店买的,将近四百块,纯工业垃圾

等一个有缘人

岩口水库

犹豫片刻,想着

如果我继续向前骑,车座可能会再往下滑

从地图上看,进山有好几条路,我不知道里面是什么环境,一旦出问题更麻烦

先不管了,坐在桥边,又点一根,等有缘人

我知道后方有一队骑行佬在歇脚,该桥是他们的必经之地

大哥在帮我找内六角扳手

十分钟后,路过一辆摩托

我高举右手,大声喊:“大哥,有修车工具吗?我需要扳手!”

他骑出去五十米远后停下,回头望我一眼,又调头回来

从车上的工具包里掏出一个工具包,说:“你看看有没有合适的,如果没有,我箱子里还有”

内六角扳手

我接过包,倒在石板上,一眼就看到合适的内六角

拧松、上调、锁紧后,我把工具包双手递给了他

真是巧,太幸运了!

修好后,我俩坐在桥边抽烟,闲聊十分钟

就在这时,后方的骑行队也呼啸而过

骑行逐风花海间,相守誓言山海鉴

骑上车后,锁鞋和锁踏扣紧的瞬间,熟悉的感觉又回来了,太舒服了!

唯一的隐患就是尾灯,时亮时暗,像在喘气似得

由于尾灯问题,这次环湖行程只完成了约九成

翻越山路,直奔上溪镇,脑子里就俩字,回家

意外惊喜

KIPRUNKIPRUN 马拉松腰带包 5号

这个腰带包是我刚学习跑步时在郑州迪卡侬买的

原本只是跑步时装个手机和钥匙,没想到骑行的穿戴体验意外地舒服

如今骑行服的款式越来越多,有些骑行裤也设计了储物口袋

可真把东西放进去骑车时,我总觉得大腿伸展不自然

至于骑行服,天气一热就闷得慌,背后的口袋一旦装上东西,那负重感时刻贴在背上

如果像我一样单穿骑行服,先不说背后的负重感,口袋里的烟盒被汗水泡烂不知多少次了

而腰带包完全没有这些问题

至少对我来说,腰部一点负担都没有,轻松、自在

KIPRUNKIPRUN 马拉松腰带包 5号

KIPRUNKIPRUN 马拉松腰带包 5号

这腰带包不到一百块,四个互通口袋加一个封闭袋

KIPRUNKIPRUN 马拉松腰带包 5号

大口袋不漏边,可塞进7英寸的手机

但大口袋是和其他口袋互通,如果物品过小,在运动中,物品可能会移位

KIPRUNKIPRUN 马拉松腰带包 5号

小口袋只能装进6英寸,其为封闭空间

这款腰包也有缺点,如果跑步时放了手机,受惯性影响,下方会来回摆动,松紧带要是系得不紧,就容易往下滑

国庆最后一天:倍鱼线二进宫

2025年10月9日 03:09

​二爬倍鱼线,别样体验。与往日不同,这次选择了走义武公路进山。进山前正值烈日当空,仿佛整个人被塞进了蒸炉。

要么说光膀子就是自在,我实在不喜欢衣服黏在身上的那种感觉。哪怕是超薄的速干背心,也比不过赤膊的畅快。至于雅观,这不是我该考虑的问题,国庆骑车进景区,逛古镇庙会都是光膀子。

摇车进山后,树影与山峰交织成天然的屏障,感受不到一点热,只剩下逆向的风和时不时的鸟叫声。

临近傍晚,山里的蚊子开始活跃起来,我身上散发出的热气,引来成群的蚊子,边骑边拍,但凡时速慢一点,就要被强奸。

义乌公路,即将进山

柏峰水库拍留念

柏峰水库

半坑附近

半坑附近

不知道谁的房子,我想不是用来住的吧

倍鱼线 19

倍鱼线旁的小溪

口非常多,一分钟一个,大小和小拇指差不多

路线图,该标识在胡公殿丁字路口

一口气憋到山顶,停下一秒都是不可能的

出发前买的士力架,已化

准备下山了

这种照片不是我拍的,把手机卡在自行车上误触了

阳光直射,拍不清

另一个方向

在这个位置蹲了五分钟,没见车经过,自拍一张走了

五菱下山了

临近傍晚了

这张照片和上面的一张照片是在同一个位置,不同的角度拍的

文字大军已经出动了

柏峰水库晚上很漂亮,跑道也很好,就是蚊子太猖狂

在厚富夜市吃了饭回家

集成 LightGallery 灯箱

2025年9月18日 19:33

月初,一个博友留言反馈讲到文章页面图片看不清楚,今下午花点时间,收拾了一下

LightGallery 这款灯箱我老博客用过,后来换到 Astro 就搁置了

<figure class={`img-container ${className || ''}`} style={style}>
  <div class="img-wrapper lightgallery-wrapper">
    <!-- EXIF -->
    {shouldShowExif && (
      <div class="exif-tooltip" data-exif-tooltip>
        <div class="exif-content">
          <div class="exif-loading">Loading EXIF data...</div>
        </div>
      </div>
    )}
    
    <!-- LightGallery -->
    <a href={finalSrc} 
       data-src={finalSrc} 
       data-lg-size="1600-2400" 
       data-sub-html={alt}
       class="lightgallery-link">
      ![ ]()
    </a>
    
    <!-- 标签 -->
    {caption && (
      <figcaption class={`img-caption caption-${caption === 'long' ? 'bar' : 'tag'}`}>
        <span class="caption-text">{alt}</span>
      </figcaption>
    )}
  </div>
</figure>

<script is:inline>
function initLightGallery() {
  // 全局初始化
  const galleryContainer = document.body;
  
  // 销毁
  if (galleryContainer.lightGalleryInstance) {
    galleryContainer.lightGalleryInstance.destroy();
  }
  
  galleryContainer.lightGalleryInstance = window.lightGallery(galleryContainer, {
    selector: '.lightgallery-wrapper > a',
    subHtmlSelectorRelative: true,
    mousewheel: true,
    download: false,
  });
}

document.addEventListener('DOMContentLoaded', initLightGallery);
document.addEventListener('astro:page-load', initLightGallery);
</script>

Canon EOS R7 RF100-400mm F5.6-8 IS USM 400.0mm · ƒ/8.0 · 1/200s · ISO 800 © Michael Kleinsasser

经过这段时间的调教,博客插入图片较为人性化了,只需要输入文件名,它会自动处理路径

  • 图片统一路径:cos + slug + name
![Canon EOS R7 RF100-400mm F5.6-8 IS USM 400.0mm · ƒ/8.0 · 1/200s · ISO 800 © Michael Kleinsasser](moon-8579189.jpg)

感觉还有进一步优化的空间,如果抛弃键值对,直接按照顺序传参,想必最简洁不过了

  • 图片 URL
  • 图片描述
  • EXIF(Boolean)
  • 标签(Boolean)
<Img 
  "moon-8579189.jpg"
  "Canon EOS R7 RF100-400mm F5.6-8 IS USM 400.0mm · ƒ/8.0 · 1/200s · ISO 800 © Michael Kleinsasser"
  "false"
  "false"
>

有时候想想也是,看似隐式优于显式的想法,实际上和过去接手的屎山一样

可读性极差,旁人一眼望去,完全不知道"false" "false"代表啥

说到底,屎山并不是一行行代码堆出来的,而是懒省事养成的

help

EasyFill v1.3 发布:支持SPA机制、优化 Shadow DOM 遍历逻辑

2025年9月18日 08:44

本次更新(commit: 7764aa3) 对 content.ts 文件进行了部分重构,详情如下:

  • 重构 Shadow DOM 遍历逻辑,使用 WeakSet 避免重复遍历
  • 新增页面变化检测机制,完美支持 PJAX/AJAX/SPA 等现代页面刷新方式
  • 新增两段填充:DOM 加载前后,提升容错

Shadow DOM 遍历优化

const processedShadowRoots = new WeakSet<ShadowRoot>();

function traverseShadowDOM(root: Document | ShadowRoot | Element) {
  // 如果 ShadowRoot 已经处理过,跳过
  if (root instanceof ShadowRoot && processedShadowRoots.has(root)) {
    return;
  }
  
  // 标记已处理的 ShadowRoot
  if (root instanceof ShadowRoot) {
    processedShadowRoots.add(root);
  }
}

页面变化检测机制

function setupAdvancedPageChangeDetection() {
  // 监听浏览器前进后退
  window.addEventListener('popstate', () => {
    logger.info('检测到 popstate 事件');
    handlePageChange();
  });

  // 监听 PJAX、AJAX
  const originalPushState = history.pushState;
  const originalReplaceState = history.replaceState;

  history.pushState = function(...args) {
    originalPushState.apply(history, args);
    logger.info('检测到 pushState 事件');
    setTimeout(() => handlePageChange(), 100);
  };

  // 监听 hashchange 事件
  window.addEventListener('hashchange', () => {
    logger.info('检测到 hashchange 事件');
    handlePageChange();
  });

  // DOM 变化检测
  const observer = new MutationObserver((mutations) => {
    if (fillState.isAutoFillStopped && !fillState.pageChangeDetected) {
      let significantChanges = 0;
      
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          // 检查大量节点变化
          if (mutation.addedNodes.length > 5 || mutation.removedNodes.length > 5) {
            significantChanges++;
          }
          
          // 检查结构性变化
          mutation.addedNodes.forEach((node) => {
            if (node instanceof Element) {
              if (node.matches('form, input, textarea') || 
                  node.querySelector('form, input, textarea')) {
                significantChanges += 2;
              }
              
              if (node.matches('main, article, section, .content, #content, .main, #main')) {
                significantChanges += 3;
              }
            }
          });
        }
      });
      
      // 判断是否为页面内容更新
      if (significantChanges >= 3) {
        logger.info(`检测到DOM变化 (${significantChanges}个变化点)`);
        
        if (detectPageChange()) {
          fillState.pageChangeDetected = true;
          handlePageChange();
        }
      }
    }
  });

  setInterval(() => {
    if (detectPageChange()) {
      handlePageChange();
    }
  }, 2000);
}

两段填充

async function executeFirstFill() {
  if (fillState.isFirstFillCompleted) {
    return;
  }
  
  await performFill('首次');
  fillState.isFirstFillCompleted = true;
  logger.info('首次填充已完成');
}

async function executeSecondFill() {
  if (fillState.isSecondFillCompleted) {
    return;
  }
  
  await performFill('第二次');
  fillState.isSecondFillCompleted = true;
  fillState.isAutoFillStopped = true;
  logger.info('第二次填充已完成,自动填充已停止');
}

立即体验

EasyFill v1.3 已正式发布:

安装方式也很简单:下载后解压到文件夹,进入 Chrome → 管理扩展程序 → 加载未打包的扩展,选择解压目录即可。其他基于 Chromium 内核的浏览器同样适用。

EasyFill - 简易填充,让每一次评论更自然,与你的博友互动无缝连接

EasyFill v1.2 发布:黑名单功能上线

2025年9月16日 11:21

距离上一次更新已经有一段时间了,最近抽空折腾了一下,在 EasyFill v1.2 中带来了一些实用的新特性,特别是 黑名单功能

本次更新亮点

  • 防抖机制:避免频繁触发填充操作,更加顺滑稳定。
  • 黑名单设置:支持官方规则和用户自定义两种模式。
  • 自定义黑名单管理:支持添加、删除、拖拽上传和导出,操作更直观。
  • 数据同步优化:无论手动还是自动更新,都会跳过缓存,确保数据时效性。
  • 隐私政策更新:新增黑名单数据处理条款,解释用途。

隐私条款部分其实没啥复杂的,只是做了明确说明:插件仅有两个外部公开文件链接,其余所有数据都采用 本地加密存储(chrome.storage.local),没有隐私泄露的风险。

立即体验

EasyFill v1.2 已正式发布:

安装方式也很简单:下载后解压到文件夹,进入 Chrome → 管理扩展程序 → 加载未打包的扩展,选择解压目录即可。其他基于 Chromium 内核的浏览器同样适用。


EasyFill - 简易填充,让每一次评论更自然,与你的博友互动无缝连接

何为享受

2025年8月30日 17:05

在初入职场时,我以为权力带来的快感才是享受

在挣到小钱时,我以为挥霍物质才是享受

在野外骑行时,我以为长途跋涉、纵身山川才是享受

后来,我沉迷于古典乐,才感受到,人生真正的享受,不过是此刻心中的平静

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=298 height=52 src="//music.163.com/outchain/player?type=2&id=2156764039&auto=1&height=32"></iframe>

热成铁板烧,又被暴雨淋成落汤鸡:西湖,我来了!

2025年7月19日 20:06

从义乌出发去杭州

上次骑行是在6月24日,转眼半个月过去了,忍不住又开始怀念行驶在路上的感觉。

7月17日 清晨,一时心血来潮,换上骑行服,简单带了件便装,便推车下楼。目的地:杭州。

除骑行服外,只带了一件便装

还是这件迪卡侬背包,没有负重感,背着很轻松,很舒服。

我夏天骑行很少穿短袖,出汗量也大,基本上能不穿就不穿。

准备出发

抵达诸暨,约 50 公里。停下来抽根烟歇歇脚。

天气太热,昨日天气预报 42℃,我感觉有五十几度。你随便热吧,爷们风雨一肩挑。

绍兴,诸暨市,兴都路,公交站 找个开空调的饭店吃饭很难,大部分小店都舍不得开。

为了下午更恶劣的天气,强行灌了一口藿香正气水,这味道绝了,苦涩辣,难以下咽。

绍兴,诸暨市,应镇南路,面馆

下午更加闷热了,让我头疼无力,感觉在中暑边缘,幸好沿途加油站不少,此时已喝掉两升水,一包盐丸。

杭州,萧山区,X120 桥戴线,中石化加油站

抵达复兴大桥很是激动,上一次在桥上骑行,还是 2021 年,那时还骑着我的 XTC800。

杭州,滨江区,复兴大桥

万松岭路落脚了,我已彻底湿透。身上一层汗、一层盐,一层雨水。真的太绝了!要知道,杭州已经连续多日滴雨未下,可就在我刚从复兴大桥下来,露出头的刹那,狂风暴雨!雨水如同密集的子弹般批头盖脸地往我脸上砸,就好像在说:“你咋又来了,你咋又来了?”

  • 骑行距离:120km,时间:5h 26m,爬升:410m

杭州,上城区,万松岭路

我的大腿

妈妈泡的茶叶

简单休息后,喝了杯妈妈泡的茶,躺下睡了一会儿。傍晚八点半醒来,直奔西湖

杭州西湖风景名胜区,柳浪闻莺,翠光亭

杭州西湖风景名胜区,钱王祠入口

杭州西湖风景名胜区,钱王祠外

在杭州市区内,最美丽的行驶路段莫过于南山路,这里是法国梧桐树密集栽种区,树龄平均在70年左右。有些枝干伸进居民楼,连玻璃都撞碎了。

杭州,上城区,南山路

晚上和妈妈去了老地方吃羊蹄,这也是我来杭州的目的之一,自出了老家,我只认这一家。

杭州,上城区,西湖大道,老地方

微雨飞来峰

上午九点,和妈妈打车去了飞来峰,天飘着细雨,院内外,人挤人。

杭州西湖风景名胜区,飞来峰

枫叶做成的龙雕像,我妈说我属相大龙,非拉我拍照。

飞来峰,入口处“叶龙”

飞来峰,水月观音坐像

有些导游群体,到了景区,真成了路障了?导游后面站了几十个人看,干啥呢?排队早高峰呢?魂都跟着导游跑到九霄云外了,导致宽敞的道路被围得水泄不通。

飞来峰,一线天

飞来峰,造像

飞来峰,观音坐像

飞来峰,释迦佛与释迦如来

飞来峰,小溪流

飞来峰,小溪流

中午和妈妈来到公司吃饭——啫卤爷,就在杭州美院对面。这是西湖春天旗下的品牌,偏中低端、年轻化,以烧烤为主。

广东的博友可能听说过,例如:江南厨子、江南味道、西湖春天、水岸十里等等,均以杭帮菜、粤菜,海鲜为主。

杭州,上城区,南山路103-2号,啫卤爷

啫卤爷二楼靠窗位置

这羊肉串我可想太久了!自第一次吃这羊肉串,去哪都找不到这个味,实际上这一串很大,我拍照不行。

啫卤爷,羊肉串

也是非常喜欢的一道菜,不过我比较怕这玩意,在上海做采购几年从来没摸过。

啫卤爷,啫啫麻辣田鸡

炒饭中的最爱,没有之一。啫卤爷是没有这道菜的,还是从隔壁店西湖春天下单送过来的。

西湖春天,菠萝炒饭

乳山生蚝个个七八两,肉质饱满,非常新鲜。作为公司曾经的海鲜采购,再熟悉不过这挑剔的原料标准。

一条东星斑,在规格上,哪怕只重了一两也不行,身上哪怕有一点点影响美观的小瑕疵也不行。就连鲍鱼、花螺这些小海鲜,也得一个个用手仔细称重、挑选,只为确保每一道菜上桌时,食材的大小都能均匀划一。

所以,在西湖春天旗的门店用餐,你几乎不用考虑卫生问题。

这公司的供应链还是比较成熟的,对于原料的源头有些较真。许多原料都是从基地直采,就一个百合,经理能坐飞机跑甘肃,直接到田间地头和农民沟通,我也是很佩服。

更扯淡的是,河南郑州的分店。他们连一根葱、一把香菜都要下单到采购部,然后杭州或深圳采购部打包后,用飞机空运过去。确实,不是一个产地的东西,那味就是不一样。

啫卤爷,乳山生蚝

啫卤爷 菜品

暴雨再来,船游西湖

午睡两个小时,醒来时外头下起了大雨。

杭州,上城区,万松岭路,暴雨中

雨稍小时,和妈妈来到长桥散步,吹着风,很凉快。

杭州西湖风景名胜区,长桥公园

杭州西湖风景名胜区,学士公园

杭州西湖风景名胜区,学士公园

西湖区的绿化非常棒,这样大的树,通常只有在森林公园才会有,而西湖区遍地都是,这也是我喜欢这个城市的原因之一。

杭州西湖风景名胜区,学士公园

雨后的西湖太美了,如水墨丹青,坐在游船上看水面泛起涟漪,若再来一根路亚竿,啊,人生不过如此啊!

西湖,在船上

西湖,雨后晴时

刚做十分钟,又开始暴雨,真会挑时候,下得好。

西湖,暴雨中

中间的岛我也没去过,不知道有些什么?

西湖,三潭印月

这时候刚六点出头,雷峰塔不到开灯时间,可惜了。

杭州西湖风景名胜区,长桥公园

烟雨柳浪,落座外婆家

坐船上约半小时,下船后继续沿着西湖漫步不知不觉就来到了外婆家(西湖天地店)。这家门店的位置实在绝了,面朝西湖、背靠绿荫。唯一的缺点,椅子太低,坐下就像在躺着吃饭。不过也不能要求太多,人均在这放着。

杭州西湖风景名胜区,外婆家(西湖天地店)

杭州,上城区,南山路,唐云艺术馆

傍晚时分的西湖美极了,没有正午的烈日,也少了游客的喧哗,最适合独坐柳浪闻莺的湖边长椅上,手边放本书,任风吹书页、吹到哪页读哪页。

杭州西湖风景名胜区,长桥公园

杭州西湖风景名胜区,长桥公园

返程回义乌

杭州的旅程告一段落了,不打算骑行回去,我订好了高铁票。

去高铁站之前,我心里没有底,我只在上海虹桥拿过自行车。不知道杭州会不会卡的严。

如果托运的话,需要专业的硬壳自行车包,先不说一个便宜的硬壳自行车包都要几千块。

前后轮、飞轮、包括车把都要拆。这拆卸、组装从来都不是一个小工程。

杭州,上城区,万松岭路,货拉拉

顺利通过安检,检票员一句话都没说,她甚至都不愿意看我的车一眼。想必江浙沪的骑友已经实践无数次了,而且江浙沪是爬坡竞赛的大省,我爱这里。

老家河南:谁管你规定1.6米限宽,自行车就是不让上,拆了也是自行车,不让上。

杭州,上城区,杭州站,6号候车厅

非常完美,下次去义乌站试试,如果拆了前轮可以上高铁,那么这条路线就打通了。

杭州,上城区,杭州站,G491,8号车厢

最后,上视频,以此纪念这段时光:

<video width="736" height="414" controls> <source src="487837dbd622415b42902.mp4" type="video/mp4" /> 你的浏览器不支持 HTML5 视频播放。 </video>

这个 RSS 有意思

2025年7月10日 02:26

晚上逛 #UNTAG 发现一个有意思的项目 —— RSS.Beauty

一款开源 RSS 美化工具。支持 RSS 2.0 和 Atom 1.0 格式,可将原始订阅源转换加以渲染。

此外,根据项目文档,作者还提供了四种食用方法,包括:

  • 在线美化
  • 本地 XSL 样式
  • Base64 内嵌样式
  • Docker / Node.js 部署

一、在线美化 RSS

只需提供 RSS 地址,即可通过服务端转换美化:

https://rss.lhasa.icu/rss?url=https://lhasa.icu/rss.xml

RSS

二、食用 XSL 模板

  • XSL 文件必须与 RSS 源在同一域名下,否则会有跨域限制。

下载模板:

# RSS 2.0
wget https://rss.lhasa.icu/rss.xsl

# Atom 1.0
wget https://rss.lhasa.icu/atom.xsl

在 RSS 文件头部添加如下声明:

<?xml-stylesheet href="/style/rss.xsl" type="text/xsl"?>

三、Base64 编码

适合不想托管样式文件,可以将样式直接内嵌进 RSS 文件中

步骤如下:

  1. 访问:https://rss.lhasa.icu
  2. 切换到 “Base64” 选项卡
  3. 根据 RSS / Atom 格式选择对应样式
  4. 将复制的代码插入 RSS 文件中,例如:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="data:text/xsl;base64,PD94bWw..." type="text/xsl"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">

四、Docker 部署

扒拉下来,启动:

docker pull ghcr.io/ccbikai/rss.beauty:main
docker run -d --name rss-beauty -p 4321:4321 ghcr.io/ccbikai/rss.beauty:main

Nginx 反代:

# /etc/nginx/conf.d/rss.conf
server {
  listen 80;
  server_name rss.lhasa.icu;
  location / {
      proxy_pass         http://127.0.0.1:4321;
      proxy_http_version 1.1;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header   X-Forwarded-Proto $scheme;
  }
}

# 重载 Nginx
sudo nginx -t && sudo nginx -s reload

Node.js 部署就不赘述了,天天接触,不新鲜。

我小改了一下配色,不过目前还不支持输出文章配图和全文。等明天下班再来折腾吧。

我去看看

RSS

相关链接:

项目主页:RSS.Beauty

官方部署指南

生日骑遇“死亡摇摆”,差点成忌日骑

2025年6月25日 20:53

农历五月二十九,二十五岁生日到了

一边上班,一边创业,这样的活法让我骑车的时间越来越少。这不是我想要的状态,但为了三十岁之前出发环球骑行的梦想,我没有办法。

前几日申请了调休,可算睡个安稳觉。早上睡到十点钟,吃完午饭,我觉得今天不能再碰电脑了,我现在很想出去骑车。可只剩下一个下午的时间了,太远去不了,近的没意思。打开高德地图扒拉半天,去横店。

横店十里街 · 江南大桥方向

江南一镇(我觉得一般)

横店影视城 · 明清宫苑景区外

横店影视城 · 明清宫苑景区外

横店 · 明清西路

在我的印象中,横店是拍电影的地方,但我实际到了地方,感觉哪都没有意思,啥玩意?一个适合骑车的地方都没有,不如来时的路好玩。

东阳市 · S217

实在没意思,已经在回去的途中了。

距离:80.43km | 爬升:533m | 时间:4h 28m

值得一提的是,骑行这些年,我第一次遇到“死亡摇摆”

在217省道放坡,点刹50码。行程到一半遭遇强逆风 + 侧风(大货车快速行驶向右推来的气流)

刚开始车把只有轻微晃动,我就轻微点刹减点速。

但没想到,几秒之后,车把开始剧烈左右甩动,大约持续十秒左右,幸好当时没慌,硬是拿捏住了!人和车都没摔!

回到家洗澡时还是懵的,不知道当时怎么解决的问题。如果当时没解决死亡摇摆:

向左摔,身后高速行驶的下坡车辆根本反应不过来,会直接从我身上碾压过去

向右倒,四五十码栽进排水沟?我估计也不会太好受。想想有些后怕,真刺激!

高速行驶下的死亡摇摆案例

高速行驶下的死亡摇摆案例

雨骑枫赤线

2025年6月25日 20:53

近况

早上睡到九点钟,很困,睡不醒。直觉告诉我,我现在有种嗜睡的状态。实际上我这段时间都没有睡醒过,因为我时常编码到凌晨三四点,甚至通宵整晚,到了点直接去上班,也是常有的事。

傍晚下了班吃完饭,出去跑跑步,回到家已经十点钟了,直接脱光站在花洒下,任凭水滴打着脸,我很享受这种感觉,这让我觉得,只有现在的我是清醒的,回头望这一天,怎么想也没有觉得什么事是有意义的,想做的事总赶不上时间,原来人的精力和时间都是有限的。

我这个人对时间不敏感,可以说我是固执吧,我想,我很多时间我都花费在这性格上,但若是放在编码上也颇有钻研精神,为爱发电,成败与否不重要了,值了。

总的来说我这人很怪,我不知道该归类到哪种类型,大概可以用那句话来形容:“见人说人话,见鬼说鬼话。”

这两年,我的心态变了很多,也不知道从什么时候养成了一种无所谓的态度,大概是真的真躺平了,之前那种中年危机感也消失了,不再内耗了。

说到中年危机,比我年长的朋友总笑,毕竟我才二十四岁,距离二十五还有一个多月,但事实就是那样。

周五

失业第二天,暂时也不想找工作,只想出去走走,手里米不多,紧着过。我是一个比较容易满足的人,吃穿不讲究,哪怕吃口馒头住桥洞呢,毕竟也不是第一次了。

但这精神的苦,是真快忍不住了。来义乌二十天有余,就没有出过几次车,总想着跑步发泄一下吧,时间重要,骑车耽误时间,去你妈的时间。

G527 · 佛堂大道

这次出行,我多带了一件短袖骑行服外套,一是暴雨天气可以作为贴身保暖,二为了烟盒存放,我可不想背着包爬盘山公路。

刚骑两公里,浑身黏黏的,实在难受,外套直接脱了挂在 ASSOS 的背带裤上,像往常一样,上半身只穿一件 MBO 白色背心。

S218 · 赤岸大道

长时间不骑车,体力确实跟不上。而且我这个双腿隐约有些疼,一个月了未见好转,就因为那个半马,真让博友说中了。

S218 · 义武公路 · 永康方向

通过高德得知,到了这个丁字路口,就算正式进入盘山公路的入口了,我也是第一次来骑枫赤线,随便搜的一个地方

义乌公路,丁字路口,即将到达山脚下

中间的湖,属于饮用水源一级保护区

此刻,我想钓鱼

湖叫啥名我不清楚,只记得每隔几百米都有牌子竖立写着:“饮用水源地一级”

我停下车,往下望,看到一个浅水滩,岸边聚着一群大约四五公斤的锦鲤。

这些地方通常不缺鱼,缺的是饲料。还记得钓友口口相传的段子:

  • 无标识 = 空军警告

  • 禁止垂钓 = 有鱼,但不多

  • 严禁钓鱼,违者后果自负 = 鱼多,但掉水里我们不管

  • 禁止垂钓,违者罚款 500 = 鱼多,钓费 500

  • 饮用水源,禁止垂钓 = 鱼多,本地人可以钓

去的时候没看到有人钓鱼,返程时在山顶往下看,发现一个打路亚的,那位置绝了,不是本地人,你真不知道从哪下去。

浅水滩的锦鲤在觅食

照片里面的这段盘山公路是整条线的精髓,骑起来很舒服,可惜太短,还不够爽。

枫赤线上面的盘山公路

顶着细雨骑了一路,最近熬夜太多,精神状态很差。

黑眼圈很重

突然暴雨,手机被雨砸的页面乱跳,镜头根本擦不干净,浑身湿透,很爽。

手机和我都遭受到了枫赤线的洗礼

中场休息

骑行到达枫赤线终点,终点见到人家了,总算有百货店了,这一条街上只有两家店,另一家进去除了啤酒和水啥都没有,相比之下,这一家好多了。

其实,我主要是借东西来充电的,吃饭是次要的。不吃饭我可以骑回去,但是没有手机,我不知道可以骑哪去。

吃到东西了,还可以充电,太幸福了

店面是个八零后阿姨开的,她给我泡了一碗面,边吃边聊,就这样过了一个多小时。

她家的位置真的很顶,可谓是:“采菊东篱下,悠然见南山”

庭院朝阳,面向群山,左边的庭院养了一池子锦鲤,右边的小庭院种满了花,

铁栏杆外的杆子上挂满了月季,吃着聊着,还给我传授了一些栽培经验,蛮有意思的。

阿姨种的花

暴雨骤停,我也停了下来,望着群山大喊,太美了!

暴雨骤停

不过,我现在写着博客,看着这些图片总觉得差了点什么,腾讯云的压缩太狠了

合影留念

合影留念

盘山公路旁的小瀑布

这时已经下山了,即将离开枫赤线。

此时此刻,山脚下

回家

义乌市区方向

爬升:1376m · 距离:101.06 Km · 时间:5h 32m

义乌这座宝藏小城市,蛮不错的。

绿水青山环绕,群山绵延,把整个城市都包裹了起来。像今天的枫赤线,我不知道义乌还有多少个这样的路线,太期待了!

还有一个特别加分的点!路上的车不多。之前下班六点出去骑车,非机动车道人车都很少,相反,我去公园跑步人挤人。

这一点很重要,是我目前去过的城市中,单飞的感受最好的,当然,我最爱的还是是宁波和舟山。

独立开发者创业了

2025年6月12日 13:07

我们生命中最美好的时刻,并非是那些接受给予、放松享受的时刻,而是那些为了完成一件困难而有价值的事情,自愿将身心发挥到极限的时刻。

像咱这种人,我觉得只有一种死法 —— 猝死。

从6月10日早上九点,一直干到6月11日下午一点半,眼睛都没怎么合过。足足三十多个小时,一口气用Go,把整个企业级的RESTful API给撸完了!

这可不是简单的CtrlCV,为了支持硬件,软件下了点功夫:MySQL做了主从复制架构 + Redis缓存 + 负载均衡。支持 Docker 一键部署。还没做压力测试,自我感觉并发二三十万如喝水。

上午测完接口,兴奋的饭都顾不上吃,更别说休息了。立马开始写文档。接口文档、数据库文档...

先用Cursor生成基本文档,再改改。每份大概3000字800行左右,足足写了八份。

还有商业计划书的思维导图,2M的大小,不放大到190%字体都看不清楚,内容密度可想而知。

真的要特别感谢 Cursor,这AI工具真是赶上了好时代!要是没有它,就凭这些工作量,外包团队没一个月起步都别想搞定。

不敢想象,我竟然在三十多个小时里,就搞定了这么多事,而且落地质量高!

接下来,还剩微信小程序对接接口,以及和硬件的联调。先给这些小卡拉米放放假,我要好好休息一个下午。

此刻,精力充沛去吃个早饭骑骑车。

6.12 注册公司:

实际上,我做的这些事情有没有公司都能推进,但没办法,就为了微信小程序后续的企业认证需求.

在浙江线上办理,全程可以不出门。

項目 內容
企业名称 浪泳科技(义乌)有限公司
自主申报预选号 [2025]******
拟登记机关 义乌市市场监督管理局
住所所在地 ******
生产经营地 ******
法定代表人 ******
从业人数 2
联系电话 186****7426
邮政编码 322000
注册资本 0.001万元
企业类型 有限责任公司(自然人独资)
行业 (6513) 应用软件开发
是否一照多址
经营范围 写不下

被驳回

提交申请还不到俩小时,我就收到了驳回通知:

您申报的“浪泳科技(义乌)有限公司”设立登记申请,预审未通过,请补齐材料后再次申报。

    1. 注册资本显著过低,不符实际,请调整;
    1. 身份证照片裁剪旋转摆正上传, 要求照片只保留身份证、其余无关背景裁去并放正上传;
    1. 部门产权信息核验不通过,需要提供产权证或是前往地名办办理登记,若房屋登记用途为住宅的,仅能从事电子商务、计算机数据处理、软件和信息服务、网络技术、文化创意、动漫游戏开发、翻译服务、工业设计,审核人将在经营范围最后添加以上销售仅限网上销售,咨询电话:0579-85232920/85117280/85518797/85518798(业务咨询电话:0579-85232920)

注册资本一元行不通,调整到了一万

经营范围调整为:

  • 软件开发;信息系统集成服务;网络与信息安全软件开发;人工智能基础软件开发;数字技术服务;网络技术服务;软件外包服务;信息技术咨询服务;数字内容制作服务(不含出版发行);数据处理和存储支持服务;互联网销售(除销售需要许可的商品);软件销售(除依法须经批准的项目外,凭营业执照依法自主开展经营活动)。

EasyFill v1.1.1 发布:全面提升用户体验

2025年6月2日 23:27

版本:v1.1.1

经过两个月的偷懒,EasyFill 迎来了 v1.1.1 版本的重大更新。这次更新主要在匹配算法上进行大幅度优化,全面提升匹配效率

更新概览

  • 支持动态 Shadow DOM 和三种全新识别方式
  • 可自定义数据源,智能缓存机制
  • Markdown 文本异步并行加载
  • 自适应三级别日志,支持控制台调试
  • 更新隐私政策

全新识别方式

1. 动态 Shadow DOM 支持

我发现有些评论系统通过 Shadow DOM 来实现封装,导致 v1.0 版本无法识别 Shadow DOM 生成的表单。在 v1.1.1 中,EasyFill 新增了对动态创建的 Shadow DOM 的完整支持。

function traverseShadowDOM(root: Document | ShadowRoot | Element) {
  const inputs = root.querySelectorAll('input, textarea');
  elements.push(...Array.from(inputs));
  
  const allElements = root.querySelectorAll('*');
  allElements.forEach(element => {
    if (element.shadowRoot) {
      logger.info('发现 Shadow DOM,正在遍历', { 
        tagName: element.tagName, 
        shadowRootMode: element.shadowRoot.mode 
      });
      traverseShadowDOM(element.shadowRoot);
    }
  });
}

2. 三种识别方式全覆盖

在 v1.0 版本,EasyFill 只支持 name 字段识别。为了更加准确的匹配字段,引入了全新三种字段识别方式,确保在各种稀奇古怪的表单都可以识别:

Placeholder 识别

通过分析输入框的 placeholder 属性来识别字段类型:

<input placeholder="请输入您的姓名" />
<input placeholder="邮箱地址" />
<input placeholder="个人网站" />

Type 识别

基于 HTML5 标准的 type 属性进行智能识别:

<input type="email" />
<input type="url" />
<input type="text" name="username" />

ID 识别

通过元素的 id 属性进行精确匹配:

<input id="author" />
<input id="email" />
<input id="website" />

匹配策略:

inputs.forEach((input) => {
  const typeAttr = (input.getAttribute("type") || "").toLowerCase();
  const nameAttr = (input.getAttribute("name") || "").toLowerCase();
  const idAttr = (input.getAttribute("id") || "").toLowerCase();
  let valueToSet = ""; // 要填充的值
  let matchedBy = "";  // 匹配方式(id, name, type)
  let fieldType = "";  // 字段类型(name, email, url)

  // 匹配 URL 字段
  if (keywordSets.url.has(nameAttr) || keywordSets.url.has(`#${idAttr}`)) {
    valueToSet = url;
    matchedBy = keywordSets.url.has(`#${idAttr}`) ? "id" : "name";
    fieldType = "url";
  } else if (typeAttr === "url" && url) {
    valueToSet = url;
    matchedBy = "type";
    fieldType = "url";
  }

  // 匹配 Email 字段
  else if (keywordSets.email.has(nameAttr) || keywordSets.email.has(`#${idAttr}`)) {
    valueToSet = email;
    matchedBy = keywordSets.email.has(`#${idAttr}`) ? "id" : "name";
    fieldType = "email";
  } else if (typeAttr === "email" && email) {
    valueToSet = email;
    matchedBy = "type";
    fieldType = "email";
  }

  // 匹配 Name 字段
  else if ((keywordSets.name.has(nameAttr) || keywordSets.name.has(`#${idAttr}`)) && name) {
    valueToSet = name;
    matchedBy = keywordSets.name.has(`#${idAttr}`) ? "id" : "name";
    fieldType = "name";
  }

  // 没有匹配上就跳过
  if (!valueToSet) return;

  // 设置值并触发事件
  (input as HTMLInputElement).value = valueToSet;
  input.dispatchEvent(new Event('input', { bubbles: true }));
  input.dispatchEvent(new Event('change', { bubbles: true }));

  // 记录日志
  logger.info('填充表单字段', {
    name: nameAttr || "",
    id: idAttr || "",
    type: typeAttr || "",
    matchedBy,
    valueToSet,
    inShadowDOM: isInShadowDOM(input)
  });
});


数据同步

1. 自定义数据源功能

v1.1.1 版本允许用户完全自定义关键字数据源。

该源来自我的腾讯云 COS,且由腾讯云境内 CDN 加速,基本上无延迟:

https://cos.lhasa.icu/EasyFill/keywords.json

自定义数据源格式示例:

{
  "name": ["name", "author", "username", "昵称", "姓名"],
  "email": ["email", "mail", "邮箱", "电子邮件"],
  "url": ["url", "website", "blog", "网站", "博客"]
}

2. 缓存机制

实现了基于 HTTP 标准的智能缓存系统,大幅减少不必要的网络请求:

  • 304 Not Modified 响应处理
  • 自动缓存有效期管理(24小时)
  • 网络失败时自动回退到缓存
  • 支持强制刷新机制

ETag 和 Last-Modified 支持:

if (etag && !forceSync) {
  headers['If-None-Match'] = etag;
}
if (lastModified && !forceSync) {
  headers['If-Modified-Since'] = lastModified;
}

性能优化

1. localStorage 持久化存储

实现 Markdown 内容的持久化机制:

const fetchMarkdown = async (url: string) => {
  try {
    // 检查 localStorage 是否已有缓存
    const cachedMarkdown = localStorage.getItem(url);
    if (cachedMarkdown) {
      logger.info(`从缓存加载 Markdown 文件: ${url}`);
      return cachedMarkdown;
    }

    // 如果没有缓存,从网络加载
    const response = await fetch(url);
    const markdown = await response.text();

    // 将加载的内容存入 localStorage
    localStorage.setItem(url, markdown);
    return marked(markdown);
  } catch (error) {
    logger.error(`加载 Markdown 文件失败: ${url}`, error);
  }
};

2. 异步并行加载优化

实现 Markdown 内容的异步并行加载:

const loadContent = async () => {
  const [aboutAuthor, recommendedPlugins, updateLog, privacyPolicy] = await Promise.all([
    fetchMarkdown('/markdowns/about-author.md'),
    fetchMarkdown('/markdowns/recommended-plugins.md'),
    fetchMarkdown('/markdowns/UpdateLog.md'),
    fetchMarkdown('/markdowns/privacy-policy.md'),
  ]);

  setAboutAuthorContent(aboutAuthor);
  setRecommendedPluginsContent(recommendedPlugins);
  setUpdateLogContent(updateLog);
  setPrivacyPolicyContent(privacyPolicy);
};

日志系统

1. 三级别日志架构

EasyFill v1.1.1 实现了单例日志系统,支持 INFO、WARN、ERROR 三个级别:

export enum LogLevel {
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
}

2. 智能环境适配

日志系统能够根据运行环境自动调整输出策略:

public configureByEnvironment(): Logger {
  const isProd = typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production';
  
  if (isProd) {
    // 生产环境:只显示警告和错误
    this.setLevel(LogLevel.WARN);
  } else {
    // 开发环境:显示所有日志,并启用彩色和时间戳
    this.setLevel(LogLevel.INFO)
        .useColors(true)
        .showTimestamp(true);
  }
  
  return this;
}

3. 控制台命令

生产状态下,日志默认关闭。所以,增加了命令调试:

// 启用日志系统
EasyFillLogger.enable()

// 关闭日志系统
EasyFillLogger.disable()

// 查看当前状态
EasyFillLogger.status()

命令绑定在全局 window 对象上,重启浏览器仍有效。

在浏览器扩展环境中,使用 chrome.storage.local 来存储,在普通网页环境中,使用 localStorage。

一样的是都用 easyfill_logger_enabled 这个键来存储

4. 链式配置接口

支持灵活的链式配置:

logger
  .setLevel(LogLevel.INFO)
  .useColors(true)
  .showTimestamp(true)
  .setPrefix('[EasyFill]')
  .setPrefixColor('color: #4CAF50; font-weight: bold');

配置选项:

  • 自定义日志前缀和颜色
  • 时间戳显示控制
  • 彩色输出开关
  • 级别过滤设置

隐私权政策

v1.1.1 版本对隐私权政策进行了全面更新:

主要更新内容:

  • 明确了关键字数据同步的目的和方式
  • 增加了用户控制权的说明

界面改进

1. 同步设置

新增了直观的同步设置界面,轻松管理数据同步:

  • 同步开关:一键启用/禁用自动同步
  • 同步频率:支持 1 小时到 1 周的灵活设置
  • 网络条件:可选择任何网络或仅 WiFi
  • 数据源管理:支持自定义 URL 和一键重置

2. 状态反馈优化

  • 实时显示同步状态和下次同步时间
  • 提供详细的操作成功/失败反馈
  • 使用 Snackbar 组件统一消息提示风格

短期计划

  • 实现黑白名单机制,控制填充权限
  • 支持多身份设置,满足不同用户需求
  • 完成在 Edge 和 Firefox 浏览器上的上架与兼容

长远计划

  • 使用 TensorFlow 训练机器学习模型,实现自动识别和评论补全功能
  • 在无性能开销的情况下,实现移动端跨平台支持(iOS 与 Android)
  • 依托 EasyFill 建立独立博客生态社区,面向计算机学生及爱好者提供系统性新手指南

致谢与支持

EasyFill 的每一次进步都离不开用户的支持和反馈。特别感谢:Mainbranch 的反馈与支持

如果您觉得 EasyFill 对您有帮助,欢迎:

<details> <summary>请我喝一杯咖啡</summary>  </details>


立即体验

EasyFill v1.1.1 现已在 Chrome 应用商店正式发布,您可以:

  1. 新用户:直接在 Chrome 应用商店搜索 "EasyFill" 安装
  2. 现有用户:通过梯子 Chrome 扩展将自动更新到最新版本
  3. 开发者:访问 GitHub 仓库 查看源代码

EasyFill - 简易填充,让每一次评论更自然,与你的博友互动无缝连接

端午骑行:倍鱼线

2025年6月1日 00:01

倍鱼线 入口

柏峰水库 一级水源

横门殿桥

养兵千日用兵一时,Samsung S Pen 触控笔有点作用了,遥控手机拍照

横门殿桥合影

横门殿桥合影

横门殿桥

柏峰水库(横门殿桥视角)

端午虽热,但来露营的人真不少

小溪旁的营地

不知道在抓什么物种

浙江的端午真不是盖的,室外像个桑拿房,给我一种快脱水的感觉。

我把车停靠在护栏上就下来了,咱也来来体验一下山泉水。

一级山泉水

热热热

即将脱水

山腰上

山顶(鱼曹头村方向)

合影合影

太幸福了!刚到达鱼曹头村,正好碰到一户人家在做午饭,花小钱办大事啊!吃吃饭再给手机充充电。

什么馅都有的馄饨

上山俩小时,下坡十分钟。速度达到 45 - 60 码!各种弯道,非常刺激!

准备返程回家了

爬升:1355m | 时间:4.15 | 距离:80.09km

离家最后三公里时突然暴雨,我滴天啊,该下的时候你不下。

到家后快递也到了,这三本书中我最期待石田裕辅写的"不去会死"

这本书是 <a href="https://freemind.pluskid.org/books/2022-book-list-winter" target="_blank" rel="noopener noreferrer">Pluskid 2022 书单其中一本</a> 看到他写的评语后,我就马上找这本书。偏小众些,年代久远不好找,都是二手货,全新没有简体版本,台版的八月开售(繁体)

C3环游记Ⅲ:加勒比没有那么远 | 徒步进藏 | 不去会死

基于 Astro Paper 的个人博客:深度定制和部署实践

2025年5月31日 03:12

今天是我独立博客走过的第八个年头。还记得那一年怀着对独立站的疑问,给孔令贤发邮件,询问是否可以使用他写的轮子,就是从他回复我那一刻起,我掉进了 WEB 深渊。

独立博客这个词,在 2025 年这个年代确实足够小众,但其中的快乐和对生活的态度,想必也只有博友能理解。

正是为了这第八个年头,才有了今天这全新的博客。从年初用 Jekyll 从零开始写,后来又用 Recat 写个半成品。最终阴差阳错选择了开源的 Astro Paper。

Astro Paper 这款主题性能极强,可拓展性也非常高,这也得益于 Astro 的静态特性和原作者优越设计。

经过一段时间的二次开发,这个博客差不多达到了我理想的样子。

在全站无缝刷新的基础下,我把博客全站的图片都做了懒加载,订阅和归档模块也做了滚动懒加载。

再加上页面内链的预加载处理,无论你点击哪个页面,都是一种享受。

Lighthouse 评分

Lighthouse 评分

下面就把新增的功能一一道来。

分类路由支持

Astro Paper 原生不支持平铺式 URL,也不能把文章进行分类:

  • https://lhasa.icu/posts/astro-paper-deployment/

改进后:

  1. 文章可按分类(技术、生活、运动等)划分路由。
  2. 支持按年份归档,且不会影响已有 URL 访问。
  3. URL 更加简洁:
  • https://lhasa.icu/astro-paper-deployment/

分类路由结构如下,可在/src/pages/中按需创建:

blog/
  ├── _releases/
  ├── examples/
  ├── life/
  │   ├── 2024/
  │   └── 2025/
  ├── sports/
  │   ├── 2024/
  │   └── 2025/
  └── technology/
      ├── 2024/
      └── 2025/

路由中间件处理

在不用 Artalk 评论系统的情况下,这个功能其实可有可无。但 Artalk 在路径识别上,存在很大的问题(带斜杠与不带斜杠会被视为不同页面),存在一定的隐患。

  • https://lhasa.icu/sports
  • https://lhasa.icu/sports/

其实使用 Nginx 会更方便一些。

import { defineMiddleware } from "astro:middleware";

export const onRequest = defineMiddleware((context, next) => {
  const url = context.url;
  const pathname = url.pathname;
  
  const staticExtensions = [
    '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
    '.css', '.js', '.json', '.xml', '.txt', '.pdf',
    '.woff', '.woff2', '.ttf', '.eot', '.otf'
  ];
  
  const isStaticResource = staticExtensions.some(ext => 
    pathname.toLowerCase().endsWith(ext)
  );
  
  // 如果是静态资源,不处理尾部斜杠
  if (isStaticResource) {
    return next();
  }
  
  // 如果URL不以斜杠结尾且不是根路径,则重定向到带斜杠的版本
  if (pathname !== "/" && !pathname.endsWith("/")) {
    const newUrl = new URL(pathname + "/" + url.search + url.hash, url.origin);
    return Response.redirect(newUrl.toString(), 301);
  }
  
  return next();
}); 

运动数据可视化

接入 Strava Riding Api 做了运动数据的可视化。

  1. 光标悬浮可切换月数据
  2. 点击日期可查看当天运动详情

EasyFill·我的信息

EXIF 元数据显示

借助腾讯云数据万象 API,默认自动启用。

  1. 参数为 Boolean 类型,false 可禁用
  2. 光标悬浮显示两秒
  3. 解析失败会自动生成合理参数并缓存


![ ](20250524003018.jpg)

EXIF 示例

图片标签

所有图片默认使用长标签,支持切换为短标签或禁用标签

义乌美术馆一角

义乌美术馆一角

若想在文章中启用 EXIF,需要将 .md 改为 .mdx,并引入组件:



![义乌美术馆一角](20250530173339.jpg)

悬停提示(tooltip)效果

图片的 title 属性不必声明,只要有 alt 属性,Img.astro 组件就会自动读取并渲染到页面中。

数学公式支持

通过 KaTeX 集成 支持了数学公式。纯静态渲染,无性能问题。示例:

$$ \text{骑行里程} = \text{均速} \times \text{时间} $$

$$
\text{骑行里程} = \text{均速} \times \text{时间}
$$

Artalk 集成

说到评论系统,首先感谢 Disqus PHP API 开源作者 Fooleap,感谢好大哥这些年来帮我在境外挂着接口...

Artalk 官方提供了简单的配置文件,不过足够了

services:
  artalk:
    container_name: artalk
    image: artalk/artalk-go
    restart: unless-stopped
    ports:
      - 9998:23366
    volumes:
      - ./data:/data
    environment:
      - TZ=Asia/Shanghai
      - ATK_LOCALE=zh-CN
      - ATK_SITE_DEFAULT=游钓四方的博客
      - ATK_SITE_URL=https://lhasa.icu

创建容器运行 Artalk:

docker-compose up -d

# 执行命令创建管理员账户
docker exec -it artalk artalk admin

再使用 Nginx 反代 9998 端口就可以实现域名访问了。

由于我是 Disqus 迁移过来的,需要把格式转换为 Artrans,然后再导入 Artalk。

由于无缝刷新的存在,就单单评论来说,调试花了不少时间,踩了很多坑,这里还把 Artalk 随着主题变化适配了配色。

<script is:inline data-astro-rerun>
(function () {
  // 单例模式存储 Artalk 实例
  window.artalkInstance = window.artalkInstance || null;

  const artalkConfig = {
    el: "#Comments",
    server: "https://artalk.lhasa.icu",
    site: "游钓四方的博客",
    pageKey: window.location.pathname,
    vote: false,

  };

  function destroyArtalk() {
    if (window.artalkInstance) {
      try {
        window.artalkInstance.destroy();
        document
          .querySelectorAll(".atk-sidebar, .atk-layer-wrap")
          .forEach(el => el.remove());
        window.artalkInstance = null;
        console.log("Artalk 实例已销毁", window.location.pathname);
      } catch (err) {
        console.error("销毁失败:", err);
      }
    }
  }

  // 初始化 Artalk 实例
  function initArtalk() {
    const container = document.getElementById("Comments");
    if (!container || container.querySelector(".atk-app")) return;

    const isDark = document.documentElement.getAttribute("data-theme") === "dark";

    artalkConfig.pageKey = window.location.pathname;
    artalkConfig.darkMode = isDark;

    window.artalkInstance = Artalk.init(artalkConfig);
    console.log("Artalk 初始化完成", window.location.pathname);
  }

  function handleThemeChange() {
    const themeBtn = document.querySelector("#theme-btn");
    if (!themeBtn) return;

    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.attributeName === "aria-label") {
          const isDark =
            mutation.target.getAttribute("aria-label") === "dark";
          if (window.artalkInstance) {
            window.artalkInstance.setDarkMode(isDark);
          }
        }
      });
    });

    observer.observe(themeBtn, {
      attributes: true,
      attributeFilter: ["aria-label"],
    });
  }

  function handlePageLoad() {
    destroyArtalk();
    initArtalk();
    handleThemeChange();
  }

  function setupArtalk() {
    if (window._artalkInitialized) return;
    window._artalkInitialized = true;

    document.addEventListener("astro:before-swap", destroyArtalk);
    document.addEventListener("astro:after-swap", handlePageLoad);

    if (document.readyState === "complete") {
      handlePageLoad();
    } else {
      document.addEventListener("DOMContentLoaded", handlePageLoad);
    }

    // 监听主题
    window
      .matchMedia("(prefers-color-scheme: dark)")
      .addEventListener("change", ({ matches }) => {
        if (window.artalkInstance) {
          window.artalkInstance.setDarkMode(matches);
        }
      });
  }

  setupArtalk();
})();
</script>

Artalk 自带的验证码不好用,这里强烈推荐 Cloudflare Turnstile。无感验证,很省心。

在 Cloudflare 控制台主页可以看到 Turnstile,在填完域名后可以申请到Site KeySecret Key

随后打开 Artalk 控制中心,填入相应参数后,captcha_type选择turnstile即可。

本地开发

纯净版 Astro Paper:

# pnpm
pnpm create astro@latest --template satnaing/astro-paper

# npm
npm create astro@latest -- --template satnaing/astro-paper

# yarn
yarn create astro --template satnaing/astro-paper

或者直接使用我的扩展版本:

git clone https://github.com/achuanya/Blog.git

然后通过安装依赖启动开发环境

# 安装依赖
pnpm install

# 启动开发环境
pnpm dev

Docker 部署

用于生产环境的 Docker 配置已经写好了,可以直接构建镜像。

# 构建生产镜像
docker build -t astropaper .

# 启动生产环境,端口为 4321
docker run -p 4321:80 astropaper

配合 Nginx 反代:

server {
    listen 80;
    server_name lhasa.icu;

    # 404
    error_page 404 /404.html;
    location = /404.html {
        root /home/github/Blog/dist;
        internal;
    }

    location / {
        proxy_pass http://127.0.0.1:4321;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

不想折腾,建议安装宝塔Linux面板,随便点击几下,两分钟上线

if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec

Github + Vercel 部署

  • https://vercel.com/templates/blog/astro-paper

只需点击 Deploy 按钮,按提示一步步即可上线。

相关命令

Command Action
pnpm install 安装依赖项
pnpm run dev 启动本地开发服务器,访问地址为 localhost:4321
pnpm run build 将生产环境网站构建到 ./dist/ 目录
pnpm run preview 本地预览生产环境构建的站点,部署前检查效果
pnpm run format:check 使用 Prettier 检查代码格式
pnpm run format 使用 Prettier 格式化代码
pnpm run sync 为所有 Astro 模块生成 TypeScript 类型。 了解更多
pnpm run lint 使用 ESLint 进行代码检查
docker compose up -d 使用 Docker 运行 AstroPaper,可通过 dev 命令中相同的主机名和端口进行访问
docker compose run app npm install 在 Docker 容器中执行任意上述命令
docker build -t astropaper . 为 AstroPaper 构建 Docker 镜像
docker run -p 4321:80 astropaper 在 Docker 中运行 AstroPaper。网站可通过 http://localhost:4321 访问

注意!
Windows PowerShell 用户如果想在开发期间运行诊断(例如 astro check --watch & astro dev),可能需要安装 concurrently 包
更多信息请参考 这个 issue

下一步

目前来说,还有很多地方没有完善,细节没有做到位:

  1. Strava Riding Api 还没有实现完全自动化,更新数据还是需要人工
  2. Img.astro 组件没有封装到位,还有细节需要把控
  3. Sports 在移动端时的表现还需要好好想想
  4. 给 Feeds 做个后台管理,先把头像显示问题解决了。当然,有邮箱最好
  5. 吃透 Astro Paper 无缝刷新机制

参考文档

一路向南,骑见江南:一人、一车、一旅途

2025年5月11日 16:28

4月24日,递交完辞职报告的那一刻,我心中一阵轻松。我终于可以离开了,离开熟悉的一切,前往下一个未知但让我心跳的地方——义乌。

原本的计划是从郑州骑行到义乌,约一千公里。然而,我的长途骑行装备都还在老家淮阳,不得不先折返一趟,郑州到淮阳≈210公里。

第一站:淮阳

4月26日,天还没亮我就起床了,所有行李早已打包妥当,多亏了顺道的好大哥和双双姐姐,他会把行李直接送到义乌,精准投放到阿丽家门口。所以我只需要背上包、骑车走就行了。

临出门前,我把钥匙存放在小区门口的保安亭,与保安打了招呼,等双姐有空再来取。

05:42 AM 郑州·孙庄北院

出小区准备吃饭

在小区南门吃了碗大米红枣粥,这是我在郑州的最后一顿早餐。我吃得很慢,望着小区,心里百感交集。最不舍的,是我的双姐,直到离开,我们也没有一张合影。唉……今朝一别,不知何日再见。

离开河南前·最后一碗粥

10:49 AM 开封·张市桥

从出发到现在,已骑行近百公里(99.81公里),历时4小时15分。中途几乎没停,一直到开封张市桥,看到一位大爷坐在家门口听戏,我停下来敬了他一根烟,顺势在他门口歇了口气。我实在太饿了,得找点吃的。

开封·张市桥 不知名大爷家门口歇脚

骑行至此,除了背包里三根香蕉,还有昨晚和双姐吃剩的半盒牛肉,没有其他补给。吃完香蕉后抽了两根烟,心理上稍有缓解,重新背上包,继续赶路,准备找个地方补点碳水。

坐在大爷家门口板凳上,补个蕉

11:52 AM 周口·扶沟

点了一碗烩面和一个鸡爪,牛肉是我自带的。说实话,第一次见到餐桌上这么干净的盘子...鸡爪也很入味,咬一口我就扔了,太清真了

白潭镇人民政府旁边的清真小饭店

骑到汴岗镇时,看到一家超市,顺便买水时偶遇了渤哥。

牌子!班尼路儿~

经过东夏亭镇人民政府旁的一条支流,干涸得厉害,河床都裸露出来了。不知是人为抽干,还是自然干旱所致。在这段行程中,这样的场景我已经见过好几次。

干旱,河床都漏了出来

5:39 PM 周口·淮阳

到家!在小区门口买了两杯最爱的粥

全程逆风,路上每一米都艰难,越过扶沟、西华,路过麦田,虫子爬满全身,骑得越快身上越多,还有像蛆一样的小东西全身都是,包括脸上,这六十多公里真的折磨,全程都是虫。

骑行 204.4 Km

本次行程装备大致如下:

  • Challenge ELITE 700×25c 管胎 x1

  • Topeak 多功能工具组

  • 康比特盐丸 x10包

  • CUKTECH 15 SE 移动电源

  • 前后尾灯

  • 便携打气筒

  • 备用袜子 x4双

  • 三合一充电线 若干

左边青蓝色的包是我从郑州背回来的,背负系统不科学,透气性也差,只适合日常出行,不适合长途骑行,这次回来扔家里让他吃灰。这次换上了右边的迪卡侬骑行包,内置水袋设计很实用,咬咬吸嘴就能补水,长途骑行相当方便。

换个背包,准备补给

韶音耳机声音太小,周围稍有噪音就听不见。干脆把BOSE音箱扎带绑车上,没有音乐我不能活。

把音箱绑在了车上

04-28 6:55 AM 淮阳·龙湖新城

奶奶身体最近不太好,高血压住院了,所以骑行晚走了一天。

这是我在淮阳的最后一顿早餐:胡辣汤、鸡蛋饼、两个鸡蛋。早上的蛋尤为重要,它决定了我今天能不能顺利完成骑行

在淮阳的最后一顿早餐

刚吃完早餐,习惯性的捏捏车胎,发现车胎气压不足,我当时就觉得不对劲,很大概率已经爆胎了。当我拿打气筒充气时,气嘴滋滋漏气,我当时就服了。这次行程我就备了一个管胎,这种胎很贵,没那么多资金支持我买一堆备胎,管胎这种轮胎和其他胎不一样,换胎要除胶贴胶费时费力,你要说补胎,我估计一般的小品牌技师都没摸过管胎,手头宽裕了,我一定要把这轮组先换了,它不支持管胎以外的其他胎型。

换胎,重新贴胶

真是服了,啥好事都让我碰上了

安装好了,最后抽条

准备出发

12:12 AM 安徽·阜阳·太和县

骑行105公里到达阜·太和县,途中遇到很多有意思的场景,就是没有拍照,天气实在太热了,骑行的过程中实在懒得下来拍照。

在阜阳吃个午饭

18:02 PM 安徽·淮南·凤台县

骑行202公里到达淮南·凤台,吃个晚饭,点了一晚大肉水饺还有一份鸭腿,这点饭量对骑行时的我是完全不够的,她们家的绿豆汤是免费的,不稀比较稠,而且还是免费的,我连着吃了三碗...

在淮南吃个晚饭

本来想着吃完饭继续骑,奈何自己看见宾馆走不动路,想想两天也到不了,不急这一会儿。

淮南·凤台 御唐宫

到了屋里第一时间扒个精光,实在太热了,热了干,干了湿,浑身都很黏,

这胳膊,熟悉的痛感

骑行 202.6 Km

04-29 6:55 AM 安徽·淮南·凤台县

早餐还是老三样,这次的粥我多加了糖,高糖分有助于我长途骑行

在凤台县的最后一顿早餐

11:12 AM 安徽·淮南·田家庵区

路过淮南市区买了5根香蕉,这天实在太热了,隔着手套都能感觉到香蕉是热的

补个蕉

12:32 AM 安徽·合肥·长丰县

这个披萨是我吃过最难吃的,已经不能称为披萨了,速冻的薄饼加热了一下,这顿饭是在蜜雪冰城吃的,就为了只有他们店舍得在荒无人烟的地方开空调

吃个中午饭

当时看到这个场景真的很惊艳,一抹绿,在这里钓鱼露营一定很爽

一抹绿

太热了,找了阴凉地,坐在国道两波护栏歇会儿...

歇会儿

6:46 AM 安徽·合肥·肥东县

绝了,即出发后第二次爆胎,当时天已经黑了,还好不是在荒郊野外爆的,不然我就要提前体验田野生活了,补救的可能性很小,因为管胎里面的管子炸了,管胎这种东西不用考虑当地施救的可能性,实体店不会有管胎卖,修更别说了,几乎失传的手艺。

再次炸管胎,而且是后轮

也怪我,左转速度太快,正好有个坡度看不到这缝,后轮当场巨响爆了!

就是在这里

爆胎后,我穿着锁鞋推行了两公里左右,找了一家宾馆住了下来,我也懒得检查哪漏气了,碳轮坏了没有,一切都不重要了,现在就想着咋带车去义乌,我没有车包,高铁肯定是上不了。

刚骑行 176 Km 当场退役

04-30 8:09 AM 安徽·淮南·瑶海区

经费紧张,去义乌我也有规划着时间,等不起在网上买管胎的代价,由于没有装车包,公路车无法上高铁,托运怕出事。在网上找了一个义乌直达大巴车,可以放下面,车费200,行李房120砍到88,合计费用比高铁还贵。

打包滚蛋

11:32 AM 江苏·南京·江宁区

到饭店了,客运公司开始收割了,大巴车把车停到了他们据点,周围几公里都没有商店,饭堂和商店的物价堪比上海浦东机场,有些人早有准备自带了泡面,但是人家的热水专供司机,不让旅客用,笑了。

客运公司开始收割

3:16 PM 浙江·杭州·上城区·九堡大桥

看到这里差点掉小珍珠,之前在杭州上班时经常开车路过这里,Hi 杭州 好久不见。

杭州·九堡大桥

时隔六年,再次为热爱脱皮。痛苦如影随形,却也因此更加坚定

晒伤脱皮的右臂

晒伤脱皮的左臂

黑白分明

新手跑步第五次:单人挑战不间断半马

2025年4月24日 12:50

从跑步小白,到不间断半马需要多久?答案是六天,五次!

Day 4:首次不间断 10 公里

2025年4月20日 · 跑步第四天。早上起床时心情很好,因为我发现大腿已经不疼了,想必是适应了十公里的运动量,我决定今天晚上下班后,开始挑战不间断十公里

Day 4 · 小区在放电影·智取威虎山

晚上刚下班,我直接回家换了运动装,出了门正好看到篮球场在放电影《智取威虎山》,我喜欢露天电影的氛围,有空一定要去看一场

Day 4 · 中原区·西流湖公园

从小区北门起跑,沿着郑上路一路向南,经过郑州市实验小学、第一中学等地标,最后在T字形路口到达西流湖公园北侧门,目前里程为4公里左右,这条路被我骑车压过不知道多少次了,可这次是跑步,带给我的感觉不一样

Day 4 · 西流湖公园·外围下坡

进了公园右转是上坡,向左则为两条路可以选择:

  1. 公园外围下坡,路面宽阔、路灯明亮
  2. 岸边小道,下坡路况一般

Day 4 · 西流湖公园·不知名桥亭

这个桥亭设计了很多座位,栏杆也不高,下面是贾鲁河的支流,还做了一个闸口的设计,水流从上面流下来,下面是人工池,形成一个小瀑布的效果,非常适合路亚、溪流钓,台钓佬就省省吧,只能大跑铅

Day 4 · 西流湖公园·不知名桥亭 ``

Day 4 · 完成不间断 10 公里

分段成绩

Km 配速 海拔 心率 (bpm)
1 5′41″ −2 m 167
2 6′29″ 0 m 174
3 6′57″ +2 m 169
4 7′54″ 0 m 161
5 8′08″ −12 m 162
6 7′35″ +13 m 168
7 6′58″ −4 m 170
8 7′07″ +1 m 166
9 6′47″ 0 m 173
10 6′42″ +2 m 170
0.1 6′05″ −1 m 175

10 km 综合数据

指标 数值
距离 10.14 km
平均配速 7′01″ / km
最快分段 5′41″ / km
平均经过配速 7′05″ / km
移动时间 1 h 11′ 08″
全程耗时 1 h 11′ 47″
平均心率 168 bpm
爬升 35 m
消耗卡路里 643 kcal

Day 6:新手跑步第五次,单人 挑战 不间断 半马!

2025年4月22日 · 跑步第六天 · 第五次

今天我,昨天晚上忙着搬家没有跑步,处于内心的愧疚,我决定今天晚上把昨天的补回来,在跑之前我还不知道半马是什么意思

Day 6 · 半马·五公里记录

像往常一样,再次来到西流湖,里程来到了五公里,这段距离的心率有些高,心率区间在165-180,往后的数据都没有这个高

Day 6 · 半马·十公里记录

饮水没控制住,已经喝了550ml,当时非常兴奋,因为我即将跑返程了

Day 6 · 半马·十五公里记录

跑到这里时,体力消耗的差不多了,双腿感觉十分僵硬,停一秒钟感觉都会导致后面跑步下去

Day 6 · 半马·记录

到小区门口了,我感到非常兴奋,因为我即将完成我的第一次半马挑战,而且是不间断,除了中途买水,期间几乎没有停过

Day 6 · 半马·记录

Day 6 · 魔镜魔镜谁是最持久男人

跑完站在家里,小腿和大腿没有疼痛感,唯一不舒服的就是双腿的膝盖关节处,活动就会有些疼痛

半马·分段成绩

Km 配速 海拔 心率 (bpm)
1 5′24″ −1 m 170
2 5′51″ 0 m 178
3 6′23″ +3 m 180
4 6′40″ −2 m 177
5 7′45″ −12 m 165
6 8′28″ +8 m 161
7 7′52″ 0 m 162
8 7′28″ +1 m 164
9 6′52″ −3 m 171
10 7′21″ −8 m 166
11 8′46″ −3 m 158
12 7′04″ −1 m 171
13 8′13″ +1 m 164
14 7′11″ 0 m 170
15 7′17″ +1 m 170
16 7′43″ +10 m 167
17 7′28″ +2 m 170
18 7′26″ 0 m 170
19 7′22″ −1 m 170
20 7′37″ +2 m 167
21 7′37″ +2 m 166
22 7′02″ 0 m 172
0.9 6′34″ +1 m 176

半马 · 数据一览

指标 数值
距离 22.96 km
平均配速 7′17″ / km
最快分段 5′24″ / km
平均经过配速 7′18″ / km
移动时间 2 h 47′ 07″
全程耗时 2 h 47′ 28″
平均心率 169 bpm
爬升 69 m
消耗卡路里 1450 kcal

Day 6 · 标签

到家准备脱裤子时才发现,我中午买的第一条跑步用的紧身裤标签还没摘,现在已经被汗水浸湿烂掉了,让我有种破茧的感觉

记录我的前3次跑步:从陪跑到主动出发

2025年4月20日 15:25

从讨厌到上瘾,原来跑步也能这样有趣.

我一直是个不爱运动的人,尤其讨厌跑步。打小起,我对跑步总是敬而远之

这次之所以开始跑步,完全是被阿坤和阿丽带动的

起初只是想着陪他们减肥,没想到,从第三天开始,我居然有点跑上瘾了

Day 1:人生第一次 5 公里(其实只跑了 3 公里)

第一次跑步是阿坤叫我的,他想减肥,我就陪他出来遛弯。他说目标是 5 公里,结果我们大半时间都在走路,实际上只跑了 3 公里

他有点胖,体力跟不上,但我直到活动结束都没有什么感觉

Day 1 · 没有什么感觉


跑步 Day 2:不间断 5 公里初体验

第二天我刚下晚班(19:00),我打电话问阿坤什么时候出发,他说八点半。我不想等太久,就先回家收拾一下便出门了

第一天穿板鞋和牛仔裤实在太难受,这次吸取了教训,只穿了短裤、速干背心和跑鞋

站在小区门口花两分钟热热身,把软件都打开便开始跑了

刚开始跑到 0.86 公里 时,心率就达到了 183,但呼吸还算平稳

跑到 2 公里时,心率稳定在 168–170,最终顺利完成不间断五公里,一点都不觉得累,只是非常口渴

跑完后在楼下买了瓶水,还给阿坤发了个微信。结果瓶盖还都没拧开,就下起了暴雨,就像是天上开了个花洒一样,很突然...

Day 2 · 不间断 5 公里,暴雨突降

Day 2 · 首次不间断 5 公里

阿坤因为下雨就没出门,我们在老地方随便吃了点东西聊聊天。准备回家时,我才发现自己腿已经快站不直了,大腿疼得厉害


跑步 Day 3:加码挑战,十公里!

早上起床,大腿肌肉酸痛,走路都不太舒服,走路都一瘸一拐的,就像当初刚学骑自行车一样,这种酸爽的痛感,反倒让我有点兴奋

出门碰头时,阿坤说想骑我的自行车,我说你骑吧,我跑步

相比昨天,今天的心率平稳多了,基本维持在 150–160。跑到 6.59 公里时,心率才到 181,那一刻我只觉得跑步,真的爽!掌握节奏之后,压根不想停下来!

Day 3 · 徐佳莹在奥体开演唱会

跑着跑着来到奥体,正好赶上徐佳莹的演唱会。场外摆摊的特别多,还有个露天KTV,这种我是第一回次见,他们的声音是真大,我在 2 公里外就听见了,没一会儿,三四个保安冲过来大喊:“里面在开演唱会呢!”结果一个大妈拿着话筒回了一句:“演唱会咋了,演唱会咋了!” 笑死我了

之后我们绕着奥体转了一圈,发现个室外健身区,有很多器械,比如健身单车,还支持联网进行在线竞赛,而且运动数据可同步app,最重要的是全部免费!

Day 3 · 阿坤骑着我的自行车

返程时演唱会刚结束,整个奥体路被堵得水泄不通,到处是人和出租车

Day 3 · 首次十公里

其实,今天的十公里多少有些违心,因为我实际只跑了 8.25 公里,剩下的两公里是骑车,阿坤说骑不动了,让我骑车,他跑着...

Strava Riding Api 上线

2025年4月10日 00:23

该脚本基于 Strava API v3 获取指定用户当年的所有骑行活动数据,并将其保存为JSON格式

功能特性

Strava Riding Api 只实现了 OAuth 2.0 授权流程的部分自动化,由于技术限制,目前无法实现完全自动化:

已实现部分

  • 半自动 OAuth 2.0 授权流程,轻松访问您的 Strava 数据
  • 自动获取任意年份的所有骑行记录
  • 获取每个活动的完整运动数据
  • 智能令牌管理:自动保存和刷新过期的访问令牌
  • 数据自动转换:公里、时间、速度单位等数据格式化
  • 内置多重容错机制,确保数据获取的可靠性

使用前设置

重要: 在使用此脚本前,请确保在Strava开发者平台上正确配置您的应用:

  1. 访问 Strava开发者设置
  2. 将以下URL添加到"授权回调域":
    localhost
    
    注意:只需输入 localhost 而不是完整的 http://localhost:8000
  3. 保存设置

使用方法

  1. 安装依赖:

    yarn install
    
  2. 获取并处理授权码:

    yarn auth
    

    获取授权后,您会收到一个授权码。将其粘贴到命令行中。

  3. 获取骑行数据:

    yarn start
    
  4. 查看输出的JSON文件,文件名格式为:strava_data.json

解决认证问题

如果您遇到API相关错误,请尝试以下解决方案:

  1. 更新令牌

    yarn auth
    

    重新获取授权并更新令牌

  2. 检查API状态

    访问 Strava API状态 确认服务是否正常

常见问题解决

  1. "protocol mismatch"错误

    • 此问题已在最新版本中解决,使用了原生HTTPS模块发送请求
    • 确保在Strava开发者设置中添加了localhost作为授权回调域 <br/><br/>
  2. 无法获取活动数据

    • 确认您的账户中确实有骑行活动
    • 检查筛选条件是否正确(默认只获取"Ride"类型活动) <br/><br/>
  3. API错误或限流

    • Strava API有使用限制(每15分钟100次,每天1000次)
    • 数据量大时,脚本已添加延迟以避免触发限流

许可证

本项目采用 Mozilla 公共许可证 2.0 版发布

Strava API v3:https://developers.strava.com/docs/reference

Strava Riding Api:https://github.com/achuanya/Strava-Riding-Api

EasyFill 发布了

2025年4月7日 20:38

就在刚刚 EasyFill 终于通过了 Chrome Web Store 的审核,正式发布了!

功能特性

  • 智能填充:DOM 加载完后,自动读取表单插入数据。
  • 无缝集成:与主流博客平台和评论系统兼容。
  • 数据加密:通过 AES-GCM 加密和解密功能,保护用户数据安全。
  • 现代化界面:基于 Material-UI 和 React 提供用户友好的界面。

安装

  1. 打开 Chrome Web Store
  2. 搜索 EasyFill
  3. 点击 添加到浏览器 按钮完成安装。
  4. 安装完成后,浏览器工具栏会显示 EasyFill 图标。

更新日志

查看 更新日志 了解最新功能和修复。

问题反馈

如果你在使用过程中遇到问题,请在我的博客留言

支持作者

感谢您对我的支持,本人非程序员,忙里抽闲,为爱发电。

如果您觉得 EasyFill 对您有帮助,可以通过以下方式支持我继续创作:

许可证

本项目基于 Mozilla Public License Version 2.0

Github 仓库:https://github.com/achuanya/EasyFill

✨ EasyFill 只为向那些在浮躁时代,依然坚守独立博客精神的你们致敬!

产品被拒

2025年4月3日 00:05

晚上下了班打开电脑刚坐下就看到了一封 Google 邮件,首先看到了发件人 "Chrome Web Store",当时就心想提交审核一个多星期了,终于看到一点音信了。点开后,还没等我高兴,便看到了:

解决BUG

被拒的原因非常低级,声明了但未使用的 scripting 权限。

scripting 权限是 Manifest V3 中引入的一个重要权限,主要用于动态脚本执行chrome.scripting.executeScript()和动态样式注入chrome.scripting.insertCSS()

而在EasyFill中,使用的是静态声明:

content_scripts: [
  {
    matches: ["<all_urls>"],
    js: ["content-scripts/content.js"],
  },
];

删除scripting参数后,重新打包并再次向 Chrome Web Store 提交了扩展。

就这么一个小BUG,浪费了我一个星期的审核时间,太耽误事了,当时为了解决 Shadow DOM 才使用 scripting,直到现在这个问题也没有解决,希望下个版本可以解决问题

产品谍照:

注册 Chrome Web Store 开发者

2025年3月20日 00:03

年前曾尝试过 Chrome 扩展开发,《写一个Chrome表单自动化插件》,但是由于没有注册 Chrome Web Store 开发者,无法上传到 Chrome 应用商店。

注册 WildCard

Chrome 注册开发者需要五美元,由于我没有境外信用卡就一直卡在这,2022 年我在杭州办过一张中信的双币卡,年费很高,后来经济紧张时注销了,现在急着用外币还挺麻烦,折腾一圈,最终无脑选择了 WildCard,尽管网上对它负面评论铺天盖地。

[WildCard][p3] 开卡费用是 10.99 美元/年,实际付款 79.71 人民币,按照今天的市场汇率 7.23,实际多付了 0.24,而且这只是开卡费用,充值另算。

开卡后我充值了 10 美元,支付宝付款 75.07,到账金额 10 美元:

$$ \frac{2.77}{75.07} \times 100% \approx 3.69% $$

四个点我能接受(接不接受都要受着),这个开卡费不便宜,毕竟钱不是大风刮来的,所以注册时,我创建了两个号,推荐注册返现两美金...

注册 Chrome Web Store 开发者

注册账号就很容易了,Google 绑卡付钱就行。但是如果要销售发布就很麻烦:

个人交易者声明

  • 您需要提供一个手机号码以验证是您本人在操作
    • 您将通过手机接收代码
  • 用于证明是您本人的身份证件
  • 可接受的文件包括:
    • 驾照
    • 护照
    • 州身份证明
    • 绿卡
  • 您需要提供一份显示您的姓名和当前地址的文件
  • 可接受的文件包括:
    • 由政府签发的文件或带照片的身份证件
    • 公共事业缴费单或话费账单(日期在过去 60 天内)
    • 银行对账单(日期在过去 60 天内)
    • 租赁合同或抵押贷款合同

因为 Google 已退出中国市场,不支持交易。而我是美国 Visa 卡,面对这样的要求不容易做到。

日后再说吧,往后这段时间,我打算把博客评论表单自动填充插件重构一下,然后上架 Chrome 应用商店。

空腹骑行75公里

2025年3月12日 15:36

周六

最近郑州天气突然转冷,骑行频率也降了下来,周六正好赶上休息,实在是憋坏了!今天不管刮风下雨,必须出去骑一趟

原计划直接奔开封,结果路过龙湖就停了下来。好久没来了,上次来还是鹅毛大雪天,如今雪没了,只剩下鹅

倒挂白鹅

周六的公园人满为患,没法骑。我推着车沿湖边缓行,遥望着远处炸水的不知名鱼,不由自主的想蹲下摸摸湖水,真的很想钓鱼,自到郑州以来,我连最爱的路亚竿都没摸过

龙湖·北岸

此时正值中午,小孩在沙滩上牵着风筝奔跑,大人排队买小吃,顿时勾起了不少儿时回忆,我也好想光着脚奔跑在沙滩上...

龙湖·人工沙滩

在龙湖公园出来后,我关掉了导航线瞎跑,根本不认识路,不知道自己在哪,扫大街呗

STRAVA 74.6km  爬升313m  时间4h 19m

话说现在骑车很少拍照,不是不爱拍,而是懒得下车,即使趴到腰酸,感觉腰快要断了,也不想停下来

周日

拍这张照片时,已经快饿晕了,周六晚上吃得少,周日早上又空腹出门,体力消耗得厉害...

周日早晨睡到自然醒,一看表,我整个人都快立正了,居然八点半了。着急忙慌洗漱后,脱下内衣裤直接换上骑行服,背上包,拿了五块巧克力出发了。因为周一要上班,所以今天必须放纵一下,出发前大致算了算,来回返程再加上逛街的时间,早饭根本来不及吃...哎...

大约骑了25公里,在中石化买了瓶宝矿力水特。又骑行了二三十公里到了贾鲁河桥,饿的没劲,更别说爬坡了,挂上小盘,我慢悠悠到了桥中间,休息了几分钟,把五块巧克力补给全吃了

郑州·贾鲁河桥

就这样空腹骑到了开封郊区,此时的里程已经来到了 75.38公里,用时3小时23分钟

到达开封后,心里那股坚持的信念瞬间消失了,又渴又饿,高德帮我找了最近一家名为三不炒(开封总店)的小炒店,我把车子靠着门店随便一放,就去买葡萄糖了

就去买葡萄糖了

买完水出来发现要排队,人还不少,我是真的饿得快走不动了,但还是懒得换地方,抱着水坐在外面等了半小时。饿得快虚脱了,感觉此时此刻,就算把馒头挂我脖子上都能饿死

我前面排了八个人

排队加吃饭花了一个半小时,吃得太撑,骑上车都趴不下去,推着车穿过老巷子走到了湖边

对面就是清明上河园

御河桥洞下

御河桥洞下

正在乐钓的五星开封好市民

STRAVA 164.1km  爬升417m  时间8h 5m

回到家已经八点半了,这条郑开大道路线真心推荐,毕竟二刷了,虽然沿途风景平平,但对于郑州来说,已经是顶级骑行路线了,一个人骑行在郑开大道,握着下把位,不用担心刹车,不用担心前方有没有人,听着歌,摇着车,也不枉来郑州走一遭

利用 Go + COS + GitHub 重构 RSS 爬虫

2025年3月12日 03:26

之前我写过一篇《利用Go+Github Actions写个定时RSS爬虫》来实现这一目的,主要是用 GitHub Actions + Go 进行持续的 RSS 拉取,再把结果上传到 GitHub Pages 站点

但是遇到一些网络延迟、TLS 超时问题,导致订阅页面访问速度奇慢,抓取的数据也不完整,后来时断时续半个月重构了代码,进一步增强了并发和容错机制

在此感谢 GPT o1 给予的帮助,我已经脱离老本行很多年了,重构的压力真不小,有空就利用下班的时间进行调试,在今天凌晨 03:00 我终于写完了

1. 为什么要重构

旧版本主要基于 GitHub Actions 的定时触发,抓取完后把结果存放进 _data/rss_data.json 然后 Jekyll 就可以直接引用这个文件来展示订阅,但是这个方案有诸多不足:

  1. 网络不稳定导致的抓取失败

    由于原先的重试机制不够完善,GitHub Actions 在国外,RSS 站点大多在国内,一旦连接超时就挂,一些 RSS 无法成功抓取

  2. 单线程串行,速度偏慢

    旧版本一次只能串行抓取 RSS,效率低,数量稍多就拉长整体执行时间,再加上外网到内地的延时,更显迟缓

  3. 日志不够完善

    出错时写到的日志文件只有大概的错误描述,无法区分是解析失败、头像链接失效还是RSS本身问题,排查不便

  4. 访问速度影响大

    这是主要的重构原因!在旧版本里,抓取后的 JSON 数据是要存储到 Github 仓库的,虽然有 CDN 加持,但 GitHub Pages 的定时任务会引起连锁反应,当新内容刷新时容易出现访问延迟,极端情况下网页都挂了

    重构后,在此基础上进行了大幅重构,引入了并发抓取 + 指数退避重试 + GitHub/COS 双端存储的能力,抓取稳定性和页面访问速度都得到显著提升

2. 主要思路

2.1 整体流程

先看个简单的流程图

        +--------------------------+
        | 1. 读取RSS列表(双端可选)  |
        +------------+-------------+
                     |
                     v
           +---------------------+
           | 2. 并发抓取RSS,限流   |
           |  (max concurrency)  |
           +-------+-------------+
                   |
                   v
        +------------------------------+
        | 3. 指数退避算法 (重试解析失败)  |
        +------------------------------+
                   |
                   v
           +-------------------+
           | 4. 结果整合排序    |
           +--------+----------+
                    |
                    v
        +-------------------------+
        | 5. 上传 RSS (双端可选)   |
        +-------------------------+
                    |
                    v
           +--------------------+
           | 6. 写日志到GitHub   |
           +--------------------+
  1. 并发抓取 + 限流
    通过 Go 的 goroutine 并发抓取 RSS,同时用一个 channel 来限制最大并发数

  2. 指数退避重试
    每个 RSS 如果第一次抓取失败,则会间隔几秒后再次重试,且间隔呈指数级递增(1s -> 2s -> 4s),最多重试三次,极大提高成功率

  3. 灵活存储
    RSS_SOURCE: 可以决定从 COS 读取一个远程 txt 文件(里面存放 RSS 列表),或直接从 GitHub 的 data/rss.txt 读取<br/> SAVE_TARGET: 可以把抓取结果上传到 GitHub,或者传到腾讯云 COS

  4. 日志自动清理
    每次成功写入日志后,会检查 logs/ 目录下的日志文件,若超过 7 天就自动删除,避免日志越积越多

2.2 指数退避

上一次写指数退避,还是在养老院写PHP的时候,时过境迁啊,这段算法我调试了很久,其实不难,也就是说失败一次,就等待更长的时间再重试,配置如下:

  • 最大重试次数: 3
  • 初始等待: 1秒
  • 等待倍数: 2.0

也就是说失败一次就加倍等待,下次若依然失败就再加倍,如果三次都失败则放弃处理

// fetchAllFeeds 并发抓取所有RSS链接,返回抓取结果及统计信息
//
// Description:
//
//   该函数读取传入的所有RSS链接,使用10路并发进行抓取
//   在抓取过程中对解析失败、内容为空等情况进行统计
//   若抓取的RSS头像缺失或无法访问,将替换为默认头像
//
// Parameters:
//   - ctx           : 上下文,用于控制网络请求的取消或超时
//   - rssLinks      : RSS链接的字符串切片,每个链接代表一个RSS源
//   - defaultAvatar : 备用头像地址,在抓取头像失败或不可用时使用
//
// Returns:
//   - []feedResult         : 每个RSS链接抓取的结果(包含成功的Feed及其文章或错误信息)
//   - map[string][]string  : 各种问题的统计记录(解析失败、内容为空、头像缺失、头像不可用)
func fetchAllFeeds(ctx context.Context, rssLinks []string, defaultAvatar string) ([]feedResult, map[string][]string) {
	// 设置最大并发量,以信道(channel)信号量的方式控制
	maxGoroutines := 10
	sem := make(chan struct{}, maxGoroutines)

	// 等待组,用来等待所有goroutine执行完毕
	var wg sync.WaitGroup

	resultChan := make(chan feedResult, len(rssLinks)) // 用于收集抓取结果的通道
	fp := gofeed.NewParser()                           // RSS解析器实例

	// 遍历所有RSS链接,为每个RSS链接开启一个goroutine进行抓取
	for _, link := range rssLinks {
		link = strings.TrimSpace(link)
		if link == "" {
			continue
		}
		wg.Add(1)         // 每开启一个goroutine,对应Add(1)
		sem <- struct{}{} // 向sem发送一个空结构体,表示占用了一个并发槽

		// 开启协程
		go func(rssLink string) {
			defer wg.Done()          // 协程结束时Done
			defer func() { <-sem }() // 函数结束时释放一个并发槽

			var fr feedResult
			fr.FeedLink = rssLink

			// 抓取RSS Feed, 无法解析时,使用指数退避算法进行重试, 有3次重试, 初始1s, 倍数2.0
			feed, err := fetchFeedWithRetry(rssLink, fp, 3, 1*time.Second, 2.0)
			if err != nil {
				fr.Err = wrapErrorf(err, "解析RSS失败: %s", rssLink)
				resultChan <- fr
				return
			}

			if feed == nil || len(feed.Items) == 0 {
				fr.Err = wrapErrorf(fmt.Errorf("该订阅没有内容"), "RSS为空: %s", rssLink)
				resultChan <- fr
				return
			}

			// 获取RSS的头像信息(若RSS自带头像则用RSS的,否则尝试从博客主页解析)
			avatarURL := getFeedAvatarURL(feed)
			fr.Article = &Article{
				BlogName: feed.Title,
			}

			// 检查头像可用性
			if avatarURL == "" {
				// 若头像链接为空,则标记为空字符串
				fr.Article.Avatar = ""
			} else {
				ok, _ := checkURLAvailable(avatarURL)
				if !ok {
					fr.Article.Avatar = "BROKEN" // 无法访问,暂记为BROKEN
				} else {
					fr.Article.Avatar = avatarURL // 正常可访问则记录真实URL
				}
			}

			// 只取最新一篇文章作为结果
			latest := feed.Items[0]
			fr.Article.Title = latest.Title
			fr.Article.Link = latest.Link

			// 解析发布时间,如果 RSS 解析器本身给出了 PublishedParsed 直接用,否则尝试解析 Published 字符串
			pubTime := time.Now()
			if latest.PublishedParsed != nil {
				pubTime = *latest.PublishedParsed
			} else if latest.Published != "" {
				if t, e := parseTime(latest.Published); e == nil {
					pubTime = t
				}
			}
			fr.ParsedTime = pubTime
			fr.Article.Published = pubTime.Format("02 Jan 2006")

			resultChan <- fr
		}(link)
	}

	// 开启一个goroutine等待所有抓取任务结束后,关闭resultChan
	go func() {
		wg.Wait()
		close(resultChan)
	}()

	// 用于统计各种问题
	problems := map[string][]string{
		"parseFails":   {}, // 解析 RSS 失败
		"feedEmpties":  {}, // 内容 RSS 为空
		"noAvatar":     {}, // 头像地址为空
		"brokenAvatar": {}, // 头像无法访问
	}
	// 收集抓取结果
	var results []feedResult

	for r := range resultChan {
		if r.Err != nil {
			errStr := r.Err.Error()
			switch {
			case strings.Contains(errStr, "解析RSS失败"):
				problems["parseFails"] = append(problems["parseFails"], r.FeedLink)
			case strings.Contains(errStr, "RSS为空"):
				problems["feedEmpties"] = append(problems["feedEmpties"], r.FeedLink)
			}
			results = append(results, r)
			continue
		}

		// 对于成功抓取的Feed,如果头像为空或不可用则使用默认头像
		if r.Article.Avatar == "" {
			problems["noAvatar"] = append(problems["noAvatar"], r.FeedLink)
			r.Article.Avatar = defaultAvatar
		} else if r.Article.Avatar == "BROKEN" {
			problems["brokenAvatar"] = append(problems["brokenAvatar"], r.FeedLink)
			r.Article.Avatar = defaultAvatar
		}
		results = append(results, r)
	}
	return results, problems
}

2.3 并发抓取 + 限流

为避免一下子开几十上百个协程导致阻塞,可以配合一个带缓存大小的 channel

maxGoroutines := 10
sem := make(chan struct{}, maxGoroutines)

for _, rssLink := range rssLinks {
    // 启动 goroutine 前先写入一个空 struct
    sem <- struct{}{}
    go func(link string) {
        // goroutine 执行结束后释放 <-sem
        defer func() { <-sem }()
        fetchFeedWithRetry(link, parser, 3, 1*time.Second, 2.0)
        // ...
    }(rssLink)
}

3. 对比旧版本的改进

  1. 容错率显著提升

    遇到网络抖动、超时等问题,能以10路并发限制式自动重试,很少出现直接拿不到数据

  2. 抓取速度更快

    以 10 路并发为例,对于数量多的 RSS,速度提升明显

  3. 日志分类更细

    分清哪条 RSS 是解析失败,哪条头像挂了,哪条本身有问题,后续维护比只给个403 Forbidden方便太多

  4. 支持 COS

    可将最终 data.json 放在 COS 上进行 CDN 加速;也能继续放在 GitHub,视自己需求而定

  5. 自动清理过期日志

    每次抓取后检查 logs/ 目录下 7 天之前的日志并删除,不用手工清理了

4. Go 生成的 JSON 和日志长啥样

4.1 RSS

抓取到的文章信息会按时间降序排列,示例:

{
  "items": [
    {
      "blog_name": "obaby@mars",
      "title": "品味江南(三)–虎丘塔 东方明珠",
      "published": "10 Mar 2025",
      "link": "https://oba.by/2025/03/19714",
      "avatar": "https://oba.by/wp-content/uploads/2020/09/icon-500-100x100.png"
    },
    {
      "blog_name": "风雪之隅",
      "title": "PHP8.0的Named Parameter",
      "published": "10 May 2022",
      "link": "https://www.laruence.com/2022/05/10/6192.html",
      "avatar": "https://www.laruence.com/logo.jpg"
    }
  ],
  "updated": "2025年03月11日 07:15:57"
}

4.2 日志

程序每次运行完毕后,把抓取统计和问题列表写到 GitHub 仓库 logs/YYYY-MM-DD.log:

[2025-03-11 07:15:57] 本次订阅抓取结果统计:
[2025-03-11 07:15:57] 共 25 条RSS, 成功抓取 24 条.
[2025-03-11 07:15:57] ✘ 有 1 条订阅解析失败:
[2025-03-11 07:15:57] - https://tcxx.info/feed
[2025-03-11 07:15:57] ✘ 有 1 条订阅头像无法访问, 已使用默认头像:
[2025-03-11 07:15:57] - https://www.loyhome.com/feed

5. 照葫芦画瓢

如果你也想玩玩 LhasaRSS

  1. 准备一份 RSS 列表(TXT):

格式:每行一个 URL<br/> 如果 RSS_SOURCE = GITHUB,则可以放在项目中的 data/rss.txt<br/> 如果 RSS_SOURCE = COS,就把它上传到某个 https://xxx.cos.ap-xxx.myqcloud.com/rss.txt

  1. 配置好环境变量:

默认所有数据保存到 Github,所以 COS API 环境变量不是必要的

env:
	TOKEN:                    ${{ secrets.TOKEN }}                    # GitHub Token
	NAME:                     ${{ secrets.NAME }}                     # GitHub 用户名
	REPOSITORY:               ${{ secrets.REPOSITORY }}               # GitHub 仓库名
	TENCENT_CLOUD_SECRET_ID:  ${{ secrets.TENCENT_CLOUD_SECRET_ID }}  # 腾讯云 COS SecretID
	TENCENT_CLOUD_SECRET_KEY: ${{ secrets.TENCENT_CLOUD_SECRET_KEY }} # 腾讯云 COS SecretKey
	RSS:                      ${{ secrets.RSS }}                      # RSS 列表文件
	DATA:                     ${{ secrets.DATA }}                     # 抓取后的数据文件
	DEFAULT_AVATAR:           ${{ secrets.DEFAULT_AVATAR }}           # 默认头像 URL
	RSS_SOURCE                ${{ secrets.RSS_SOURCE }}               # 可选参数 GITHUB or COS
	SAVE_TARGET               ${{ secrets.SAVE_TARGET }}              # 可选参数 GITHUB or COS
  1. 部署并运行

只需 go run . 或在 GitHub Actions workflow_dispatch 触发 运行结束后,就会在 data 文件夹更新 data.json,日志则写进 GitHub logs/ 目录,并且自动清理旧日志

注:如果你依旧想完全托管在 COS 上,需要把 RSS_SOURCE 和 SAVE_TARGET 都写为 COS,然后使用 GitHub Actions 去调度

相关文档

骑行开封

2025年2月20日 16:14

我对于开封的印象,还停留在开封府尹·包拯。处于好奇和无处可去的想法,周六早上吃完饭,说走就走了

到达开封鼓楼

这里就到达开封了开封·鼓楼,郑开大道的路上很轻松,室外温度17°+,小风微微的吹着,不冷不热好不痛快

Strava记录

在郑开大道单飞的过程中偶遇骑友,王哥是开封本地的,骑行的路上跟我聊开封哪里好玩,哪里最具性价比,把我领进开封鼓楼后,又带我在景区逛了一圈带我认路,在此感谢大哥

与王哥的合照

早饭吃的比较仓促,真的很饿,在书店街附近买了些吃的

干饭

本来是想在开封呆一天,晚上去清明上河园玩,想到公司有事就提前回去了,怕耽误明天的行程

鼓楼合影

郑开大道

这次跨市骑行急了一些,时间太紧张了!再过几天休息,我想回一次家,骑行约200KM

Strava记录

博客功能更新 2025 (2)

2025年2月6日 13:03

Update details

  • 移除红灯笼
  • 新增 sitemap.xmlsitemap.txt,自动生成,不再手动更新!

之前我一直使用 <a href="https://www.xml-sitemaps.com" target="_blank">xml-sitemaps</a> 手动生成sitemap.xml,但每当 URL 新增或变更都需要手动提交。实在麻烦!所以,今日用 Liquid 实现自动生成,一劳永逸

sitemap.xml 优化策略

  • 首页优先级最高 (1.0),其他页面次之 (0.8)
  • 新文章优先级高(30 天内 0.9,半年内 0.8,一年内 0.6),让新内容更容易被搜索引擎收录
  • 旧文章优先级降低(1 年以上 0.4,2 年以上 0.2),减少搜索引擎对老旧内容的爬取
  • 动态调整 changefreq,确保新内容频繁爬取,而老文章爬取频率降低
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  {% assign now = site.time | date: "%s" | plus: 0 %}
  
  {% for page in site.pages %}
    {% if page.url == "/" %}
    <!-- 首页优先级最高 -->
      {% assign page_priority = "1.0" %}
    {% else %}
      {% assign page_priority = "0.8" %}
    {% endif %}
    
    <url>
      <loc>{{ site.url }}{{ page.url | replace:'index.html','' }}</loc>
      <lastmod>{{ site.time | date_to_xmlschema }}</lastmod>
      <changefreq>weekly</changefreq>
      <priority>{{ page_priority }}</priority>
    </url>
  {% endfor %}
  
  <!-- 根据发布时间动态调整 priority 和 changefreq -->
  {% for post in site.posts %}
    {% assign post_time = post.date | date: "%s" | plus: 0 %}
    {% assign diff = now | minus: post_time %}
    {% assign days_old = diff | divided_by: 86400 %}
    
    {% if days_old < 30 %}
      {% assign priority = "0.9" %}
      {% assign changefreq = "daily" %}
    {% elsif days_old < 180 %}
      {% assign priority = "0.8" %}
      {% assign changefreq = "weekly" %}
    {% elsif days_old < 365 %}
      {% assign priority = "0.6" %}
      {% assign changefreq = "monthly" %}
    {% elsif days_old < 730 %}
      {% assign priority = "0.4" %}
      {% assign changefreq = "yearly" %}
    {% else %}
      {% assign priority = "0.2" %}
      {% assign changefreq = "never" %}
    {% endif %}
    
    <url>
      <loc>{{ site.url }}{{ post.url }}</loc>
      <lastmod>{{ post.date | date_to_xmlschema }}</lastmod>
      <changefreq>{{ changefreq }}</changefreq>
      <priority>{{ priority }}</priority>
    </url>
  {% endfor %}
</urlset>

sitemap.txt 兼容旧版爬虫

sitemap.txt 适用于不支持 XML 的搜索引擎(如某些旧版爬虫)

---
layout: null
permalink: /sitemap.txt
---
{% for page in site.pages %}
{{ site.url }}{{ page.url | replace:'index.html','' }}
{% endfor %}

{% for post in site.posts %}
{{ site.url }}{{ post.url }}
{% endfor %}

在 robots.txt 里声明 Sitemap

确保搜索引擎能找到 Sitemap,需要在 robots.txt 文件中声明 sitemap.xmlsitemap.txt

User-agent: *
Allow: /

User-agent: MJ12bot
Disallow: /
User-agent: AhrefsBot
Disallow: /
User-agent: SemrushBot
Disallow: /
User-agent: dotbot
Disallow: /

Sitemap: https://lhasa.icu/sitemap.xml
Sitemap: https://lhasa.icu/sitemap.txt
❌
❌