2026-01-07 20:12:00
熟悉我的朋友都知道,我是一个懒人。懒得说话,懒得吃饭
所以像我这样的人,按理说对运动是没什么兴趣的
但耐不住压力大,我急需一种方式来发泄情绪
毕竟,人是可以活活憋死的

我之前的发泄方式主要是骑车,但现在碳轮坏了,暂时没钱换,于是便转向了登山徒步
不可否认,我非常喜欢这种方式,但徒步太耗时间了,我不可能每天都拨出大半天做这件事
至于跑步,我没想过。仅有的尝试也纯粹是出于好奇。可以说,这辈子我跑过的次数,一个巴掌就能数得过来

2025年12月31日,早晨七点。我实在憋不住了,穿上体能服直接冲了出去
跑出一公里后,我有些后悔没戴帽子和手套。苏堤两岸吹来的冷风冻得我头皮发麻
跑到岳湖附近时,想吐。我手撑着树干呕了几分钟,深感不适,当场掉头慢跑回家了,全程九公里
纯菜逼,回到家后,连饭都吃不下
第二天,也就是 2026 年的第一天。杭州下了一场暴雨,我便没出门

如果不算年前的那次,从 1 月 2 号到今天,是我连续晨跑的第六天
以前我习惯通宵写代码,而现在为了凌晨能跑起来,作息也调整了
不管手头有什么几把事,统统往后排,每天 12 点前必须睡觉

由于晚饭吃得少(或者干脆不吃),凌晨醒来时明显的饿
所以出发前,我都会冲点麦片。这东西挺容易消耗,有饱腹感,即便吃半碗也不影响马上跑步

插播一个有意思的小插曲:跑步的第二天,经过湖滨银泰时,我貌似和马云擦肩而过
当时我俩距离不过半米,几秒钟后我才反应过来,那个人很可能是他
毕竟马云的相貌还是很出众的,有一定辨识度,可惜当时光线差,他还戴着帽子
回家后我特意查了他近期的行程,啥也没搜到
但如果他当时在杭州,我敢说大概率是他
因为马云一直有户外运动的爱好,之前在杭州爬山、骑车也常被网友偶遇

闻子状雷峰,老僧挂偏裻。日日看西湖,一生看不足
—— 张岱《雷峰塔》
今天的天气极好,跑过苏堤时,恰好瞥见了西湖十景之七的“雷峰夕照”
可惜当时站位欠佳,视线被桥两侧的树木挡了大半
不过我也没打算专门跑到桥下去取景,毕竟我是出来跑步的,不是来搞摄影的
日志里的这些照片,每张的拍摄停留时间都不会超过 5 秒
因为我我习惯在跑步过程中提前打开相机,停下的瞬间,让远景对准参考线便按下快门,不过一瞬间
其中不乏有运动过程中拍下的照片,所以有些画面难免会有重影或模糊

实际上我这几天的跑步,都没怎么在中途停下过,也就是所谓的不间断
骑行更是如此,即便是两百公里,我也宁愿在车上慢踩,而不会选择停下

当然,凡事皆有意外
昨天跑得确实有点过火,到 11 公里时,左腿膝盖突然刺痛难忍
眼看只剩最后两公里,我实在不想放弃,便拖着左腿蹦着跑,那种刺痛感真是钻心刺骨,疼得我直咬牙
最后还是认怂了,不过我发现只要不刻意发力,刺痛感就不会有,于是快走了两公里。回到家一看配速:6:13,绝了

5 号那天是我人生第一次“雨跑”
刚出门,万松岭的路面就已经全湿了,手掌伸出去能感觉到细密的水滴
就这点降水量想挡住我跑步?门都没有
毕竟我衣服都穿好了,脱是不可能脱的

雨跑给我的真实感受其实就两个字:湿、重
汗水夹杂着雨水,上半身很快就湿透了,体感并不算舒服
我想,产生这种不适感,也许是因为跑步时注意力还不够集中
不像骑行,如果你行驶中分神,就有可能出事故
所以骑行时人的感官会高度聚焦,从而忽略身体的琐碎不适
显然,我跑步还没达到那种境界

除了跑步,最近我还尝试着自己动手做饭
凌晨 5-6 点起床,跑完步刚好是早餐时间
到家后的第一件事通常是点根烟暖暖身,如果出汗多就洗个澡,然后从冰箱拿点能吃的搞一搞
我的早餐口味偏清淡。比如这盘西兰花,什么调料都不放,开水烫一下就可以出锅

南德调味料,河南的老乡应该都很熟悉,家乡话叫“南呆”
这大概是我早餐里口味最重的一道菜了,再油腻的我也吃不下
炒鸡蛋出锅时,高压锅里的米粥也熬好了。趁热再去微波炉里热个馒头,一顿饭就齐活了
在盛出米饭前还要去微波炉热一个馒头
大概就是这样

