news 2026/10/7 7:30:58

“Caveman”极简式架构:用Shell与静态页面重构个人项目

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
“Caveman”极简式架构:用Shell与静态页面重构个人项目

1. "caveman" 到底是什么:一次回到工具最初的实践

先说结论,我最近把一个维护了快两年的项目,彻底推倒重来,全部按 "caveman" 思路重构了一遍。直译过来就是"穴居人",听着像退步,实际上做完之后我只有一个感觉:早该这么干了。

这个项目的起点很朴素。我手头有一个长期在跑的小工具,功能不复杂,就是定期抓取几个固定来源的数据,做一点清洗,再渲染成网页给人看。最早用的是比较主流的框架,数据库、定时任务、后台管理界面全套上齐,跑起来也确实没什么问题。但半年之后问题来了:服务器从 1 核升到 2 核,内存从 512MB 升到 1GB,依赖从十几个变成三十几个,升级一次系统要连带处理各种兼容问题。整个项目的复杂度已经超过了它本身的功能价值,我一看不对劲——这哪是维护工具,分明是伺候一个"薛定谔的庞然大物"。

后来我给自己定了一条规矩:凡是这类内部工具、个人站点、信息聚合页,只要规模在可控范围内,一律先想想能不能用最原始的方式实现。所谓最原始,就是文件靠手写,页面靠模板拼,部署靠一个 20 行的脚本。听起来很野路子,但恰恰是这样的方式,让整个项目稳定得像块石头——这个思路我管它叫 "caveman" 风格。

这篇博客不会推荐你用老掉牙的东西,而是想聊聊我在重构这个项目时是怎么一步步拆掉不必要组件的。如果你手上也有一个"功能不大但越维护越累"的项目,或者你想给自己做一个常年不需打理的个人站点,这篇文章应该对你有用。我会把完整的目录约定、构建脚本、部署方式以及踩过的坑都放在后面,可以直接照着抄。

1.1 从"守旧"到"稳定":这个标题背后的灵感来源

给这个方案起名叫 "caveman",是因为我想起一个很经典的比喻:人类从狩猎采集进入农业社会,看起来是进步,但生活其实变复杂了——要种地、要储存、要防御天灾,每一环都是新的依赖。数字世界也一样,我们习惯性地给每一个小需求搭配一整套基础设施,最后不是我们在用工具,而是工具在用我们。

caveman 的精神就是:明明可以直接摘果子吃,就不要先去搭一个果园。具体到项目里,体现为三个非常朴素的生活常识:

  • 能用文件存储,就不用数据库。单用户、低并发的场景,一个压缩过的纯文本文件比任何数据库都容易备份、迁移和审查。
  • 能用静态输出,就不用服务端渲染。反正内容生成频率低,提前算好结果,让服务器做一个"搬运工"就好,连应用进程都不用常驻。
  • 能用 shell 脚本解决,就不用引入第三方自动化框架。几分钟就能写完的循环和定时任务,硬要去读一堆插件的文档,纯粹是给自己找事。

这不是排斥新技术,而是拒绝让新技术来迁就一个很小的需求。就像出门买瓶酱油,开跑车也不会比步行快多少,但你得先考虑停车、油耗和剐蹭风险。caveman 的做法是:楼下小卖部,一双拖鞋,足够了。

1.2 这个思路适合谁,不适合谁

我在重构过程中也认真划了边界,因为 "caveman" 不是万能的,更不是拿来显摆"我会用命令行"的。

先说适合的人群。第一类是个人站点的站长,你只是想把笔记、随笔、作品集放上网,更新频率一周可能就一次,这就完全没必要上 CMS 加数据库。第二类是内部工具维护者,比如我做的那种数据聚合页面,用户可能就是两三个人,稳定性和可维护性远比花哨交互重要。第三类是刚开始学习全栈的新人,通过手写一个完整项目,你能把技术链路每个环节都摸透,而不是靠框架替你兜底。

至于不适合的场景,也很明确:如果项目涉及多用户协同、实时数据更新、复杂权限模型,或者未来半年明确要上移动端 App,那还是老实选择成熟方案吧。caveman 的前提是"范围足够小",一旦需求本身的复杂度就高,强行简化只会把复杂度从工具层转移到你的脑子上,这就不划算了。

我自己的判断标准很简单:如果这个项目长时间不更新,只有我一个人在用,并且出问题后排查时间不希望超过半小时,它就有资格走 caveman 路线。

2. 核心场景拆解:为什么"少即是多"能抵御风险

