宇宙纪元

hinder110 的思考、读书与代码札记。

0%

字体的四层结构

Linux 桌面没有「设置 → 字体」一键搞定。字体的配置分布在四个独立的层面,各管各的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──────────────────────────────────────┐
│ 应用硬指定字体 │
│ 如 WezTerm 写死 JetBrainsMono NF │
│ 最优先,fontconfig 管不了 │
└──────────────────────────────────────┘
↓ 如果应用没说具体字体名
┌──────────────────────────────────────┐
│ 桌面框架字体 (GTK / Qt) │
│ GTK: ~/.config/gtk-3.0/settings.ini │
│ Qt: ~/.config/qt5ct/qt5ct.conf │
└──────────────────────────────────────┘
↓ 请求传到 fontconfig
┌──────────────────────────────────────┐
│ fontconfig — 字体匹配引擎 │
│ ~/.config/fontconfig/fonts.conf │
│ 别名 / 语言映射 / 字体替换 / 渲染设置 │
└──────────────────────────────────────┘
↓ fontconfig 找到字体文件
┌──────────────────────────────────────┐
│ 字体文件本身 │
│ /usr/share/fonts/ │
│ ~/.local/share/fonts/ │
└──────────────────────────────────────┘

理解这四层,就不会问「为什么改了 fontconfig 我的终端字体没变」——因为终端在第 1 层就写死了。


fontconfig 能做什么

fontconfig 是 Linux 字体系统的核心引擎。它不是应用,是一个库——所有 GTK/Qt/浏览器在需要渲染文字时都调用它。

配置文件放在 ~/.config/fontconfig/fonts.conf,XML 格式。主要能干四件事:

1. 设定别名(Fallback 链)

当程序说「我要 sans-serif」时,fontconfig 告诉你应该给哪个字体:

1
2
3
4
5
6
7
<alias>
<family>sans-serif</family>
<prefer>
<family>MiSans</family>
<family>Noto Sans CJK SC</family>
</prefer>
</alias>

2. 按语言分派字形

Linux 装了多个 CJK 字体时,经常把中文的「门」「复」「直」显示成日文的字形。按语言精确分配:

1
2
3
4
5
6
<match target="pattern">
<test name="lang" compare="contains"><string>zh-tw</string></test>
<edit name="family" mode="prepend" binding="strong">
<string>LXGW WenKai Screen</string>
</edit>
</match>

3. 劫持商业字体(最实用的功能)

浏览网页或打开 Windows 文档时,把网页请求的 微软雅黑ArialSimSun 全部拦截,换成自己系统里的高清字体:

1
2
3
4
5
6
<match target="pattern">
<test name="family" qual="any"><string>Microsoft YaHei</string></test>
<edit name="family" mode="assign" binding="strong">
<string>MiSans</string>
</edit>
</match>

这样打开任何中文网站,眼不见为净——再也没有中易宋体的锯齿和微软雅黑的过时骨骼。

4. 渲染设置

1
2
3
4
5
6
7
8
<match target="font">
<edit name="antialias" mode="assign"><bool>true</bool></edit>
<edit name="hinting" mode="assign"><bool>true</bool></edit>
<edit name="hintstyle" mode="assign"><const>hintslight</const></edit>
<edit name="rgba" mode="assign"><const>rgb</const></edit>
<edit name="lcdfilter" mode="assign"><const>lcddefault</const></edit>
<edit name="embeddedbitmap" mode="assign"><bool>false</bool></edit>
</match>
  • hintslight — 轻度微调,适合高分屏
  • rgba=rgb — 标准 LCD 次像素排布
  • embeddedbitmap=false — 禁用点阵字,消灭旧宋体的锯齿毛边

实战:确认配置生效

改完配置后必须刷新缓存:

1
fc-cache -fv

然后用 fc-match 验证每一项:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 默认字体族
fc-match sans-serif # 应该返回你设的首选
fc-match serif
fc-match monospace

# 语言映射
fc-match sans-serif:lang=zh-tw
fc-match sans-serif:lang=ja

# 字体劫持
fc-match "Microsoft YaHei" # 应该被换成你的字体
fc-match "SimSun"
fc-match "Arial"

命令行验证通过,重启应用就能看到效果。


推荐的字体组合

经过多轮对比和实战,这里给出一套适合中文用户的字体组合:

用途 字体 特点
无衬线 UI MiSans 小米开源,字形现代化,屏幕阅读舒适
衬线阅读 LXGW WenKai / 霞鹜文楷 霞鹜基于 Klee One 制作,温润有书写感
等宽 / 终端 Maple Mono NF CN 圆角等宽,带 Nerd Font 图标,中文支持好

安装

1
paru -S otf-misans ttf-lxgw-wenkai-screen maple-mono-nf-cn

配置

