资讯详情

从想法到上线:自托管写作系统开发全记录与工程实践复盘

📅 2026/10/14 7:25:51 | 华诺云谱 👁 阅读
从想法到上线:自托管写作系统开发全记录与工程实践复盘
五年前一个很普通的晚上我在电脑里新建了一个文件夹给它起名叫“梦”。文件夹里只有一篇文章几百字第一行写着“梦的开始”。当时的我根本没有想过这个文件夹后来会变成一个真正在用的写作发布系统更没想过从“有念头”到“做成第一个可用版本”的这段路会比我后来写的任何代码都更有复盘价值。这篇文章不是什么产品发布公告也不是技术教程更不是鸡汤。它是一个真实项目从零到一的过程记录一个模糊到几乎说不出口的想法如何被一步步拆解、落地、试错、修正最后变成一个每天都会打开的日常工具。如果你心里也有一件“想做很久但一直还没开始”的事不管它是写代码、做内容、运营一个社群还是经营一门手艺这篇文章应该能给你一些具体到可以立刻动手的参考。1. 一个念头从“想”变成“做”的全过程1.1 最初的那张草稿那个文件夹里的文章写得很朴素甚至有点乱。我至今记得里面大概有三段话第一段我受够了内容散落在各种文档、备忘录、网盘里想找一个地方把所有文字统一收拢。第二段我希望写完之后能不经过任何人、任何平台直接变成一个可以在任意设备上打开的页面。第三段我希望这个系统足够简单简单到十年后我回头看时仍然知道每一行代码在做什么。现在回看这三段话就是整个项目的种子。它不是什么宏大愿景反而像三句抱怨。但后来我才意识到所有能落地的项目起点往往不是“我想改变世界”而是“我实在受不了某个问题了”。问题越具体项目活下来的概率越高。当时我面临的选择是继续用现成的笔记软件还是自己动手。我认真列了一下现成工具的主要问题有三个。一是数据格式不透明换工具等于搬家搬家就得重新整理一次结构二是编辑器体验和发布流程被厂商预设好想调整就得跟着版本更新走主动权不在自己手里三是最重要的我把大量文字放在别人的服务器上心里总有一种不踏实感像是把自己的日记本寄存在他人家中钥匙却不在自己手上。所以那个晚上我在文件夹里写下了最后一行字如果一定要有这样一个地方那就自己做一个吧。1.2 为什么不直接买一个现成的工具有人会问市面上现成的写作工具那么多为什么要自讨苦吃这个问题的答案其实就藏在“梦的开始”这个命名里。我想要的不是“一个工具”而是“属于自己的系统和节奏”。举个例子。用现成工具就像在宜家买一张组装书桌好看、便宜、安装快但桌面高度是固定的抽屉大小是固定的材质是固定的。自己动手则像是请木匠打一张桌子前期要设计、买料、加工成本高很多但你可以把桌角磨圆避免撞到膝盖可以加一层隔板专门放某一类书可以刷成和房间匹配的颜色。对于需要长期使用、深度依赖的东西“定制感”带来的价值会随着时间慢慢放大。我当时给自己的判断标准很简单使用频率越高、个性化需求越强的东西越值得自己做反之则应该优先用现成方案解决。写作把这两条全占了。于是我决定动手。这个决定背后还有一个更隐蔽的动机我想验证自己是否具备“把一件事从头做完”的能力。不完全是为了做出来一个系统更是为了给自己一个交代——那些年积累的技术和经验不该只停留在浏览别人代码和收藏教程的层面。2. 需求拆解与工具选型把模糊想法变成能执行的任务2.1 把想法拆成七个具体需求“做一个写作系统”是一个模糊的愿望模糊的愿望无法直接开工。我花了大概两天时间把最初的三个想法翻译成具体的功能点。翻译的过程很简单想象自己每天使用这个系统时的完整动作然后列出每一步需要系统做什么。最后我得到了一份七个需求清单能新建文章并且每一篇都有一个唯一的固定链接。文章支持纯文本写作最好能支持标记语法让标题、列表、代码块看起来清晰。文章能按分类和标签归档方便日后回溯。写作时能实时预览最终效果不用等发布后再看。一键发布让草稿变成公开页面。支持全文搜索至少能按标题和内容关键词找到文章。能记录每篇文章的创建时间和最近修改时间。这七个需求后来几乎没有变过。现在回头总结我特别庆幸当时做了这一步。为什么因为需求拆解的直接好处是你开始知道“什么是不必要的”。比如我当时坚决没有加评论系统没有加点赞按钮没有加访客统计也没有做多用户权限。这些功能会成为当时最诱人的“以后再做”但正是因为没有做它们项目才可能在第一个月内就达到可用的状态。把模糊想法变成清晰任务的核心方法就一句话用“使用者视角”而不是“设计者视角”去描述流程。你不是在规划一个系统你只是在描述自己会遇到什么、想完成什么。2.2 技术选型的三个原则需求清单确定之后技术选型反而变得简单。我的选择过程遵循了三条原则第一选自己已经熟悉的技术而不是看起来最前沿的技术。当时我对某个轻量级前端框架已经很熟对后端的 JavaScript 运行环境也算熟悉数据库方面日常会用嵌入式单文件数据库。于是技术组合就是这三样。这不是最时髦的方案但它是当时我最“不怕出问题”的方案。第二选长期稳定的技术而不是短期热度高的技术。我给自己定的假设是这个系统可能要用十年。十年后前端的打包工具、后端的部署方式可能完全变样但一个单文件数据库、一个由静态页面组成的输出物几乎一定还能被打开。事实也证明了这一点直到现在这套系统最重要的输出仍然是一堆纯静态的页面任何托管环境都能承载它。第三能用配置解决的就不写代码能用约定解决的就不用配置。这句话展开说就是与其写一堆自动化脚本来处理特殊情况不如从一开始就规定好“所有文章按同一个格式存放所有日期统一用同一个时区记录”。规则简单了代码自然简单问题也就少了。很多人在技术选型上花的时间远远超过做项目本身。我的经验是选型只要满足“能用、能维护、不锁死”这三点就够了剩下的时间应该留给真正的核心功能。工具永远只是手段系统才是目的。2.3 项目定名前后想清楚的事给项目取名这件事我犹豫了一阵子。最初想的名字都很“功能化”什么“写作台”“发布机”之类的念起来都不对味。后来我回到那个文件夹看见第一篇文章的标题写着“梦的开始”突然就定了这个项目就叫“梦的开始”。这个名字和写作系统听起来不那么技术但它恰好记录了项目的来处。我给自己定了一个规矩将来如果有人问起这个项目我不想解释一堆架构和名词只想说每个人都会有一些想做但一直没做的事这个系统就是我做成的第一件。名字越接近初心后续维护时的耐心就越足。很多项目半途而废不是因为技术难而是因为忘了当初为什么要做。3. 核心功能实现草稿系统从哪里开始长出来3.1 数据结构用一张表承载全部内容整个系统的数据核心其实只有一张表我把它叫做 articles 表。当时设计它的时候我逼着自己做减法凡是可以从内容里推导出来的字段一律不存凡是以后可能变来变去的字段先不设计。最后的字段列表非常朴素CREATE TABLE articles ( id TEXT PRIMARY KEY, title TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, content TEXT NOT NULL DEFAULT , summary TEXT DEFAULT , category TEXT DEFAULT 未分类, tags TEXT DEFAULT [], status TEXT CHECK(status IN (draft, published, archived)) DEFAULT draft, published_at INTEGER DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL );这个数据结构解决了我最早的那个痛点内容散落。所有文章都在这里分类和标签只是两个辅助字段真正的内容是纯文本加标记语法格式公开透明随便用什么工具都能读。时间字段我用的是毫秒级时间戳并且统一按协调世界时存储展示时才做转换。这个细节当时只是一笔带过后来却在排障里帮了我大忙。关于表结构设计我当时悟出一个经验数据库设计不是越复杂越好而是越“诚实”越好。一个字段能不能表达它名字的含义数据进去之后还能不能读出来比字段多不多重要得多。后来系统经历了多轮迭代这张表始终没变过因为它足够简单——简单的结构天然容易保持稳定。3.2 编辑器先保证“能写”再追求“好写”写作系统的第一个版本编辑器就是页面左上角的一个文本输入框。那确实很朴素但我对此有一个清醒的认识编辑器是工具工具的价值在于让你持续用下去而不是第一次打开时惊艳。因此我定的路线是第一版只要能写成、能保存、能预览就算过关。初步实现是左右分栏左边输入纯文本右边实时渲染预览。通过解析标记文本生成对应的标题、段落和代码块。几百行代码就能把它跑起来而且效果很直观。最有成就感的瞬间是第一次写完一篇文章按下预览按钮右边出现完整排版的那一刻——那感觉像是自己造了一个纸和笔都够不到的小印刷厂。后来我把编辑器换成了更成熟的所见即所得组件这不是因为旧方案不可以用而是因为在积累了上百篇文章之后我发现继续用纯文本输入时表格和图片的排版效率太低了。这个升级很顺利因为底层存储还是纯文本标记编辑器只是一个输入界面换掉它并不影响任何历史数据。这让我更坚定了一个理念把“内容存储格式”和“编辑交互界面”彻底分开能换来巨大的长期灵活性。3.3 发布链路从本地文件到线上页面的最后一公里发布功能是整个系统里最核心的部分也是最容易做得过重的部分。我的需求只有一句话按一个按钮草稿变成公开页面。具体实现围绕状态迁移展开。文章初始状态是草稿写作过程中不管怎么保存都不会影响外部写完后发布动作会把状态改成已发布打上发布时间并触发一次快照生成。快照是指把这篇标记文本渲染成静态 HTML 文件写入输出目录。最终驻场的是一个静态托管环境所有页面都是预先生成好的纯 HTML 文件不依赖数据库实时查询。这个设计的妙处在于文章发布后就是一个不可变的静态文件访问速度极快也没有动态请求崩溃的风险。写到这里我必须承认一个现实环节这套流程里最麻烦的不是代码而是“部署”。起初我的部署方式是手动把文件传到服务器配了一些脚本才自动化。这里有几个关键点值得分享每次发布后必须生成唯一的输出目录用时间戳命名避免覆盖旧版本。发布脚本里必须校验所有链接指向的文件是否存在避免出现死链。发布前自动跑一遍内容完整性检查比如标题是否为空、固定链接是否重复发现问题直接中止发布。自动化之后“发布”这个动作终于变成了一个让人愉悦的仪式写完检查发布看到新页面上线。也正是因为发布的动作足够轻快我才会愿意持续输出。一旦发布流程变得痛苦写作热情就会迅速流失——这个道理我后来在很多创作系统里都反复看见。4. 踩坑与排障上线前后来回折腾的四个问题4.1 幽灵文章时区导致发布状态错乱项目上线后的第一周我就遇到了一个形容诡异的 bug。深夜发布了一篇文章页面上显示的文章日期竟然是“明天”。更离谱的是列表里还出现了一篇我从未记得写过、发布时间在未来的文章。排查了很久才定位到原因发布时间保存时使用的是本地时间但数据库字段设计成了协调世界时时间戳。保存时已经转换过一次时区读取时又转换了一次两个环节叠加最终时间就偏了好几个小时。那篇“未来的文章”其实是当天早些时候保存的一篇草稿它被错误地当成了已发布文章。这个 bug 的教训非常朴素时间字段全链路统一使用同一个标准存储和传输只在展示边界做转换。后来我在代码里加了一条约定——凡是遇到时间戳一律按协调世界时处理页面上的显示统一交给前端格式化。从那以后这个类型的问题再没有出现过。4.2 移动端点击穿透弹窗背后的按钮被偷偷触发第二个问题出现在移动端。我在文章列表上做了一个“操作确认弹窗”弹窗里有两个按钮确认删除和取消。弹窗出现时背景是半透明遮罩。结果我发现在手机上点击“确认删除”时弹窗刚关闭位于弹窗后面、列表里同一位置的另一个按钮竟然被“顺带”触发了。这就是经典的点击穿透问题。移动端浏览器在触摸事件结束时会派发一个延迟的点击事件如果弹窗在这个延迟间隙内已经关闭后续元素就会收到这个多余的点击。解决方式有两个层次。第一个层次是改变交互关闭弹窗时增加 100 至 300 毫秒的过渡确保点击事件派发时遮罩层还在不会穿透到下层第二个层次是从事件机制上根治在遮罩层上监听点击开始位置阻止默认行为同时将确认按钮的触发从点击事件改为触摸结束事件。我当时两个层次都做了。这件事给我的经验是移动端的交互问题不能只看现象还要理解浏览器事件模型的时序。很多看起来随机发生的“灵异操作”背后其实是一套固定的机制查明机制比急着打补丁重要。4.3 页面越用越慢一次看不出来的性能劣化系统用了一个月之后我开始觉得列表页打开变慢了。起初以为是网络问题后来发现不是——是文章数量涨到两百篇之后列表页先把所有文章的全文都拉回来了。问题就出在我的取数逻辑上列表页只需要标题、摘要、标签和时间但我当时为了省事直接把这个表中的所有字段全部返回。文章内容多的时候一次请求就要传输几兆文本页面自然越来越慢。性能问题搭建版本时往往不显现数据量小的时候完全无感一旦数据量起来隐患就被放大。排查方式也简单我看了网络请求返回的负载大小发现内容字段占了 90% 以上的体积。解决办法是给列表查询单独写了一个投影函数只返回列表展示需要的字段进入文章详情页时再按 id 重新获取完整内容。改完之后列表页响应时间从大约两秒降到了零点几秒。这个优化非常基础但它让我养成了一个习惯不管系统大小设计数据读取方式的时候永远要考虑“这个场景需要什么字段”而不是“表里有什么就返回什么”。4.4 差点丢数据的早晨备份策略救了我最惊险的一次发生在一次例行调整分类逻辑的时候。我写了一个数据迁移脚本作用是给所有已有文章重新计算分类字段。脚本的源数据读取用的是开发环境的临时文件写回时手滑写成了正式数据库的路径。等我反应过来正式数据已经被覆盖了一部分。当时我背后一凉第一个念头是“完了”。但紧接着我打开了备份目录——因为我从项目第一天起就设了每日自动备份备份文件按日期归档。恢复操作只花了几分钟丢失的那部分文章在最新备份里完好无损。从那之后备份策略成了我逢人必谈的头号安全底线。我的具体做法其实朴素得很每天凌晨自动导出一次完整的数据库文件保留最近 30 天的历史版本另外在每个月的月初做一次整库的离线归档。这里想多说一句备份这件事不能等出了问题再做也不能只靠“记得手动备份”。因为人是不可靠的尤其是深夜、着急、要改数据的时候最容易忘记。自动化备份不需要很复杂但它必须早于任何危险操作发生。5. 从自用到开源一个私人工具如何变成公共资源5.1 第一次收到陌生人的反馈项目稳定用了几个月后我把系统开源了。最初的目的很简单让代码多一双眼睛看也免得哪天电脑坏了整套设计思路跟着消失。开源之后很长一段时间都没有动静。直到某天邮箱里收到一封陌生来信。对方说他在社区里看到这个“梦的开始”项目试用之后觉得很有意思但发现发布功能在中文标题下会生成乱码固定链接问我要不要看看这个问题。我第一反应是惊讶原来真的会有人用我写的东西第二反应是压力自己用的时候从来没测试过中文标题的场景。很快我就复现并修复了问题中文标题在生成固定链接时需要做一次编码转换而我原来只处理了英文字符。修复的过程不过半小时但它打开了我的思路——用户的反馈不是在批评而是在提供一张你从没走过的地图。5.2 数据迁移比想象中麻烦十倍开源之后陆续有人想把自己在其他工具里积累的文章迁入系统。我开始想得提供导入功能。结果一研究才发现数据迁移是这个世界上最难“通用”的事情之一。不同工具的导出格式五花八门有的是一堆带复杂的包装的文档文件有的是一个含有几十列的大表格有的干脆只有一堆零散的文本文件。字段映射更是牵扯不清标签怎么合并分类怎么对应创建时间用什么精度正文里的图片链接用什么形式存放。一个导入功能硬是做了三个星期。后来我想通了一件事导入工具做得再完美也不可能覆盖所有人千奇百怪的数据。于是我不再做“全自动导入”而是改成了“半自动迁移辅助”的定位。系统只提供最简单的批量录入接口把每一步拆得很小再写清楚说明文档。愿意迁移的人可以按步骤操作棘手的情况就走手动整理。这件事给我的收获是服务的边界不是越宽越好。项目从个人工具变成公共资源之后最需要守住的反而是“它到底解决什么问题、不解决什么问题”。什么都做只会让项目变成四不像还把自己拖垮。5.3 维护的边界感什么该做什么不该做维护一个开源小项目最大的敌人不是技术而是无限膨胀的期望。有人会提权限管理有人会提多人协同有人会提移动端原生应用还有人建议加语音输入。这些需求本身都合理但每一项都能吃掉大量精力。我给自己定了两条维护原则。第一条叫“先看它是不是项目的核心痛点”非核心需求一律记入远期规划不进入短期开发。第二条叫“默认拒绝例外接受”新需求先回绝只有当它确实能提升八成用户的体验时才重新考虑。这套原则帮我省下了大量时间。项目因此一直保持着小而美的状态功能不算多但每个功能都稳定、好用、不打架。要知道一个项目的生命力不是靠功能数量撑起来的而是靠可靠性和清晰定位慢慢积累出来的。6. 回头再看“梦的开始”给同样想出发的人三句话6.1 先问自己三个问题再动手如果你也被某个想法反复折磨我建议在下定决心之前先问自己三个问题。第一这个想法是解决谁的什么痛苦如果你的回答是“解决我自己的一个具体的、反复出现的痛苦”那这个想法值得做如果回答是“我觉得别人可能需要”那它大概率还需要再验证一轮。第二最小的完成品长什么样比如你的目标是做一个写作系统那最小的完成品就是“能写、能存、能发布”这三个动作可以走通的一条线。去想这个最小完成品能帮你看清自己要做的事到底是多少量级的工程。第三你愿意为它付出多久不是“三分钟热度”而是“持续六个月每天维护一下”的付出。这个问题的答案决定了你选工具、定方案、排优先级时的取舍方式。这三个问题问完之后启动的方向基本就清晰了。我见过太多项目死在“完美规划”上就是因为第一个问题没想清楚带着模糊的愿望去选型最后掉进了无穷尽的需求漩涡。6.2 第一个版本越丑越好这是我想说的第二句话。很多人迟迟不敢开始是害怕自己做出来的东西不够好、不够体面、拿不出手。但根据我的经验第一个版本越丑反而越好。为什么因为丑的版本足够小小到可以快速完成从而让你尽早走完“开始到结束”的完整闭环。这个闭环带来的正反馈远胜过任何精致的规划。我那个第一版写作系统界面只能说“能看”编辑器只是一个朴素文本框发布流程还要手动执行命令但它完成了一件重要的事——它让“从想法到产出”这件事变得可能。从那之后每一次优化都是在已经运转的轨道上微调。记住这个次序先让东西跑起来再让它变好。跑起来的是骨架变好的是皮肤没有骨架皮肤无处附着。如果你现在有一个想法我建议你今天就定下第一个最小版本的范围在接下来一周内做出一个能用的东西即使它丑到只敢给自己看。6.3 接受中途的停滞但别取消下一次出发项目后来有几个月我几乎没有碰过它。家里事情多、工作忙、状态差那段时间连打开文件夹的欲望都没有。有一阵子我甚至想过要不要把这个项目关掉免得心里总挂着一件事没做完。但后来我换了一个角度理解“停滞”。停滞不是失败它只是时间轴上一个正常的间歇。植物冬天也会休眠那是为下一个春天储备能量。真正重要的不是保持连续不断的进度而是每次停滞之后你还愿不愿意回到那个文件夹里继续往下走。大概在半年后我又打开它一次性补齐了拖了很久的三个功能。那个过程出奇地顺畅因为离开的那段时间我对系统的不满更清晰了知道哪些地方最需要改。这个体验让我释然了一件事出发这件事不是只能发生一次。梦的开始可以有很多个。你中断了第一次不代表你不能开始第二次。你只需要保证自己还愿意在某一个时刻重新把手放到键盘上。最后再说一点个人感受。回头看我那个名为“梦”的文件夹它早已不是当初几百字的草稿也不是一套软件系统它更像是一面镜子照出了我如何对待自己的念头。如果现在的你心里也有一个存在了很久的念头希望你从今天开始给它一个最小的开始。不需要声势浩大不需要准备周全只要打开一个新文档写下第一行字然后把“完成它”当成唯一的目标。许多事情的起点都朴素得不像话但正是这些朴素的起点悄悄改变了时间的走向。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