2025-12-31 23:22:00
跨年前夜,刷到贾跃亭发视频唱了一首北京北京,多少是想家了,希望他下周能回国吧
点开评论区,还是一个吊样:嘲讽和谩骂。我就纳闷了,骂他究竟能让你舒服多少?
有人指责他财务造假。说句不中听的,就这个环境,大到顶层建筑,小到微企个体,谁没做过一些违背良心的事?区别无非在于,有的你可以随便骂,有的你一句话就成了冒犯君主不敬之罪
还有人替供应商鸣不平,“他们收不到货款,哪里错了?” 真鸡巴扯淡,既然选择创业,就得承担风险。你作为下游供应链,和企业就是一根绳上的蚂蚱,这是何尝不是投资?而不是去餐馆吃饭,不满意就说菜难吃
就这一点,和那些把买房当理财的人有什么区别?
涨了炫耀,跌了骂娘,骂开发商、骂社会不公,甚至媒体曝光,集体诉讼。就好像风险只该由别人承担。这种逻辑,本身就很扯淡
看着这些评论,我只想笑
就单论一个人创业十余年,面对失败,面对别人的冷嘲热讽,仍选择向前走,你有这个魄力吗?
这还不算个爷们?
换位思考,把你放在贾跃亭的位置,给你三个选项,你会怎么做?你还会坚持自己的梦想吗?
2025-12-28 08:19:00
从 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 的真实源文件:
「时隔六年,再次为热爱脱皮。痛苦如影随形,却也因此更加坚定」
2025-12-23 07:58:00
每次给博客搞拆迁,最让人割舍不下的,往往不是瓦片和墙皮,而是那些被我用到形成肌肉记忆的家具。由于Jekyll 和 Astro 说着不同方言,导致家具根本拿不到“异地安置指标”,我只能含泪签字
Photosuite 正是在这样的背景下诞生的
它由 Vite + Typescript 开发,拥有我博客图像的核心能力,包括:
灯箱
EXIF 展示
图片说明
图片路径自动补全

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

Photosuite 利用 Remark 和 Rehype 插件生态,在 Markdown 编译阶段完成图片处理,避免在运行时增加负担:
- 构建期
├→ Remark:补全图片 URL
└→ Rehype:读取图片 EXIF 并写入 HTML
↓
- 运行期
├→ photosuite(opts) 入口:按需动态 import
├→ glightbox 模块:把图变成可点击灯箱
├→ imageAlts 模块:用 alt 自动生成 caption
└→ exif 模块:加载 EXIF 样式,清理空条
如前所述,Photosuite 的首要职责是补全图片 URL
设计目标很简单:在 Markdown 中只写文件名,其余交给 Photosuite 处理:
整体思路如下:
- 使用标准 Markdown 语法插入图片,只填写文件名
例:
- 配置一个基础 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
- 稍作调整即可实现按年 / 月分类等更复杂的目录结构。
实现逻辑:
image 节点../)url 属性EXIF 本身并不是 Photosuite 的核心创新点,毕竟,也不是我实现的
我只是在 HTML 生成之前,我通过 exiftool-vendored.js 提取图片参数,并将其以文本形式注入到 DOM 中,仅此而已,零 JS 成本、零性能开销
遍历 HTML AST → 找到 img → 解析图片 → 提取 EXIF → 重写节点结构
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 也不会出现在页面中
实现逻辑:
检查配置中的 scope(作用域选择器)是否存在于页面中
根据功能开关配置:
enableLightbox, enableAlts, enableExif 并行、动态 import() 对应模块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 开发,理论上适用于多种架构
如果您在配置中遇到任何问题,欢迎联系我,为爱发电,无偿奉献
如果 Photosuite 对您有帮助,欢迎在 GitHub 点个 ⭐️
它不会让代码跑得更快,但会让我写得更勤快
PS:如果您正在使用 Photosuite
欢迎告诉我您的博客地址,我会把它展示在项目页面中
2025-12-02 01:40:00
有些年头没摸相机了。上一部相机还是 2019 年买的“佳能 EOS 200D”,那时 Fooleap 还没有退网,我还在做程序员 《我的第一部单反相机》
一晃眼,六年过去了。一切都变了,我刚入圈时,Fooleap 还在为哄女朋友而苦恼,现在孩子都会打酱油了。当初认识的博友大多已停更,中间我也曾断过更。不过后来因为喜 欢上户外活动,又把博客捡了起来。现在的博客大部分内容被户外生活占据,也是我持续更新的动力
前天,我把 EXIF 样式重写了,但图中的照片画质实在太烂,这对于一个完美主义者来说是无法接受的。一时间想到了相机,早年那台佳能 200D 因为闲置太久早已出掉, 看了些博主评测,最终圈定了两款:“佳能 R50”和“尼康 Z30”。因为 R50 阉割了热靴接口,我选择了尼康 Z30

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

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

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



实际试拍了不少照片,全程 M 档,不是过曝就是欠曝。晚上去西湖大道的桥上拍车流,又没控制好,画面还是过曝了
先这样吧,接着回 B 站看摄影教程去了
2025-11-26 23:23:00
我一直觉得 Artalk 的 ui 设计的很不协调,特别评论框下方的“ 评论数、通知中心"
这并非重要功能,但是官方没有做开关控制。实现后,感觉很实用,可以推一下
$ git remote remove upstream
$ git remote add upstream https://github.com/ArtalkJS/Artalk.git
$ git remote -v
origin [email protected]:achuanya/Artalk-ui.git (fetch)
origin [email protected]: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'
$ 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: [email protected]: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 这是我复制的备份配置,用处不大,删了然后推走
$ 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 官方已经断更几年了,审核过与否都不重要,就当玩了