做手作的人多少都有个通病创作的时候痛快整理的时候遭罪。一年下来手机相册里堆了几百张过程图浏览器收藏夹里躺着几十个灵感网址备忘录里还有一堆写着“上次那个深色木蜡油色号”的碎片。等到真要找一样东西时翻来翻去半小时都未必找得到。后来我干脆自己动手写了一个叫 ArCraft 的创作工作台。名字取了 art 和 craft 的组合核心目标只有一个让灵感素材、创作过程与成品记录不再散落在聊天记录、相册和备忘录里。这篇复盘会把项目的立项思考、数据模型、内容采集链路、部署运维和后续迭代完整过一遍给同样想搭建自托管资料系统的创作者作参考。1. 立项动机创作碎片散落在四个工具里我终于决定自己写一个1.1 “artcraft”这个名字的含义以及项目边界ArCraft 这个名字没什么玄妙的典故。art 是艺术craft 是手艺拼起来就是“把艺术灵感落到具体工艺上”。它不是一个社交平台不做电商导购也不打算变成又一个云笔记。它就是一个面向手作人、插画师和独立创作者的轻量内容工作台负责保存三类东西灵感素材、创作草稿、成品档案。这个边界是第一版就划死的。因为“创作工作台”一旦定义得太宽很容易把用户管理、互动社区、拍摄剪辑等功能全部卷进来项目会迅速失控。我给自己定的范围是一个人或一个工作室小团队使用登录后维护自己的条目库能够打标签、传图、写正文、看历史版本、做检索。后来项目能顺利推进很大程度得益于一开始就限制了这个边界。1.2 我在实际记录创作时遇到的四类碎片我做木艺器物流程大致是先刷到一张值得参考的国外作品图截图存到手机再在备忘录里记下木料种类和漆料色号动手阶段每一步工序都要拍下来留档方便之后复盘最后成品图需要修图准备发到社区。这四类信息分散在相册、备忘录、浏览器收藏夹和文件管理器四个地方。真实的情况往往是某天想做一件新的器物想参考一下去年那件深色器物的上色效果同时确认当时用的木蜡油品牌却要在三四个地方来回找。相册里是图备忘录里只有含糊的一两行字浏览器收藏夹里躺着十几个未分类链接根本对应不上。这个痛点足够频繁频率高到我觉得必须用一套工具来收拢。1.3 现成笔记、相册、文档工具差在哪一口气我没有一上来就写代码而是花了两周时间认真试用市面上的工具想找一个“半成品”直接上手。结果没有一款产品能同时满足下面几个条件图片可补充自定义字段分类和多维标签灵活正文支持轻量 Markdown来源链接能保留数据最好能导出。对这轮试用过程我整理成了一个小表格工具类型适合干什么实际缺什么相册工具按时间和地点管理大量图片无法给图片补充“材料/技法/出处”等自定义信息在线文档成篇幅的长文与协作打开慢、结构重不适合高频碎片化记录灵感收集工具快速收藏网页素材收藏之后没有创作侧流程缺少过程跟踪本地文件管理器按目录归档搜索和标签能力弱多端同步麻烦结论很清楚我需要一个轻量的、以条目为核心的自建工具。数据模型和交互自己说了算该存哪些元数据可以慢慢打磨。于是 ArCraft 正式立项。2. 数据建模一张条目表如何同时装下作品、素材和草稿2.1 为什么不做作品表和素材表两张表第一版我确实按习惯分了两张表作品表存成品素材表存灵感。写了两天就发现大量重复代码列表页要渲染两种卡片、搜索要同时查两张表、回收站还得各写一套逻辑。于是第二版直接合并成一张 entries 表用 type 字段区分类型额外字段统一放进 meta 这个 JSON 列里。CREATE TABLE entries ( id TEXT PRIMARY KEY, type TEXT NOT NULL, -- work 成品 / material 素材 / draft 草稿 title TEXT NOT NULL, cover_url TEXT, content TEXT, tags TEXT, meta JSON, source_url TEXT, created_at INTEGER, updated_at INTEGER );合并表之后列表、搜索、标签过滤、回收站全部只写一遍作品与素材之间的互相引用直接用一个字段存目标条目 id不用考虑跨表主键转换。实际用下来的感受是这个合并远不止是少写代码更重要的是心智负担降低所有条目都用同一种方式看待不用在“我这会儿处理的是作品还是素材”上多想一秒。2.2 标签体系词频统计做候选人工规则兜底标签是检索用的地基。第一版让用户手动打标签结果是我自己都经常偷懒大量素材堆在“未分类”里。后来改成半自动新增条目时后端在标题和正文里提取候选标签按词频排序返回用户勾选或补充后保存。切词逻辑很基础按空格以及常见的中英文标点分隔再用一份停用词表过滤“关于、然后、这个、一个”这类噪声。中文没有用独立分词库而是对标题做二元字符组切分效果谈不上完美但配合人工补充足够使用。真正有价值的往往不是自动切出来的“木蜡油”“榫卯”而是“待制作”“已实现”“复购”这类状态词所以我在保存流程里加了人工规则含特定前缀的内容自动附加对应状态标签。2.3 版本快照只存增量不存全量写作品条目时描述和工序记录会被我反复改于是必须要有历史版本回滚。最初想全量存版本几十个版本下来体积膨胀得厉害尤其正文还带着图片引用。后来改成增量的行级 diff每条记录先存一个 baseline之后每次编辑只记录与上一版的文本差异配合一个递增 version 号。读取历史版本时从 baseline 开始按顺序把差异重放回去。这个方案对 Markdown 正文尤其合适因为 Markdown 天然按段落组织段落级 diff 语义清晰。为防止重放链过长我每攒到 20 个增量就重新生成一次 baseline并清理历史差异。实际使用中版本回滚非常顺滑数据体积也比全量方案小得多。3. 内容工作流从浏览器剪藏到自动保存的完整链路3.1 浏览器剪藏插件正文提取和主图选择ArCraft 使用频率最高的功能是采集灵感。我正在浏览一个作品页面点一下浏览器插件页面标题、正文、主图、来源链接会在一两秒内变成一条 typematerial 的素材条目自动打上“待分类”标签留到之后统一整理。这里几个细节很关键。正文提取不能直接取整个页面的 innerHTML 存库否则搜索时会被导航栏、页脚、推荐位噪声干扰所以要优先解析到 article 标签或者主内容区块。主图选择优先取 og:image 标签这是社交分享时专门声明给第三方用的主图可靠性最高没有时才退回到页面尺寸最大的 img并把 alt 文本也记录进 meta方便搜索。来源链接必须原样保存这样之后从素材条目跳回原网页只要一次点击。插件侧我只保留了收集功能分类和打标签都放到 Web 端处理这样插件的逻辑足够薄不随功能迭代频繁发版。3.2 图片压缩与三档缩略图列表页加载终于不卡了图片是素材和作品最主要的载体原图直接传当然最省事但列表页一旦有几十张原图浏览器基本卡死。我在早期就做了规则原图保留一份同时生成 1280、640、320 三档缩略图。列表卡片用 320详情页主图默认 640点击放大才会加载原图。缩略图宽度使用场景作用320px列表卡片、搜索结果大量图片同时加载不卡640px详情页默认主图清晰度和加载速度的平衡点1280px大图预览前的用户主动操作接近原图观感但不加载原图原图保存原始素材后续修图、打印、出版可用处理脚本的核心逻辑是按目标宽度等比缩放文件名用时间戳加随机后缀生成避免不同条目间冲突。JPG 固定 85% 质量输出PNG 只有透明通道需要保留时才会输出为 PNG否则一律转 JPG。这套规则跑完后列表页加载速度提升了大约五倍滚动浏览素材时再也没有卡顿感。3.3 编辑器选型、自动保存防抖与本地草稿兜底条目正文我用的是 Markdown 编辑器。选择它主要是因为创作者写记录时用到的就是标题、列表、引用、图片说明这几件事Markdown 足够表达而且段落式结构天然契合我前面做的增量版本快照。编辑器本身没有做深度定制只保证三件事粗体和标题快捷键、图片粘贴直传、自动保存。自动保存的防抖时间我调过几次最终稳定在两秒编辑器内容一变化延迟两秒请求保存接口如果存接口失败就把最新内容写入 localStorage下次打开编辑器时优先从本地草稿恢复。这个双保险看起来简单却在几次浏览器崩溃里真正救回了差点丢失的创作记录。具体实现是监听内容变化后启动一个 2000ms 的定时器再次变化就重置定时器到期才真正发起保存请求。4. 上线后的运维补课存储、搜索、备份三轮调整4.1 本地目录迁移到对象存储签名 URL 是怎么处理的第一版所有图片都放在服务器本地目录配合 Web 服务器直接托管单机部署很顺。问题出现在换机器部署的时候整个图片目录需要手动拷贝一旦文件多了迁移费时费力而且本地目录方案将来也很难支持多端访问。所以后来把图片统一迁到了对象存储。对象存储最容易踩的坑就是权限边界桶不能完全公开读否则图片链接等于裸奔也不能完全私有因为浏览器加载图片时需要带上鉴权信息。我的方案是预签名 URL——后端在返回条目列表时为每张图和缩略图生成一小时有效期的签名链接客户端直接使用签名链接访问对象存储不需要接触任何密钥。上传也走后端换取上传凭证文件流直接到对象存储服务器不用转发大文件既省带宽也降低负载。升级时还踩过一个和 CDN 相关的小坑签名链接和 CDN 缓存同时启用时旧签名过期但 CDN 缓存还没过期会出现加载混乱。单人自托管场景实际不需要 CDN所以直接关掉让签名 URL 直接访问存储源站。4.2 搜索从 LIKE 到全文索引中文标题如何切词ArCraft 的条目到三千多条时搜索框的响应明显变慢了。最初实现是 SQL 的 LIKE 模糊匹配胜在简单数据量小的时候毫无察觉但数据量上来之后每次搜索都相当于全表扫描等待时间越来越难接受。于是把标题和正文字段换成了全文索引。这里真正有坑的是中文标题。默认分词规则按空格和标点切分英文内容没什么问题中文标题则被当成一整段检索效果很差。我的临时方案是在写入索引前对标题做一次字符级二元切分把“深色木蜡油”切成“深色 色木 木蜡 蜡油”这样的二元组再进索引搜索时对查询词做同样的二元切分再查询。这种方法达不到专业分词库的语义效果但对资料检索足够而且不用引入额外依赖。还有一种容易忽略的情况如果前端做了离线缓存搜索请求和响应也要纳入缓存更新策略否则离线状态下搜到的是旧结果。4.3 备份策略与每周一次的恢复演练自托管系统最怕的不是缺功能而是数据全丢。ArCraft 里存的是几年的创作记录一旦丢失很难重建。备份策略分了三层数据库每天凌晨自动快照保留三十天文件对象存储开启版本管理同时每天把增量部分同步到一台不常开机的设备每周手动执行一次恢复演练把最近一个快照恢复到测试环境确认能登录、条目数对得上、图片能正常预览。恢复演练是最容易被省略的环节。很多人做了备份但从不验证真正需要恢复时才暴露问题。我演练时发现过一次恢复脚本里数据库路径写死成旧目录的错误修复之后才真正放心。每次演练结束后我会在日志里记录耗时和问题下一次演练优先检查上一次出现过的问题项。5. 真实使用反馈与下一轮迭代方向5.1 用户反馈里最意外的三个需求ArCraft 从个人使用扩展到几个朋友试用后反馈方向和我预期差别还挺大。被问得最多的是导出功能有人想把所有条目一键导成 PDF 或图片包用来制作纸质归档其次是批量导入旧笔记大家手里都有几百条历史内容不想一条条手写新建还有人问能不能让工作室几个人在局域网内共同维护同一个素材库。导出功能实现起来不算复杂后端把条目按时间线渲染成 HTML再通过无头浏览器打印成 PDF图片包则遍历条目附件按标签目录结构打包下载。批量导入则提供一个 CSV 模板按标题、正文、标签、类型的格式解析导入后先统一落为“待分类”标签再交给用户批量修正。5.2 从云服务优先转向本地优先的调整计划这些反馈让我重新审视了 ArCraft 的架构方向。当前方案是后端跑在服务器上浏览器通过公网访问离线时完全不可用而创作者恰恰经常在没网的环境里产生灵感比如木工房、户外写生地。下一步我计划把数据层改成本地优先本地数据库作为主存储远端只做同步与备份界面随时可以打开网络恢复后自动把增量推送到服务器。这个改动会牵动编辑器、图片存储和版本快照三条链路短期成本不低但更贴合项目的定位。毕竟这是给自己和身边少数创作者用的工具数据先留在本地比云端优先更让人安心。最后再分享几个折腾完之后的实操体会。数据模型尽量早统一我在作品表和素材表之间反复切换浪费了不少时间图片缩略图一定要在第一版就做等条目多起来再补改动成本会翻倍自动保存要做双层服务器接口失败时本地草稿必须能兜底。如果你也在攒一个给创作者用的自托管工具这三件事建议排进前三位。它们看起来不起眼却是每天都会用到的基础能力。
阅读完成 · 觉得有帮助?