把 Hugo 博客扩展成个人私密图床与内容网关

我一直希望这个博客除了放公开文章,也能顺手承担一些个人内容的展示需求,有些图片、PDF、笔记或作品集并不适合挂到首页、菜单、归档和 RSS 里,但又确实有临时分享的需要,这时我只要把指定 URL 发给对方,对方带上密码参数后就能看到内容。 单靠纯静态博客做不到真正的访问控制,因为文件一旦被 Hugo 构建到了 public/ 或放进 static/,前端的密码判断就只是在页面上多加了一层遮挡,知道真实路径的人仍有办法绕过去;考虑到这一点,我保留了 Hugo 原有的公开站点,再给它接上一个服务端私密内容网关,把需要保护的文件放到公开构建目录之外。 目标 我给这套方案定下的范围不算复杂,主要包含下面这些能力: URL 会校验 query 参数中的密码,例如 /p/trip-2026-cover?password=xxx 密码配置保存在本地文件,不需要依赖 Vercel Storage、KV、数据库或 Dashboard 环境变量 PNG、JPG、WebP、GIF、AVIF、PDF、Markdown、HTML、TXT 等常见内容都能处理 私密内容不会进入博客首页、菜单、归档、RSS 或 sitemap,访问者只能拿指定 URL 打开 同一份内容可以配置多个密码,临时分享和后续撤销都会方便一些 加上 download=1 后,可以在浏览器预览与文件下载之间切换 相册模式能让一个密码打开一组图片和 PDF,也可以生成 expires + sig 形式的过期签名链接 访问时没有密码会看到提示页,输入完成后再跳到带参数的 URL 所有私密响应都会带上 X-Robots-Tag: noindex, nofollow 与 Cache-Control: private, no-store 批量导入脚本会生成别名、密码哈希和配置模板;本机装有 ImageMagick 时,还能一并生成缩略图、水印图以及去 EXIF 版本 对外只暴露 /p/trip-2026-cover 这类 alias,真实文件名不会跟着出现在 URL 里 架构选择 实际落地时,我增加了一个 Node.js Vercel Function,入口关系如下: /p/:id -> /api/private-content?path=:id 原先的图片入口没有直接删掉,仍然可以兼容使用: ...

2026年7月9日 · 2 分钟

Java 性能问题定位:从系统指标到应用排查