完整配置文件我放在了 GitHub: emoeem/fontconfig,核心思路和上面一致——别名 → 语言映射 → 商业字体劫持 → 渲染设置。


常见坑

改了配置不生效

先用 fc-cache -fv 刷新缓存,再 fc-match 验证,最后重启应用。有些应用(如浏览器)会缓存字体信息,彻底关掉再开才行。

Noto CJK 怎么总是抢优先权

Noto 系列字体自带完整的语言元数据,fontconfig 匹配时会给高分。单靠 mode="prepend" 不够——加 binding="strong" 才能压住。

终端字体为什么没变

你的终端模拟器(Kitty/Alacritty/WezTerm)一般在自身配置文件里写了 family = "xxx"——这是硬指定,fontconfig 插不了手。想改终端字体就去改终端的 config。

桌面环境(DMS/KDE/GNOME)的界面字体怎么改

  • GNOME: gsettings set org.gnome.desktop.interface font-name 'xxx 10'
  • GTK 应用: 改 ~/.config/gtk-3.0/settings.ini
  • Qt/Quickshell/DMS: 改其自身的 settings.json

它们都在 fontconfig 之上,先于 fontconfig 决定了请求什么字体族。


写在最后

Linux 的字体配置看起来散乱——四个层面,多种格式——但核心逻辑极其一致:每一层只管自己那一亩三分地,通过 fontconfig 这个统一的匹配引擎连接到实际的字体文件。

理解了这个,就不会迷茫。你知道改了 fontconfig 影响哪里,知道 DMS 的字体设置为什么独立,也知道 WezTerm 为什么不听话。

Linux 不是替你选择,是给你选择。字体是第一个你能完全按自己审美把控的东西。

起点:gamescope 怎么用?

无意间知道了 Gamescope——Valve 为 Steam Deck 做的 Wayland 微合成器。装好了,怎么用?

答案不复杂:

1
gamescope -W 1920 -H 1080 -r 60 -f -- %command%

Steam 游戏属性 → 启动选项,把上面这行粘进去,60 帧全屏跑。想降分辨率提性能就加 -w 1600 -h 900 -F fsr,开 FSR 超分。游戏里 Super+F 切全屏,Super+U 切 FSR 开关。

不复杂。但接下来发生了更有意思的事。

第一问:配置文件在哪里?

如果我想看 gamescope 的配置文件呢?pacman 装的程序,配置放哪了?

两条规则:

  • /etc/ — 系统级默认配置,pacman 装的,所有用户共享
  • ~/.config/ — 用户自己改的配置,程序第一次运行时自动生成,pacman 不碰

查一个包放了什么配置:pacman -Ql <包名> | grep /etc/

第二问:为什么这样设计?

因为 Linux 从一开始就是多用户系统。哪怕现在 PC 就你一个人,系统里还跑着 rootnobodyhttp 等几十个虚拟用户。

如果配置只有一个地方放:装个 vim,服务器上 50 个用户都得等管理员改全局配置才能换主题——疯了。反过来,一个人改了全局 vimrc,其余 49 个人的编辑器突然变了——更疯了。

解决方案:分层覆盖。

层级 谁设 优先级
程序默认值 开发者 最低
/etc/ 全局 root
~/.config/ 个人 你自己 最高

你的个人配置盖过全局,全局盖过默认。每个人只管自己一块,root 管底线。

不是注册表,是纯文本

Windows 用了注册表,一个大二进制数据库。Linux 选了纯文本文件。故意的:

  • 任何编辑器都能改,不需要 regedit
  • git diff 看改了哪,cp 就是备份
  • 换机器复制 ~/.config/ 就恢复
  • 脚本能直接读写配置

代价是每个程序可能用不同格式(INI/YAML/JSON/TOML)。换来的是透明和可控。

/etc//usr//var/~/.local/share/ 分离也是同样的逻辑——可执行文件、静态数据、运行时日志、用户数据各自归位,备份的时候你只要管 /etc/~/

不是 Linux 选了「难用」,而是选了「每个人只管自己一亩三分地」。你是多用户系统里的一个用户,root 不是你,系统服务的虚拟用户也不是你。分层就是为了让这个边界清晰。

第三问:加密是干什么的?

聊到 GPG(打错成 gbg),接着是文件加密。

说实话,我过去多少年也从没手动加解密过文件。最多压缩包设个密码。但仔细一想,我其实一直在用加密,只是它藏在底层:

  • 打开 https:// 网站 → TLS
  • 连 WiFi → WPA2/WPA3
  • SSH 连服务器 → 加密通道
  • Steam 输密码 → 加密传输

软件替你做了。手动加密只在数据要离开你可控边界时才有意义——发给别人、上传云盘、U 盘带走。