你可能觉得我只是想偷懒。但把项目推倒重做后,我发现复杂系统里有一个经常被忽略的隐性成本——认知负担。如果你打开一个项目目录,看到里面有一堆"不知道干嘛用"的配置文件、"可能是历史遗留"的模块,你每一次改动都会战战兢兢。这种焦虑会持续消耗你的精力,而且根本写不进技术文档。

所以我在重构前花了一天时间,把原来的项目所有组件列了一张清单,逐个问自己:它到底在承担什么职责?有没有更直接的替代品?这个问题放到大家都熟悉的场景里就是:你新买了一台打印机,本来只想打印文件,结果厂商给你预装了三个管理软件、两个驱动更新服务、一个云账户系统。你为了打印,被迫维护这一整套东西。项目里的很多组件就是这样的"预装软件",它们不是我们主动需要的,只是因为选择了某个框架而连带安装了。

我在下面的章节里会把典型组件和对应的 caveman 替代方案放在一起对比,方便你在做自己的评估时有一个直观参考。这张表可以作为你动手前的对照清单,逐项打钩,不符合的就先放一放。

2.1 复杂系统的"隐性成本":从运维到心智的全面消耗

先算一笔账。原项目用的是轻量级架构,但为了"将来可能需要",我加了数据库、对象存储、Redis 缓存、后台管理接口。每一样在引入当时都有充足理由,但六个月后,我发现自己平均每个月要花半天时间做维护:更新依赖、检查兼容性、备份数据库、处理磁盘告警。这半天既不能产生新功能,也不能让页面变得更好看,纯粹是"因为你引入了它,所以你必须伺候它"。

这还不算最要命的。最大的隐性成本是团队或个人的"心智切换成本"。每次捡起这个项目,我都需要重新回忆:这个表为什么这样设计?那段代码从哪个版本开始没被调用过?配置文件里的某个参数是调优过的还是随手写的?这些问题叠加在一起,会让人越来越不想碰这个项目,最后变成一个谁也不敢动的大黑箱。

caveman 方案的厉害之处就是把你从这种黑箱中解放出来。文件就是文件,脚本就是脚本,没有任何隐藏在框架层之后的"魔法"。你用编辑器打开项目,从头看到尾,完全清楚系统是怎么运转的。这种透明感带来的安心,比任何自动化工具都有价值。

2.2 何时启用 caveman,何时必须止损

为了防止思路走偏,我把判断标准总结成了四条,全部满足才建议动手:

  • 功能边界清晰且稳定:未来半年内需求不会有重大变化。
  • 使用人数少:不超过几位数,不需要复杂的并发应对。
  • 数据格式简单:能用结构化文本表达,不需要强一致性的关系模型。
  • 你有一定的排查问题能力:至少看得懂日志和脚本报错。

反过来,只要出现一条就得谨慎:需求还在快速迭代;需要多人共享编辑同一份数据;数据之间关系复杂;或者你所在的团队有强制的技术栈要求。在这些情况下,caveman 节约下来的成本,很可能在另一个维度变成更大的成本。

我自己也遇到过该停手却没停手的情况。最开始我试图把原来的数据库结构直接用 JSON 文件模拟,结果发现数据量虽然不大,但查询路径散落在十几个脚本里,反而比用 SQL 更绕。后来我重新定义了项目的核心边界,原本需要"查询"的场景被改成了"定期生成摘要",信息粒度变了,文件存储才真正变得轻巧。这说明好的 caveman 并不仅仅是砍掉组件,而是重新思考功能的最小表达方式。

3. 实操:我从零搭建了一个 caveman 工作流

接下来是全文最核心的部分。我以自己的项目为例,带你把一个典型的 "caveman" 架构从零搭起来。整个流程分三步:建目录、写生成脚本、部署上线。不需要任何高级技术,只需要你熟悉基本的 Shell 命令和 HTML 标签。

看到这里你先别觉得"就这"。恰恰是这些朴素的步骤,让整个系统变得极其容易理解。即使你完全不写代码,跟着操作一遍,也能建立起"数据从哪来、文件怎么变、最终如何呈现"的全局认知,这种认知在很多花哨框架里反而是稀缺的。

3.1 先定义边界:一个纯文本的信息库

项目的第一个设计决策不是选什么技术栈,而是把所有数据统一收敛成纯文本文件。你可能觉得 "纯文本" 是一种降级,但在我这个场景里,它就是最优解。

我的原始数据是一堆来源固定的文章链接、标题、摘要、以及我手动添加的备注。原先这些散落在数据库的多个字段里,现在我用一个极简单的目录结构承载:

caveman/ ├── posts/ │ ├── 2025-01-14-数据清洗心得.md │ ├── 2025-01-15-服务器搬迁记录.md │ └── 2025-01-20-caveman 重构日志.md ├── scripts/ │ ├── build.sh # 构建脚本 │ ├── check.sh # 自检脚本 │ └── backup.sh # 备份脚本 ├── templates/ │ └── post.html # 文章页模板 ├── public/ │ └── index.html # 构建后的输出目录 └── config.env # 站点基础配置

每个 Markdown 文件的开头用三行 YAML 固定格式记录元信息:标题、日期、标签。正文就用普通的 Markdown 语法写。这样做的直接好处有两个。第一,我可以在不打开任何编辑器插件的情况下,用任何设备查看和修改文件,包括手机上的纯文本应用。第二,版本管理变得毫无心理负担,一个文件夹加 git 就够了,不要数据库导出,我不需要记忆某个神秘的备份命令。

这背后的逻辑很像整理房间:你不需要为一个只有二十件衣服的衣柜购买整套全屋智能管理系统,一个抽屉分格就绰绰有余。文件系统天然支持按名字排序、按日期修改、按内容查找,这些能力已经能满足我的全部需求。不要以为非要"结构化"才叫数据管理,非结构化本身就是一种结构。

3.2 用五步脚本生成静态页面

信息库建好了,接下来要解决"展示"的问题。传统项目的做法是写一套后端程序,接收请求、查询数据、渲染模板。caveman 的做法则简单粗暴:提前把所有页面渲染成静态 HTML,用户访问时服务器只是顺手把文件发出去。

具体到这个项目,我写了一个build.sh脚本,完整逻辑如下,你可以直接拿来改改用:

#!/bin/bash set -euo pipefail cd "$(dirname "$0")/.." rm -rf public mkdir -p public # 第一步:遍历 posts 下所有 md 文件,逐个生成独立 HTML 页面 for file in posts/*.md; do name=$(basename "$file" .md) title=$(head -n 1 "$file" | sed 's/^# //') date=$(stat -f '%Sm' -t '%Y-%m-%d' "$file") # 这行是本工具发挥核心作用的关键:用 sed 把 markdown 标题行替换成完整 HTML 头部 { echo "<html><head><title>${title}</title></head><body>" echo "<h1>${title}</h1>" echo "<p>发布时间:${date}</p>" echo "<article>" # 第二行开始的内容按原文输出,跳过第一行的标题 tail -n +2 "$file" | sed 's/$/<br>/' echo "</article></body></html>" } > "public/${name}.html" done # 第二步:生成首页 index.html,列出所有文章链接 echo "<html><body><h1>Caveman 站点</h1><ul>" > public/index.html for file in public/*.html; do name=$(basename "$file") if [ "$name" != "index.html" ]; then title=$(grep -o '<title>[^<]*' "$file" | sed 's/<title>//') echo "<li><a href=\"$name\">${title}</a></li>" >> public/index.html fi done echo "</ul></body></html>" >> public/index.html echo "构建完成,共生成 $(ls public | wc -l) 个文件"

注意,这是一个最基础的版本,我实际用的时候还加了摘要截断和标签归类。但核心思路就是这个:数据变换从"由应用动态解释"变成"由构建脚本一次性完成"。生成出来的 HTML 文件放在服务器上,任意一个静态文件服务器都能托管,根本不依赖 Python、Node 或者 PHP 运行时。

我之所以把构建逻辑控制在一个脚本里,是因为脚本的每一行都能被阅读者直接理解。你看着它一步步处理文件,不会有"这一步是不是某个框架内部做的"这种疑惑。这个项目里没有框架,没有 ORM,没有路由表,只有数据和操作数据的脚本——这是我眼中 caveman 方案的灵魂。

3.3 部署到一台"小到不能再小"的机器

部署环节是 caveman 优势体现最明显的地方。传统项目建议最少 1 核 1G 内存的云主机,而我的 caveman 站点跑在一台只有 128MB 内存的迷你 VPS 上都绰绰有余。

流程只有三件事:

  1. 把public目录上传到服务器的指定路径。
  2. 安装一个 Nginx 或者 Caddy,配置一个静态文件服务。
  3. 设置定时任务,每隔一小时执行一次scripts/build.sh,重新生成页面。

Nginx 的配置可以精简到最短:

server { listen 80; server_name example.com; root /var/www/caveman/public; index index.html; }

如果用的是 Caddy,甚至可以一句话完成:

example.com { root * /var/www/caveman/public file_server }

整个部署过程大概五分钟。之后你完全不需要关心服务器上的应用进程是否存活,因为根本没有常驻进程。每次更新内容,只需要把新的 Markdown 文件放进posts目录,然后执行一次构建脚本,页面就会自动出现在站点上。配合 cron 定时执行,发布新文章只需要一条命令加一次上传,有时候我甚至直接用 GitHub 的 webhook 来自动完成。

