MoreRSS

site iconlhasa | 游钓四方修改

千禧年生,长途骑行、野钓路亚、振出并继、古典乐、摇滚、布鲁斯、茶叶爱好者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

lhasa | 游钓四方的 RSS 预览

晨跑,破六

2026-01-07 20:12:00

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

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

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

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

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

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

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

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

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

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

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

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

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

跑步的第二天,差点破六

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

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

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

 咖啡 + 牛奶 + 燕麦

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

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

凌晨 6 点 13 分的湖滨路

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

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

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

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

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

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

雷峰夕照

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

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

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

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

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

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

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

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

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

当然,凡事皆有意外

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

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

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

凌晨雨跑,环西湖

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

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

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

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

白堤

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

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

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

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

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

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

开水煮西蓝花

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

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

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

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

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

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

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

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

在盛出米饭前还要去微波炉热一个馒头

大概就是这样

破六

聊聊贾跃亭

2025-12-31 23:22:00

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

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

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

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

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

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

这还不算个爷们?

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

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

我的博客 2025

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 的真实源文件:

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

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

2025-12-23 07:58:00

每次给博客搞拆迁,最让人割舍不下的,往往不是瓦片和墙皮,而是那些被我用到形成肌肉记忆的家具。由于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
欢迎告诉我您的博客地址,我会把它展示在项目页面中

尼康 z30 开箱小记

2025-12-02 01:40:00

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

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

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

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

下单送了四样东西:

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

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

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

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

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

我的第一次 GitHub PR

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'

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:   [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 这是我复制的备份配置,用处不大,删了然后推走

使用 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 官方已经断更几年了,审核过与否都不重要,就当玩了