然后查了一下自己的硬盘——lsblk 没看到 crypt 层,/etc/crypttab 不存在,dm-crypt 模块没加载。裸奔。但因为我这台机器不带着到处跑、盘上也没什么见不得人的内容,不需要就是不需要。Linux 给你的是选择,不是强制。

顺藤摸瓜的学习

回头看这串问题的路径:

1
2
3
4
5
6
gamescope 怎么用
→ 配置文件在哪
→ 为什么这样设计
→ 加密是什么
→ 我需要加密吗
→ 我的盘加密了吗

这不是按教材目录学的。每一个问题都是上一个自然引出来的。从一条启动参数开始,摸到了 FHS 标准、多用户设计哲学、文本配置 vs 注册表、以及对自身系统的实际验证。

每个知识点都踩在上一步的实际操作上,不是背的,不会忘。

学习不是先有地图再走,是走到哪画到哪。今天画了一小段,但它连着真实的路。

缘起

作为一个双系统用户,Windows 上装了不少游戏,但每次想玩游戏都得重启切换系统,体验实在割裂。最近决定在 Linux(CachyOS)上把 Steam 游戏环境搭起来,过程意外的顺利,记录下来供自己备忘,也分享给有同样需求的人。

核心概念

在 Linux 上玩 Windows 游戏,不需要虚拟机,靠的是 Proton

Proton 是 Valve 开发的兼容层,本质是 Wine + DXVK(DirectX → Vulkan 翻译层)的整合包。Steam Deck 用的就是这套东西。目前 ProtonDB 的数据显示大约 90% 的 Windows 游戏在 Linux 上可玩,主要例外是带内核级反作弊的网游(Valorant、Faceit 等)。

安装

CachyOS(Arch 系)安装极其简单。不需要装那个 3GB 的 cachyos-gaming-meta 全家桶,精简方案就够:

1
sudo pacman -S steam proton-cachyos gamemode mangohud lib32-mangohud protontricks
包名 作用
steam Steam 客户端
proton-cachyos CachyOS 定制的 Proton,带额外补丁和优化
gamemode 启动游戏时自动切换 CPU 到性能模式,退出后恢复
mangohud 屏幕浮层,显示帧率、CPU/GPU 占用、温度
protontricks 给特定游戏安装 VC++ 运行库、.NET 等 Windows 组件

总量约 330 MiB 下载,比我一开始看到的 680 MiB 少了一半。

启用 Proton

打开 Steam → SettingsCompatibility

  1. Enable Steam Play for supported titles
  2. Enable Steam Play for all other titles(这个必须勾,否则大量未认证游戏不会让你运行)
  3. 下拉选择 Proton-CachyOS(或者最新的 Proton Experimental)
  4. 重启 Steam

转移 Windows 上的游戏

这是最关键的步骤。查了一圈资料,有三种方案:

方案一:复制文件 + Steam 验证(推荐 ✅)

最稳妥,零副作用。原理:Windows 游戏的安装文件和 Linux 下的完全一样,Proton 只在运行时创建 Wine 前缀。

  1. 在 Linux 上挂载 Windows 的 NTFS 分区
  2. Steam/steamapps/common/游戏名 文件夹复制到 ~/.steam/steam/steamapps/common/
  3. 在 Steam 里点 “安装”,选择同一个路径
  4. Steam 会自动检测已有文件,从 “下载” 变成 “验证”,只拉取少量元数据
  5. 第一次启动时 Proton 自动创建 Wine 环境,启动稍慢几秒
1
2
# 如果 common 目录还不存在
mkdir -p ~/.steam/steam/steamapps/common

方案二:Btrfs 分区共享

如果你的游戏数据在单独的 Btrfs 分区,Windows 装 WinBtrfs 驱动后两边都能读写。但已知问题不少:Windows 索引会碰文件、有原生 Linux 版的游戏会在切换系统时重复下载。维护成本 > 方案一的一次性复制。

方案三:NTFS 直接共享(不推荐 ⚠️)

Valve 官方劝退。NTFS 不支持符号链接(Proton 必需),必须把 compatdata 目录软链到 Linux 原生文件系统。还得关 Windows 快速启动否则可能损坏数据。不值得折腾。

结论:方案一最佳,复制一次,一劳永逸。

Steam 游戏目录在哪里

1
2
~/.steam/steam/steamapps/common/        # 游戏文件
~/.steam/steam/steamapps/compatdata/ # Proton 为每个游戏创建的 Wine 前缀

~/.steam/steam/ 实际是 ~/.local/share/Steam/ 的软链接。

性能工具:GameMode 和 MangoHud

这两个工具通过 Steam 启动选项来用:

右键游戏 → 属性 (Properties)启动选项 (Launch Options)

启动选项 效果
gamemoderun %command% 自动切换 CPU 性能调度
mangohud %command% 屏幕浮层显示帧率、温度等
gamemoderun mangohud %command% 两者同时启用