Java 性能问题定位:从系统指标到应用排查 这篇笔记参考了阿里云相关博客中的排查思路,原文写得很细,我在此基础上按自己的使用习惯重新做了整理。 线上 Java 应用一旦出现响应变慢、资源飙升或进程异常,排查范围很容易越铺越大,下面会从业务代码、CPU、线程、内存、GC、磁盘 I/O、网络 I/O 和常用命令几个角度,把常见的定位方法串起来。 实际的性能问题很少只对应一个异常指标,更多时候是系统层、组件层与应用层互相影响后的结果;排查时可以从最贴近业务的应用层入手,拿到初步方向后,再回到系统指标中交叉验证。 性能优化工具图谱 ├── 系统层 │ ├── CPU │ │ ├── 🚩 CPU利用率(top/vmstat/sar/dstat) │ │ ├── 🚩 CPU平均负载(top/uptime) │ │ └── 上下文切换次数(pidstat/vmstat/dstat) │ ├── 内存 │ │ ├── 全局内存使用 │ │ │ ├── 🚩 已用/剩余/可用内存(free/vmstat/sar) │ │ │ └── 缓冲区/缓存(pcstat/cachestat/cachetop) │ │ └── 进程内存使用 │ │ ├── 🚩 虚拟内存/常驻内存/共享内存(top/ps/pidstat) │ │ ├── 🚩 SWAP 内存使用/换入换出速度(top/free/vmstat/sar) │ │ ├── 缺页异常(ps/pidstat) │ │ └── 内存分布(pmap/jmap) │ ├── 磁盘 │ │ ├── 空间容量(df/du) │ │ ├── 🚩 吞吐量/磁盘 I/O 使用率(iostat/dstat/sar) │ │ └── 缓冲区/缓存(pcstat/cachestat/cachetop) │ └── 网络 │ ├── 🚩 吞吐量(sar) │ ├── 网络延迟(ping) │ ├── 🚩 网络连接数/错误数(netstat/ss/sar) │ └── 网络抓包(tcpdump/wireshark) ├── 组件层 │ ├── 数据库 │ │ ├── SQL 调优 │ │ ├── 🚩 索引调优 │ │ └── 连接池配置 │ ├── 网络 IO │ │ ├── I/O 调度模型 │ │ ├── 序列化框架 │ │ └── 线程调度模型 │ ├── Web 容器 │ │ └── 线程池配置 │ └── 缓存/MQ…… └── 应用层 ├── 线程 │ ├── 死锁检查(jstack/arthas) │ ├── 🚩 线程状态分布(jstack/arthas) │ ├── 锁竞争分布(jstack/arthas) │ ├── 🚩 代码执行热点(jprofiler/zprofiler) │ ├── 🚩 占用 CPU 较重的线程(top + pidstat + jstack) │ └── 代码追踪(btrace/housemd/greys/arthas) ├── 内存 │ ├── 内存分配 │ │ ├── 常驻内存/虚拟内存(top) │ │ ├── 对象分配热点(jprofiler/zprofiler) │ │ ├── 🚩 堆内对象分布(jmap/zprofiler/MAT) │ │ ├── 类加载相关(jstat/greys/arthas) │ │ ├── 🚩 内存泄漏(gperf/MAT/zprofiler) │ │ └── 堆外内存(jmap + MAT + NMT + gdb + perf) │ └── 垃圾回收 │ ├── GC 线程使用(jinfo) │ ├── 对象晋升年龄(gclog) │ ├── 🚩 GC 的频率和时间(jstat/gclog) │ ├── 垃圾回收器类型/JVM 参数(jinfo/jcmd) │ └── 🚩 堆大小设置及分区大小(jinfo/jstat) ├── 网络 │ ├── 带宽使用 │ ├── 流量异动 │ └── 网络分区 └── ★ 业务(日志、监控…) ├── 🚩 代码逻辑 ├── 远程调用 └── 架构设计 CPU 与线程 观察 CPU 时,最常看的指标是下面三项,常用工具包括 top、ps、uptime、vmstat 和 pidstat。 ...

2026年5月3日 · 8 分钟

大语言模型的可观测性应该有哪些

LLM 与 AI Agent 可观测性 传统微服务常看请求量、错误率和耗时,到了 AI 应用里,也可以先用 Token、Error、Duration 建立一组基础指标;不过这三项只能说明服务有没有运行、花了多少钱、速度是否正常,还不能回答模型为什么给出了这个结果,更无法判断一次 HTTP 200 背后的答案是不是正确。 对普通 LLM 调用来说,一次请求已经可能包括 prompt 拼装、模型推理、内容清洗和结果返回;换成 LLM Agent / AI Agent 后,链路还会加入意图识别、RAG 检索、工具选择、参数构造、多次 LLM 调用与重试,因此真正需要建设的不是“多打一批日志”,而是让整条执行轨迹能够被还原、评分和归因。 1. 为什么传统 APM 还不够 传统 APM 擅长回答“哪里慢了”“哪里报错了”,但 AI Agent 会出现一些没有异常堆栈的失败,例如模型很自信地答错、选中了相似但不适用的工具、参数类型正确却语义错误,或者在几个工具之间来回循环,最终造成 token 和账单持续增长;这些现象在 HTTP 状态码上可能全部是成功,用户拿到的结果却不可用。 因此,AI Agent 可观测性至少要能回答下面几类问题: 一次任务内部执行过哪些步骤,每一步花了多少时间和 Token RAG 召回了哪些文档,排序和 score 是否合理,最终答案有没有使用这些证据 Agent 选择了哪个工具,tool args 是否正确,调用顺序与依赖关系有没有被满足 最终输出是否准确、相关、完整,格式和安全要求有没有达到 失败发生在 Prompt、retrieval、Tool、LLM 还是 orchestration,能否复现到具体 span 单次请求、用户、组织和模型分别产生了多少成本,异常调用是否触发了熔断或 fallback AgentTrace 将结构化轨迹分为 operational、cognitive、contextual 三类 surface,并把可观测性用于安全、可复现性、问责与实时监控;这类设计比单纯保存最终 input/output 更接近 Agent 的实际运行方式。 ...