这里有一个大家都好奇的问题:如果内容更新频率高,静态方案会不会很吃力?我的经验是,对于个人站点或内部工具,每小时甚至每天构建一次都足够。构建本身耗时通常不到一秒,完全不会造成任何负担。如果你需要秒级更新,也可以引入系统级的文件监控工具,但当你有这个需求时,可能就该重新判断项目是否已经超出 caveman 的适用范围了。

4. 我踩过的五个坑与排查技巧

任何方案都不是完美无缺的,caveman 也有自己的阴暗面。我在重构和后续使用中踩过不少坑,下面这些如果能帮你避掉,这几千字就值回票价了。

4.1 第一个坑:路径问题引发的"神秘失踪"

刚开始写构建脚本时,我在脚本里直接用了相对路径posts/*.md。在本地测试一切正常,但一旦通过 cron 定时执行,脚本就跑不起来了,生成的页面全是空的。查了很久才发现,cron 执行时的工作目录是用户根目录,而不是脚本所在的目录,所以相对路径全部失效。

解决方法是在所有脚本开头加上一行保险:

cd "$(dirname "$0")/.."

这行代码先把工作目录切换到脚本上一级(也就是项目根目录),之后所有相对路径都不会受调用方式影响。别小看这一行,没有它,你的定时任务就会变成一个定时炸弹。

4.2 第二个坑:Markdown 内容没有做 HTML 转义

我在第一版脚本里直接用sed把文本搬运进 HTML,结果遇到正文里有<或>符号的文章,页面布局就崩了。这是经典的安全和兼容问题:直接把原始文本嵌入 HTML,可能会导致标签被浏览器解析成 DOM 元素。

解决办法有两个:要么在生成时把<替换成&lt;,要么像我一样,统一改用只支持纯文本内容的写作习惯。我的建议是尽量在脚本里处理掉,而不是依赖作者的写作纪律。一行sed 's/</&lt;/g'就能解决,为什么不加呢。

4.3 第三个坑:缓存与 CDN 的"顽固不化"

静态站点的缓存是把双刃剑。第一次更新页面内容后,我打开线上地址发现还是旧数据,排查了半天,最后发现是浏览器缓存和服务端缓存的双重叠加。后来我在生成静态文件时做了一个小动作:给文件名加上内容 Hash 或是经过时间戳。

我的实际做法是在每个 HTML 文件的链接上附加版本号,但更通用的办法是让 Nginx 关闭对 html 文件的强缓存,只保留协商缓存:

location ~* \.html$ { add_header Cache-Control "no-cache"; }

这样每次请求都会向服务器确认文件是否更新,而静态资源本身的缓存策略不受影响。

4.4 第四个坑:一个被忽略的"构建前自检"

最尴尬的一次问题,是我批量修改了某个 Markdown 文件的前缀格式,结果首页文章列表全部变成空白标题。因为我生成的首页会从每个 HTML 文件中提取<title>,而文件内容因为格式问题没有正常生成标题。

这让我意识到,在构建流程中加入"自检"很有必要。我现在会在build.sh最后加一段检查逻辑:确认每个生成的文件大小都大于 0,首页文章列表数量大于 0,否则立刻报错。宁可构建失败也不要发布一个坏页面。这个习惯帮我拦下了至少两次事故。

4.5 第五个坑:链路简化后的"安全错觉"

caveman 方案因为组件少,容易让人产生"没有攻击面"的错觉。但实际上,只要暴露在公网,任何服务都有风险。我的服务器开放了 SSH 端口,就曾遇到过密码暴力破解的扫描。此外,虽然是静态站点,但内容本身如果包含敏感信息,一样存在泄露风险。

针对这些问题,我采取的规避策略是:关闭密码登录,改用密钥认证;所有非公开数据绝不放进public目录;定期备份整个项目文件夹到另一个物理位置。这套习惯和项目本身的复杂度无关,属于所有网络服务的基本卫生准则。不要因为"它就是一个文件服务器"就觉得高枕无忧。

5. 从零复刻一套通用模板:把 caveman 变成你自己的方法

说完了我的项目,我想把最通用的部分抽象成模板,让你能直接拿过去套用到自己的场景里。无论是搭建简历页、活动展示页,还是整理学习笔记,这套结构都能无缝迁移。我这里不写具体代码,而是把核心约定和决策点抽出来,你照着做就不会走弯路。

5.1 文件夹规范:约定大于配置

项目的骨架非常简单,但稳定运行的秘密全在一套明确的约定里。我的建议是把这几个规则写进项目的 README 文件,当作自己的"宪法":

  • posts/目录下,每个文件代表一个独立页面,文件名必须按照YYYY-MM-DD-简短标题.md的格式命名,方便排序。
  • 文件第一行是# 标题,之后是正文;不再使用任何花哨的元数据格式。
  • public/目录是生成的产物,禁止手工修改,每次构建都会被清空重写。
  • scripts/下的构建脚本必须幂等,也就是说,重复执行多少次,结果都一样。

这套约定解决了"这个文件该放哪"和"这个文件能不能删"这两个最消耗精力的问题。因为所有规则都摆在了明面上,半年后回来再维护,你不会一脸茫然。

5.2 模板页面:从功能到审美的提升

我的第一版页面就是白底黑字,能用但不好看。要做出一个现代感十足的页面,也不必引入复杂前端框架。只要在模板里引入一个现成的 CSS 类库即可,比如用 Pico.css 或者 Simple.css,整个页面的颜值立刻提升好几个档次。

以 Simple.css 为例,只需要在模板的<head>中加入一行:

<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">

之后你的普通 HTML 结构会自动获得一套精心设计过的排版、配色和响应式布局。这个组合非常符合 caveman 精神:模板本身依然是纯粹的 HTML,样式全部交给一个外链解决,完全不用构建工具参与。它证明了一点:简单不等于粗糙。

5.3 备份与恢复:最容易被拖延的一环

静态网站最怕的不是出问题,而是出问题之后找不到恢复的办法。我的备份策略简单到不能再简单:由于所有数据就是一堆文本文件,备份就是一次文件复制。

tar -czf caveman-backup-$(date +%Y%m%d).tar.gz posts/ scripts/ config.env

这条命令会把所有源码目录打包成一个压缩文件。恢复也很直接,解压后运行一次构建脚本,一切回到原状。我会把备份文件同步到两个不同位置:一个本地外接硬盘,一个对象存储。每月执行一次,没有任何心理负担。

如果你愿意多走一步,把整个项目交给 Git 托管,那备份和历史回滚的问题就彻底解决了。每一次修改都是一个 commit,随时可以回到任意历史版本,即使误删文件也不怕。这一步再次验证了 caveman 路线的核心红利:数据越接近文本,越容易获得通用工具的加持。

6. 在工具焦虑时代,偶尔让自己做个"穴居人"

整理完这些经验,我想说点更个人化的感受。在做这个重构之前,我一度陷入一种奇怪的循环:每次看到新框架发布,都会不由自主地思考"我的项目是不是也该升级一下";看到别人讨论复杂架构,也会隐隐担心自己的方案显得不够专业。这种焦虑其实毫无必要,因为工具的价值永远取决于它解决的问题,而不是它用到的技术名词。

caveman 项目的经历让我重新找回了对技术的掌控感。当我把所有依赖都砍掉,亲手用几十行脚本构建起一个稳定运行的系统时,我对这个系统的每一个角落都了如指掌。这种"一切尽在掌握"的确定感,是在大型框架中很难获得的。它让我更愿意去读代码、改脚本、尝试自己动手,而不是遇到问题就搜索"XX框架 怎么实现 YY 功能"。

最后再分享一个实操里的小技巧。你不需要像我一样彻底推倒重来,就可以尝试 caveman 的思路:从你手头的一个小功能、一个小页面开始,故意不用框架,全手工实现一遍。哪怕只是写一个生成个人简历的脚本,或者做一个每日油价播报的静态页面。当你亲自把原始数据一步步加工成最终成品时,你会重新理解那些被框架隐藏起来的底层逻辑。这种经验很难通过读文档获得,但一旦拥有了,你就再也不会被工具牵着鼻子走。

说到底,做一个数字世界的"穴居人",不是拒绝进步,而是拒绝被复杂绑架。时代在变,工具在变,但有些朴素的原则不会变:能用简单解决的,绝不复杂化;能被自己掌控的,绝不交给未知的黑盒。我的小站点,用最原始的方式,已经稳定运行了大半年,没有出过一次需要熬夜排查的事故。对我来说,这就是最好的技术方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 7:29:22

终端效率工具与自动化脚本:打造属于你的个人工作流超能力

如果你最近在搜索框里敲过superpowers&#xff0c;很可能会看到某个同名开源项目&#xff0c;似乎"安装"一下就能拥有万物。但我要说的&#xff0c;是另外一种superpowers&#xff1a;它不是单一软件&#xff0c;而是我把一堆顺手的小工具按自己的日常习惯组合起来&a…

作者头像 李华