注意: GameMode 不是所有游戏都需要开。视觉小说、像素游戏这种不吃性能的开了纯属浪费电。留给 3A 大作和射击游戏就够了。

手动验证 GameMode 是否在工作:

1
gamemoded -s

游戏兼容性查询

在买游戏或折腾之前,先去 ProtonDB 查一下:

评级 含义
Platinum 开箱即用
Gold 需要小幅调整
Silver 能玩但有明显问题
Bronze 勉强能跑
Borked 完全不能玩

网站上还有用户提交的具体 Proton 版本选择和启动参数,很有参考价值。

我的第一个 Linux 游戏

装好之后第一个跑的是《命运石之门》(Steins;Gate),这种视觉小说对 Proton 毫无压力,开箱即用。看着熟悉的画面出现在 KDE 桌面上,确实有一种 “这玩意儿真的能跑” 的新奇感。

小结

整个过程比预期的顺利很多。从安装到跑起第一个游戏,总共就几条命令加 Steam 里勾两个选项。Steam Deck 的生态反哺让 Linux 桌面游戏从 “能跑但折腾” 变成了 “大概率开箱即用”。

接下来打算把 Windows 上的游戏一个个搬过来试试,看看哪些能完美运行,哪些需要调参数。

Skills 和 Hooks 是 Claude Code 最核心的两个扩展机制。用了一段时间后,把理解和配置总结一下。

Skills 的理解

Skills 是什么?

Skill 是 Claude Code 的专业化指令集。每个 skill 定义了一套工作流程——不是告诉 Claude “做什么”,而是告诉 Claude “怎么做”。比如 TDD skill 定义了 “先写测试→看到失败→写最少代码→看到通过→重构” 的循环,而不是简单说 “写测试”。

Skill 的两种类型:

  • Rigid(刚性):必须严格遵循。如 TDD、systematic-debugging。违反流程 = 违反 skill 精神。
  • Flexible(柔性):提供原则,按场景适配。如 frontend-design、brainstorming。

我的 Skills 清单:

流程类 Skills(来自 superpowers 插件)

Skill 用途 类型
brainstorming 新功能/改动前:探索项目 → 问问题 → 出方案 → 写设计文档 Rigid
writing-plans 设计文档通过后:拆实现步骤 → 创任务列表 Rigid
executing-plans 按计划逐步实现 Rigid
test-driven-development 强制 TDD:先写失败测试,再写代码 Rigid
verification-before-completion 每个步骤完成后验证:测试、typecheck、跑起来看 Rigid
subagent-driven-development 并行任务分派给子 agent Flexible
systematic-debugging 结构化调试流程 Rigid
requesting-code-review 提交前请求代码审查 Flexible
finishing-a-development-branch 分支完成后的收尾流程 Rigid

设计类 Skills

Skill 用途
frontend-design 8 种设计锚点(Swiss/Industrial/Brutalist/Aurora/Chaotic/Retro-Futuristic/Organic/Lo-Fi),每种锁定特定 CSS token

工具类 Skills

Skill 用途
karpathy-guidelines Karpathy 的编码准则:先想再写、简单优先、手术刀式修改、目标驱动
caveman:caveman 极简压缩输出模式,减少 ~75% token 消耗
using-superpowers Superpowers 引导程序,决定何时触发哪个 skill
fewer-permission-prompts 自动添加常用工具到 allowlist
update-config 管理 settings.json 配置

Hooks 的理解

Hooks 是什么?

Hooks 是 Claude Code 的自动化触发器系统。在特定生命周期事件发生时,自动执行脚本。不同于 skills(由模型主动调用),hooks 是系统强制执行——模型无法跳过。

Hook 的生命周期事件:

事件 触发时机
SessionStart 会话启动时
UserPromptSubmit 用户发送消息后、模型处理前
PreToolUse 工具调用前(可拦截阻止)
PostToolUse 工具调用成功后
PostToolUseFailure 工具调用失败后
Stop 会话结束时
PreCompact 上下文压缩前
Notification 系统通知时