2026年4月25日 · 5 分钟

AI 时代个人的一些思考(持续更新)

技术一定是向着降低技术的门槛发展的,比如AI coding,降低了写代码的门槛,但也其实只是降低写代码这件事情本身,很多系统性的思考,系统设计的思想,AI是不能脑控你的,你得要自己理解why。 AI时期泡沫已经来了,不确定是不是泡沫的最高点,但是泡沫终会散去,怎么在这个时期保全自己? 苦练基本功 把自己的能力与AI红利摘除,你不能是因为吃了AI红利而强,与AI强绑定的话,可能就完蛋了。 这一点其实也和上面绑定,也就是一致。 初稿:2026-04-25。若你读到与当下实践不一致的句子,以你现场约束为准;欢迎留言或 PR 指出可改进之处。

2026年4月25日 · 1 分钟

MySQL 与 Elasticsearch 双写一致性

场景引入 如果你设计了一个类似 B 站的视频网站中的搜索系统,或许在初期 MySQL 的 like 模糊搜已经能满足你对视频的标题、简介的搜索需求。 SELECT * FROM videos WHERE title LIKE '%'||:search_term||'%' OR description LIKE '%'||:search_term||'%'; 如果后期系统视频的量级上来了,对性能有一定要求了,你或许已经开始考虑 ES 了。后面如果你还想让与搜索关键词相关的创作者名称、视频评论、关联标签也能级联视频搜出来,甚至弄一个各种业务维度的融合公式,那么 ES 这样的搜索引擎再适合不过了! 但是,如果数据写一份 MySQL,还要落一份 ES 来支撑筛选/搜索,要怎么写入两个异构的存储系统、并保证它们的一致性呢? Note 如果你有更好的场景欢迎提出/补充~ 方案一:同步双写 优点 简单粗暴 ES 同步写入快 缺点 业务耦合,扩展性差,代码侵入性强(要在写 MySQL 的地方后写 ES 的代码,要在代码设计上做好收敛) 影响性能,写入两个存储,响应时间变长(本来 MySQL 的写入性能不是很高,再加一个 ES,系统的性能必然会下降),吞吐也会下降 存在双写失败丢数据风险(比如写完MySQL,ES 就挂了) 方案二:异步双写 优点 性能高 不易出现数据丢失问题,主要基于 MQ 消息的消费保障机制,比如 ES 宕机或者写入失败,还能重新消费 MQ 消息 多源写入之间相互隔离(业务侧只管发消息),解耦数据生产者和消费者,便于扩展更多的数据源写入 缺点 同样存在业务耦合的问题,写完 DB 之后,还需要发个 MQ(代码设计上做好收敛,也可以关注一下所用 orm 框架有没有 hook 之类的能力,支持你“写后 xxx”) 接入新的数据源需要实现新的消费者代码 系统复杂度增加,引入了消息中间件 MQ是异步消费模型,存在延迟问题 Tip MQ 可以缓冲和 DB 的负载能力不一致问题 ...

2025年11月17日 · 1 分钟

Redis 大 key 问题