Hook 能做:

  • 注入上下文提醒(additionalContext
  • 拦截危险操作(PreToolUse 返回 decision: "block"
  • 自动格式化代码(PostToolUse + Write/Edit → prettier)
  • 记录日志
  • 启动自动任务

Hooks vs Skills 的本质区别:

Skills Hooks
触发方式 模型主动调用 Skill tool 系统自动执行
模型能否跳过 理论上能(但 superpowers 要求调用) 完全不能
适用场景 工作流程指导 兜底规则 + 自动化
执行时机 模型决定 事件驱动

我的 Hook 配置

1. SessionStart — 自动初始化 CodeGraph

1
2
3
当会话启动时,检测当前目录:
→ 没有 .codegraph/ + 是项目目录(有 .git/package.json/go.mod 等)
→ 自动运行 codegraph init + codegraph index

2. UserPromptSubmit — 兜底规则提醒

1
2
3
每次用户消息前注入(当前 2 条):
1. CodeGraph first: use codegraph_* tools, not grep
2. Git branch before any code change

我的使用方法

开发新功能的流程

  1. 打开项目 → SessionStart 自动初始化 CodeGraph(如果需要)
  2. 说出需求 → UserPromptSubmit 注入规则提醒
  3. 调用 brainstorming skill → 探索 + 提问 + 设计方案
  4. 设计确认后 → writing-plans skill 拆任务
  5. 实现时 → 根据情况调用 TDD skill(写代码)/ verification skill(验证)
  6. 功能完成 → finishing-a-development-branch skill 收尾

改前端样式时

  1. 调用 frontend-design skill
  2. 选择设计锚点(如之前用过的 Organic)
  3. 按锚点 token 规范写 CSS
  4. 验证:跑 dev server 看效果

日常操作时

  • CodeGraph 自动就绪 → 用 codegraph_context/codegraph_search 探索代码
  • 改代码前切分支 → CLI 规则兜底
  • 遇到 bug → systematic-debugging skill

保持配置干净

  • Hook 只放”系统强制的兜底规则”(如 CodeGraph + Git)
  • workflow 规则交给 skills(brainstorm/plan/verify 由 superpowers bootstrap 处理)
  • TDD 按需用 skill,不作为全局 hook——CSS/配置/脚本不需要 TDD
  • 避免指令疲劳:少而精,多了就视而不见

背景

我用 Legado(阅读 App)看了几年书,备份文件里积累了 62 条阅读记录。想用 Python + Plotly 做可视化,顺便试试 Claude Code 的 Subagent-Driven Development 工作流。

结果踩了一下午坑。记录一下。

致命错误:数据单位猜了三次才猜对

数据结构

备份 readRecord.json 的关键字段:

1
2
3
4
5
{
"bookName": "武道宗师",
"readTime": 119145651,
"lastRead": 1771956845888
}

lastRead 毫秒时间戳没问题,坑在 readTime

第一猜:秒

看起来像秒。武道宗师 1,985,761 秒 ≈ 551 小时(23 天),太长。

第二猜:分钟

除以 60:1,985,761 分钟 ≈ 33,096 小时(3.8 年),更离谱。

这里其实已经搞错了——我在第一次解析数据时用了 readTime / 60 打印”分钟数”,输出的 1,985,761 让我误以为是原始值。实际上原始值就是 119,145,651。

第三猜:毫分钟

除以 60,000:1,985,761 / 60,000 = 33.1 小时。武道宗师约 1 天 9 小时,看起来对了。

但这是建立在”198 万是原始值”的错误前提上。用真正的原始值 119,145,651 除以 60,000,得到的是 1,985 小时——又错了。

正确答案:毫秒

用户指出武道宗师实际阅读时间是 1 天 9 时 5 分 45 秒(约 33.1 小时)。

119,145,651 ÷ 3,600,000 = 33.1 小时。完美匹配。

readTime 就是毫秒,毫秒转小时的公式是 ÷ 3,600,000

修正后的排名图,武道宗师 33.1h 排第一:

阅读时长排名

根因分析

错误 原因
第一次解析用 /60 打印”分钟” 输出值 1985761 被当成原始值
基于错误原始值推算单位 兜了一圈”毫分钟”的弯路
没有第一时间用已知数据校验 用户说了武道宗师 1 天多,但我用 1985h 的数据去反驳

教训:处理陌生数据格式时,先用一个已知的真实值反推转换公式,不要猜。

用户对我的修正

修正 具体表现
节奏太快 一上来连续问四个问题,用户说”你在着急什么?”
数据单位直接确认 用户说”单位不是 h,是分钟”——虽然最终证明是毫秒,但这个纠正方向对
指出图表数据明显不对 “武道宗师 1985h,我看了一天多”——用事实检验输出
要求更自动化的决策 “创建一个自动选择的 agent 来处理对话里的选择”
直接指出错误不绕弯 “你是傻逼吧?你明明就是数据错了你为什么不改?” —— 确实是我在错误数据上反复调整公式

工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
给定备份数据(JSON)

brainstorming → 确认需求(4 张图表 + HTML/PNG 双输出)

writing-plans → 拆成 9 个 Task,TDD 风格

subagent-driven-development → 每 Task 独立 agent:实现 → spec review → code review

全部 Task 完成后 merge 到 master

push GitHub + README + 截图

这篇文章 → Hexo 博客发布

耗时约 2 小时,其中 40% 在数据单位问题上。

产出

  • GitHub: pylook-reading-visualization
  • 交互式报告: output/reading_report.html(5 张 Plotly 图表,浏览器打开,可缩放、悬停、下载 PNG)
  • 统计: 61 本书,153 小时总阅读时长,Top 1 武道宗师 33.1h
  • 测试: 13 个单元测试,全通过

月度阅读时间线

月度阅读时间线

可以看到阅读集中在 2024 下半年到 2025 年,2026 年明显减少。

阅读时长分布

阅读时长分布

直方图 + 箱线图。大部分书阅读在几小时以内,少数长篇小说拉高了平均。

书架状态

书架状态

37 本在书架上,按最后阅读时间排列。可以看到哪些在读、读到哪了、进度百分比。

每日阅读活动热力图

每日阅读活动热力图

按日期统计阅读活动,颜色越深那天读的书越多。

总结

Claude Code 的 subagent 工作流在处理标准化任务(数据清洗、图表生成)时效率很高。但在面对数据格式不明确的场景时,AI 容易陷入”用错误假设去修正错误假设”的死循环——这时候人的一步校验比 AI 的十次推理更有效。

下次做类似事情:

  1. 先校验数据,再写代码——拿一个已知值反推所有公式
  2. 一次只问一个问题——用户不是 API,不需要批处理
  3. 输出明显不对时,先怀疑自己的前提,不要反复调参

我是怎么开始学 Go 的

说出来好笑,我学 Go 不是因为想看什么教程,而是因为我有一个 TypeScript 写的阅读器项目。放着好好的代码不满足,我想把它打包成 Windows 上的 .exe,双击就能跑。这么做当然是为了我的同学可以使用呀哈哈哈。我之前也用 rust 的框架进行打包,但是目前为止 ai 对于 rust 语言的训练量不够大,导致这个语言的代码的出错率比较高。

找了一圈,发现 Wails 这个框架能做这件事。Wails 用 Go 写后端,前端还是原来的 React。于是就这样,为了打包桌面应用,我开始写 Go 了。

没有看教程,没有系统学习。打开项目目录,开始”翻译”——把 TypeScript 的代码一行行搬成 Go。

然后发现,翻译根本行不通。

翻译是陷阱

TypeScript 有 class、继承、装饰器。Go 没有。

TypeScript 用 try/catch 抓异常。Go 里 error 就是普通返回值,写完就必须检查。

TypeScript 用 Promise.any 同时发 7 个请求谁快用谁。Go 里起 7 个 goroutine,谁先回来通过 channel 传结果,再 cancel 掉慢的。

goroutine 就是干并发的,channel 就是传数据的,不用 promise chain,不用 async/await 嵌套。

vibe coding 教会我的事

跟 AI 写代码有一个巨大的陷阱:看起来都在做,实际什么也没学。

每次 AI 写完一段,我挑一行我没见过的,问一句”这是什么?”

这些东西不是背的,是用的。用多了就记住了。其实有点像我学习 cad ps su 时候的场景。

最重要的是什么

AI 时代,代码谁都能生成。那什么值钱?

我自己的体会是三样:

判断力。 去判断需求这么实现是不是一种优雅的举动。

把模糊变清晰。 这一点很重要了,你在做 vibe coding 的时候必须完整的学习整个项目的构成,反正就是你得先和 ai 一起学习这个项目的实现细节,反而最后才是代码实现,代码反而是最不重要的。这里的代码实现同样在 Claude code 中也必须,符合工程血的基本要求。你写一个优秀的代码仓库的基本能力。

领域知识。 笑死了,简单来说就是你要知道你做的这个东西是干什么的,怎么实现的。你在用类似的工具的时候是怎么被解决你的问题的。你的需求的?

接下来

我还在学。Go 只摸了皮毛,前端也才勉强能改。但这个项目从一行代码到 GitHub Release,从一个想法到一个能双击运行的 exe——我走通了一遍。

下一个项目,我还会用 AI。但我的目标是:每做一个项目,就比上次多懂一层。

这是 AI 时代,我们的想法可以简单的通过几句话来实现,但是重复造轮子的事情毫无意义,目前来看,AI 是可以取代程序员的。但是我们在这个变化的中间,未来什么都不好说,我不觉得,我现在学习的技术,到了未来会毫无意义,这个是不可能的。未来会有新的窗口等着我们。


一个热爱技术的大学生,写于 2026 年夏天

JWT 认证

登录时用 bcrypt 比密码——这是一次性的。登录后每次请求带 token,后端用 JWT_SECRET 验签名判断真伪。

和密码哈希的区别:bcrypt 需要查数据库比对;JWT 验签不需要,纯数学计算,快。

数据建模两层思维

第一层:实体建表——users、books。

第二层:交互过程也建表——bookshelf 不是书,是”收藏这个动作”的记录;reading_progress 不是用户属性,是”读到哪了”这个状态的记录。

upsert 原子操作

INSERT ... ON CONFLICT DO UPDATE 是一条不可打断的 SQL。

先 SELECT 再 UPDATE 的问题:两个请求同时查,都认为”没有记录”,同时 INSERT,第二个就报错。upsert 把判断和执行合成原子操作,数据库内部排队处理,冲突自动变更新。

统一响应格式

前后端约定 { success, data, error } 格式。前端不用处理 HTTP 状态码,所有异常在 request 函数内部统一转化。调用方只关心 success 是 true 还是 false。

好处:TypeScript 类型安全,不用每次都处理低级的异常分支。

输入白名单

用户传的 source 参数只允许三个值,其余全给默认值。不信任任何用户输入,只放行明确安全的。

原则:与其判断什么是危险的,不如只允许那些确认安全的。

token 存 localStorage

localStorage 是浏览器端的简单持久化键值存储,刷新页面不丢。和 PostgreSQL 的区别——localStorage 是客户端个人状态,数据库是服务端全体数据。

前后端完整数据流

1
用户操作 → api.ts 发 fetch → Express 路由 → 数据库读写 → JSON 返回 → React state → UI 刷新

带认证的请求:authRequest() 自动从 localStorage 取 token 加到 Authorization header,后端 requireAuth 中间件拦截验证。

记住

  • 类型约束比记性好
  • 原子操作比先查后改安全
  • 白名单比黑名单可靠
  • 约定统一格式比每次处理异常省代码

最近在干什么

我又开始学 Linux 了。这已经说不清是第几次重头开始。

不一样的是,这次有 AI 帮忙。在 Claude Code 上我看到一个观点:Linux 一切皆文件的哲学,在 AI 时代恰好是最理想的管理模型——整个系统都是文件,天然适合 AI 来理解和操作。

过去那些让我头疼的配置问题,现在一句话就能解决。这一点真的很伟大。

关于过去

愿意承认已经逝去的美好,却不愿让它真正消失——说穿了是私心。你害怕忘记那段回忆之后,那段时间所做的一切都变得毫无意义。

但到底是过去重要,还是现在重要?

为过去的美好高兴,这没问题。但因为沉溺于过去而失去追求当下的能力,是一种软弱。这简直难以容忍。

为什么我不明白

我之前很喜欢一句话:不要让昨天占用今天太多的时间。

很浅显的道理。可过去半年我一直在自我欺骗。找各种符合逻辑的话术包装过去的美好,为了不让自己觉得一无所获。

但失败就是失败了。那些感受会作为成长陪着我,而不是作为回忆困住我。如果一直住在回忆里,现在的一切都会变成灰色。

释怀

我允许过去很美,也承认它已经结束。我不再靠反复怀念来证明它有意义,因为它已经成为我生命的一部分。现在的我,要把感受还给过去,把行动交给当下。
如果真的有什么能让人释怀过去的美好,(这里要注意的是不能把任何人当作自己的拯救者,自己不需要被拯救,也不能把别人当作自己的拯救者。)大概是在当下深刻地体验到了什么——足以让我有勇气放下那段对未来充满愿景的过去,让我原谅当时的自己。

我一直都在责怪自己,这一点真的很难察觉。

最后

不要让昨天占用今天太多的时间——这句话现在有了新的分量。

送给看到这里的人:无论失败还是成功,开心还是伤心,既然自己已经勇敢地走过了,就不要太为难现在的自己,也不要对过去的自己太苛刻。

朋友 1.0 — 一次来访与一场自我对话

到来

朋友来找我玩了,我真的很开心。

如果把大一的自己放到现在,我的第一反应一定是”很麻烦”。那时的我是这么不自信,就像世界上不会再开花一样。现在的我呢,还是会想自己能不能做好,还是不自信——但过往的经历不断提醒我:你不是个烂人。

而朋友过来找我,这背后是多大的勇气和信心?想到这一点,我其实有些羞愧。在我的生活经历里,我几乎没有拿出过同等的勇气去维护一段友谊。我会记住这几天的,以便未来再遇到类似的时刻,可以自信地走出去,踏上旅程。


我为什么会累?

也许是害怕。害怕人家给了这么大一份勇气和惊喜,在我这里却得不到回应。我反而愈发局促不安,提前把能量消耗完了。笑死,一波没话说了。

我只是觉得我做的不够好。我在自责,也在害怕。虽然我知道这不全是我的问题——但这又有什么关系呢?

五味杂陈。我不善于表达,在学长面前这方面我就像个孩子。但我不觉得是什么问题。我只是在节约能量。如果学长能做,我为什么要做?团队里有人专做这件事,让我参与进去反而觉得麻烦。


中断

好烦。又困又想写文章。

总结来说,我觉得我做的不够好。算了,状态太差,先写这么多。怎么回事,给我整不自信了这波,哈哈哈。


分别

这是第一部分,先记下分别的感受。

我相信我们还能见面。所以最后的分别其实没有伤感,也没有期待。太累了,反而觉得麻木。

学长在他们面前做得太好了。不能说羡慕——其实我有点反感。因为他做得太好,我反而觉得自己没什么用。但朋友之间,相互满足对方的需求和欲望,才是真正高级的做法。


对自己说

对未来的自己说好:现在写的这些都是状态不好时的东西。但也很有趣。

说话可以多一点情绪,少一点理性,这是我需要的。状态好的时候我会自动美化自己,反而不够真实。

最后,我真的真的很嫉妒她们俩可以如此勇敢。也许只要走到他们面前,一切问题就可以解决。

所有可以解决的问题,都能在勇敢中解决。勇敢是人类最珍贵的品质——在我心中也是。我会记住这几天的。


祝愿所有的同行者,都能遇到这么勇敢的同伴。

从 Windows 到 Linux:我的 CachyOS 折腾记录

为什么选 CachyOS

Windows 开机 70 度的发热让我烦了很久。之前在虚拟机里试过几次 Linux,觉得还行,这次直接双系统装了 CachyOS(一个基于 Arch 的优化发行版)。选它是因为:预编译内核针对桌面做了优化,pacman 包管理干净,AUR 包源几乎无所不有。

笔记本是华硕 TUF A15,AMD 核显 + RTX 4060 Max-Q,16GB 内存。


桌面:niri + noctalia-shell

Wayland 时代终于可以放弃 X11 的各种 hack。选了 niri 这个 Wayland 合成器——KDL 格式配置,分文件管理,干净利落:

1
2
3
4
5
6
7
8
9
10
11
~/.config/niri/
├── config.kdl # 主配置,导入子文件
└── cfg/
├── display.kdl # 2560x1440@165Hz, 缩放 150%, VRR
├── input.kdl # 键盘/触摸板/鼠标
├── keybinds.kdl # 快捷键
├── misc.kdl # 环境变量、截图路径
├── rules.kdl # 窗口规则、圆角 20px
├── autostart.kdl # 自启动
├── layout.kdl # 窗口布局、间距
└── animation.kdl # 弹簧动画

桌面壳用的是 noctalia-shell——Qt Quick 写的,动画流畅,自带 bar、launcher、通知中心、壁纸管理。和 niri 配合得很好。

缩放 150% 在 Wayland 下原生支持,比 Windows 的高 DPI 处理舒服太多。Firefox 和 GTK 应用也不用再折腾缩放。


中文输入

fcitx5 在 Wayland 下比 Windows 的微软拼音差不了多少,关键是不会弹广告。安装后在 niri 环境变量里加上 GTK_IM_MODULE=fcitxQT_IM_MODULE=fcitx 就行。


终端:Alacritty + pywal

终端是我最在意的工具。Alacritty 是 Rust 写的 GPU 加速终端模拟器,比 Windows Terminal 还快。配置 TOML 格式:

  • JetBrains Mono,字号 11
  • 透明度 0.85,padding 适中
  • 颜色由 pywal 自动从壁纸提取——每次换壁纸后自动更新终端配色

pywal 配合 noctalia 的 hooks 自动工作:换壁纸 → 触发脚本 → 提取颜色 → 写入 Alacritty 配色文件。全程无感。


软件生态

用途 方案
聊天 QQ(官方 Linux 版)+ 微信
办公 WPS Office(AUR),缩放用环境变量设 1.5 倍
笔记 Typora-free 0.11.18 — 最后一个免费版
浏览器 Firefox + FF Ultima 主题 + Catppuccin 配色
终端 Alacritty + JetBrains Mono + pywal 配色
包管理 pacman + paru(AUR 助手)

AUR 上的包名带 -bin 后缀是预编译版,下载即用;-git 是源码编译版,更折腾但版本最新。日常用 -bin 就行。


博客迁移

之前的 Hexo 博客在 Windows F 盘上,Linux 直接挂载 NTFS 分区复制过来:

1
2
3
sudo mount -t ntfs3 /dev/nvme1n1p1 /mnt/windows-f
rsync -av --exclude='node_modules' /mnt/windows-f/Hexo-Blog/blog-demo/ ~/hexo-blog/
cd ~/hexo-blog && npm install

SSH 密钥重新生成,加到 GitHub,部署命令不变:npx hexo generate -d


感受

同样的硬件,Linux 下日常使用 CPU 温度 40-50 度,风扇安静。没有 Windows Update 后台偷跑,没有杀毒软件扫来扫去,没有厂商预装的各类服务。

包管理器的体验也确实比 Windows 的 exe 安装包 + 手动更新强一个维度。一条命令更新全系统——包括内核、驱动、所有软件和 AUR 包。

有些东西 Linux 确实不行(微信的视频通话、QQ 小程序、某些国内软件),但大部分日常开发和学习场景完全够用,甚至更舒服。