大 key 标准 在一般的业务场景下(并发和容量要求都不大): 单个string的value>1MB 容器ds元素数量超过10000 在高并发且低延迟的场景中:(互联网上看到比较多的版本) 单个string的value>10KB 容器ds元素数量超过5000或整体value>10MB 阿里云的云版本Redis: Key本身的数据量过大:一个String类型的Key,它的值为5MB Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个 Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value(值)总大小为100MB 但其实Redis大key问题的定义及评判准则并非一成不变,没有固定的判别标准,还是要根据Redis在实际业务中的使用来综合评估。(注意边界问题) 大 key 影响 读取成本高 时延(执行命令时间更长) 带宽(大 key 消耗更多的带宽,从而影响相关服务) 大key的写操作容易阻塞,从而导致无法正常响应 慢查询 主从同步异常 占更多的存储空间,从而导致逐出,甚至是 OOM(Out Of Memory) 集群架构下,某个数据分片的内存使用率远超其他数据分片,即数据分片的内存资源不均衡 根本原因 Redis 是单线程! 大 key 产生原因 业务设计不合理。这是最常见的原因,不经过合理拆分,就直接把大json塞在一个key中;甚至塞二进制文件数据。 没有处理好value的动态增长问题。如果一直添加value数据,没有定期的删除机制、合理的过期机制或者卡量,大key只是早晚的问题。(例如:微博明星的粉丝列表、热门评论、直播弹幕等) 程序bug。某些异常情况导致某些key的生命周期超出预期,或者value数量异常增长。比如LIST的业务消费侧发生代码故障,造成对应Key的成员只增不减。 找到大 key bigkeys 参数 参考官方文档:https://redis.io/docs/latest/develop/connect/cli/#scanning-for-big-keys 使用redis-cli命令客户端,连接Redis服务的时候,加上 --bigkeys 参数,可以以遍历的方式分析Redis实例中的所有Key,并返回Key的整体统计信息与每个数据类型中Top1的大Key。(原理是基于SCAN) $ redis-cli --bigkeys # Scanning the entire keyspace to find biggest keys as well as # average sizes per key type. You can use -i 0.01 to sleep 0.01 sec # per SCAN command (not usually needed). [00.00%] Biggest string found so far 'key-419' with 3 bytes [05.14%] Biggest list found so far 'mylist' with 100004 items [35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes [73.91%] Biggest hash found so far 'myobject' with 3 fields -------- summary ------- Sampled 506 keys in the keyspace! Total key length in bytes is 3452 (avg len 6.82) Biggest string found 'counter:__rand_int__' has 6 bytes Biggest list found 'mylist' has 100004 items Biggest hash found 'myobject' has 3 fields 504 strings with 1403 bytes (99.60% of keys, avg size 2.78) 1 lists with 100004 items (00.20% of keys, avg size 100004.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 1 hashs with 3 fields (00.20% of keys, avg size 3.00) 0 zsets with 0 members (00.00% of keys, avg size 0.00) 总结 优点:方便、快速、安全。 缺点:分析结果不可定制化,准确性与时效性差。 Redis RDB Tools工具 使用支持定制化分析的开源工具Redis RDB Tools,分析RDB文件,扫描出Redis大key。可以根据自己的精细化需求,全面地分析Redis实例中所有Key的内存占用情况,同时也支持灵活地分析查询。 ...

2025年3月15日 · 2 分钟

计算模型与主定理

例题 使用主定理 主定理是一个强大的工具,用于解决递归关系式,通常用于分析某些递归算法的时间复杂度。当递归关系式具有以下形式时: ...

2024年3月16日 · 2 分钟

Java 设计模式笔记

设计模式 举个例子,比如我们有个导出功能,按照不同的格式导出数据,比如 CSV、Excel、JSON 等;比如我们需要导入 execl 文件,需要解析文件内容,针对不同的格式,需要不同的解析方式;再比如我们有一个给用户发送邮件功能,有可能需要用 gmail, qq, 163 等邮箱服务商,手机验证码服务也是如此。如果我们使用 if-else 或者 switch-case 来实现,代码会变得很臃肿,而且扩展性很差,多一个导出为其他格式的需求,就需要大量修改代码。 处理之前的代码可能长这样: /** * @param filePath 文件路径(含文件名) * * 导出 CSV 和 Excel 的需求 */ public void export(String filePath) { String fileType = filePath.substring(filePath.lastIndexOf(".") + 1); if ("csv".equals(fileType)) { // 导出 CSV 的具体代码,以下省略100行 } else if ("excel".equals(fileType)) { // 导出 Excel 的具体代码,以下省略100行 } else { throw new IllegalArgumentException("不支持的文件类型:" + fileType); } } 策略模式 策略模式的主要作用是用来提升一些代码的复用性的,或者解决代码中出现很多 if-else 语句的问题。 ...

2024年1月21日 · 4 分钟