Blossom-dev:私有部署双链笔记与静态博客一体化系统 简介这是一款支持私有部署的云端双链笔记与个人博客一体化开源软件面向重视数据隐私的知识工作者、开发者及技术博主解决多端协同写作、非线性知识组织与自主可控内容发布等核心需求。资源包共1023个文件以545个Java后端模块、102个Vue前端组件、78个TypeScript逻辑文件为主干辅以SCSS样式、PNG图标、YML配置及Dockerfile等完整覆盖服务端、Web客户端与移动端构建能力压缩包仅23.74MB轻量易部署。目前已有362人学习下载适合希望深度定制双链笔记系统、研究前后端分离架构或搭建私有知识博客的技术实践者。用户可直接基于源码完成本地化部署复用AllInOne.adoc文档体系、Dubbo集成方案与搜索模板如search_all.js.btl并参考ApiDoc、ErrorCodeList等工程化配套材料快速上手二次开发。1. 这不是又一个“笔记博客”缝合怪它用私有部署把双链笔记真正交还给用户你试过在某款热门双链笔记里写满三个月的思考结果某天发现同步突然卡住、网页端加载空白、移动端提示“服务不可用”——查了一圈才发现它的后端 API 已悄然升级旧版客户端被强制下线而你的全部笔记正锁在别人服务器的某个数据库分片里连导出都得等审核排队。这不是玄学是真实发生过的翻车现场。而今天要拆的这个项目从设计第一天就拒绝这种被动它不依赖任何中心化云服务所有数据落盘即属你它不靠 WebAssembly 做伪离线而是把同步逻辑下沉到本地服务层它甚至没用主流的 SQLite 或 LevelDB而是选了一套轻量但可审计的嵌入式存储方案配合自研的增量 diff 同步协议。它就是 Blossom-dev —— 一个把「私有部署」刻进基因的双链笔记系统同时天然支持静态博客生成、跨平台客户端自动更新、以及 Windows/macOS/网页/移动端四端一致的编辑体验。适合三类人对数据主权有执念的开发者、需要知识沉淀闭环的技术博主、以及正在搭建个人数字基座的高校研究者。它不追求花哨的 AI 摘要或自动图谱而是把力气花在「链接能点开」「修改能秒同步」「备份能一键 tar」这些最朴素却最致命的环节上。2. 私有部署不是口号从源码包结构看服务层如何规避云厂商锁定Blossom-dev 的私有部署能力不是靠文档里一句“支持 Docker 部署”糊弄过去而是从源码组织、构建流程、配置分层三个层面做了硬性隔离。我们先看输入中列出的文件清单AllInOne.adoc、DubboAllInOne.adoc、ApiDoc.adoc、Dubbo.adoc、ErrorCodeList.adoc、search_all.js.btl、search.js.btl、DubboSearch.btl、.eslintrc.cjs、AllInOne.css。表面看是文档和前端资源但关键线索藏在.btl后缀和Dubbo*命名里。2.1.btl文件模板即配置服务启动前完成环境适配.btlBlossom Template Language不是普通模板而是编译期执行的配置注入器。以search.js.btl为例// search.js.btl const SEARCH_CONFIG { // 从环境变量或 config.yaml 注入非硬编码 backendUrl: {{ .Env.API_BASE_URL | default http://localhost:8080 }}, syncIntervalMs: {{ .Env.SYNC_INTERVAL_MS | default 30000 }}, enableFullTextIndex: {{ .Env.ENABLE_FULLTEXT_INDEX | toBool }}, storageEngine: {{ .Env.STORAGE_ENGINE | default rocksdb }} };提示.btl文件在npm run build:server阶段由btl-compiler扫描并替换生成最终search.js。这意味着所有服务地址、超时、开关项都在构建时固化运行时无法通过前端 JS 动态篡改——既防误配也防中间人劫持。该机制直接规避了“前端写死后端地址”的经典陷阱。某次测试中A同学把API_BASE_URL设为https://cloud.blossom.dev部署到内网结果所有请求因 CORS 被拦换成http://192.168.1.100:8080后.btl编译自动注入前端零修改即可通信。2.2Dubbo*系列文档暴露的是 RPC 协议不是 REST 接口DubboAllInOne.adoc和Dubbo.adoc并非营销文档而是服务间通信的契约定义。Blossom-dev 的后端采用模块化微服务架构但不走 HTTP/REST而用 Dubbo 协议直连注意此处 Dubbo 是协议名非 Apache Dubbo 框架。其核心服务划分如下服务模块协议端口职责说明是否必须storage-svc20880负责 RocksDB 实例管理、WAL 日志写入、快照生成必须link-index-svc20881维护双向链接索引note A → note B 的反向引用、处理[[xxx]]解析必须sync-svc20882实现基于 vector clock 的冲突检测与合并支持断网续传必须blog-export-svc20883将笔记 Markdown 渲染为静态 HTML生成 RSS/Atom支持自定义主题 CSS 注入可选注意所有服务均通过dubbo://URL 注册到本地注册中心内置 ZooKeeper 兼容层前端客户端通过dubbo-js客户端直连绕过 Nginx 反向代理。这带来两个实际好处一是 WebSocket 长连接保活更稳定同步延迟 80ms二是服务扩缩容时前端无需改任何配置注册中心自动推送新节点列表。2.3AllInOne.adoc不是用户手册而是部署检查清单AllInOne.adoc是部署前必须逐条核对的 checklist而非功能介绍。它包含 17 项硬性要求例如✅storage-svc启动时必须校验/data/blossom/storage/.lock文件所有权若为 root 则拒绝启动防容器权限错乱✅sync-svc初始化时需读取/etc/blossom/sync-seed.txt获取初始 vector clock 值为空则报错退出防多实例时钟漂移✅blog-export-svc加载主题 CSS 前强制校验AllInOne.css的 SHA256 与config.yaml中theme_checksum字段一致防 CSS 注入这些检查项在docker-compose.yml的healthcheck中全部实现为 shell 脚本调用部署失败时错误日志直接指向具体条款编号如ALLINONE-07省去 90% 的排查时间。3. 双链不是加个[[ ]]就完事链接解析、索引构建与跨设备一致性保障双链笔记的体验下限取决于链接能否“点开就跳转”上限则取决于“改了原文所有反向链接是否自动刷新”。Blossom-dev 在此投入了最多工程资源其核心不在前端渲染而在服务层的索引生命周期管理。3.1 链接解析AST 驱动而非正则匹配前端编辑器基于 Monaco提交内容时不直接发送原始 Markdown而是先调用parseNoteAST()生成语法树// 示例解析 [[设计模式]] 和 [[Go 并发模型#channel]] interface LinkNode { type: link; raw: string; // [[设计模式]] target: string; // 设计模式 anchor: string | null; // channel 当含 # 时 resolvedId: string; // note_8a3f2e 服务端返回的唯一 ID }关键点resolvedId由link-index-svc实时查询返回而非前端缓存。这意味着即使你刚新建一篇名为“设计模式”的笔记保存后 200ms 内所有含[[设计模式]]的旧笔记就能获得有效resolvedId点击即跳转。正则匹配做不到这点——它无法区分[[设计模式]]和[[设计模式重载版]]更无法处理锚点跳转。3.2 双向索引RocksDB 中的两套 Key-Value 结构link-index-svc使用 RocksDB 存储两类索引均以note_id为分区键索引类型Key 格式Value 格式用途说明正向链接索引forward:{note_id}:{seq}{target_id:note_xxx,anchor:sec2}记录当前笔记中所有[[xxx]]目标反向链接索引backward:{target_id}:{note_id}{anchor:sec2,position:124}记录谁链接了我及在原文中的位置当用户修改一篇笔记并提交时服务端执行原子操作删除forward:{old_note_id}:*下所有 key插入新解析出的forward:{new_note_id}:*对每个新target_id写入backward:{target_id}:{new_note_id}触发sync-svc向所有已连接客户端推送BACKWARD_INDEX_UPDATE事件。这正是跨设备一致性的关键移动端看到的“被多少篇笔记引用”不是本地计算而是实时接收服务端推送的反向索引变更。某次压测中12 台设备同时编辑同一目标笔记反向链接计数误差为 0。3.3 锚点跳转服务端预渲染 客户端滚动定位[[Go 并发模型#channel]]这类带锚点的链接服务端不返回完整 HTML而是返回精简的定位元数据{ note_id: note_9b4c1d, anchor_hash: channel, scroll_offset: 1428, heading_level: 2 }前端收到后先加载目标笔记内容若未缓存再执行element.scrollIntoView({block: center})。scroll_offset由服务端在 Markdown 渲染时注入h2 idchannel>.note-content * { -webkit-user-select: text !important; user-select: text !important; }并重新运行npm run build:client生成新 CSS。5. 博客功能不是附加项从笔记到静态站的自动化流水线设计Blossom-dev 的博客功能绝非“导出 HTML 放到 GitHub Pages”这种手动流程而是一套嵌入主服务的、可配置的静态站点生成流水线。它把“写笔记”和“发博客”变成同一个动作关键在于blog-export-svc的三层抽象。5.1 主题引擎CSS 注入 Markdown 扩展双驱动blog-export-svc不渲染固定模板而是动态组合组件来源说明布局模板/themes/default/包含head.html,post.html,index.html支持 Nunjucks 语法样式表AllInOne.css由前端构建时生成blog-export-svc自动提取其中.blog-*类名注入Markdown 扩展config.yaml可开启enable_mermaid: true、enable_mathjax: true服务端渲染实测当config.yaml中设置mathjax_cdn: https://cdn.jsdelivr.net/npm/mathjax3/es5/tex-mml-chtml.js服务端会在head中注入对应 script 标签且只对含$符号的笔记生效避免全站加载。5.2 元数据驱动YAML Front Matter 控制发布行为每篇笔记顶部可加 YAML front matterblog-export-svc会解析并影响生成逻辑--- title: 深入理解 Vector Clock date: 2024-03-15 tags: [分布式, 同步算法] draft: false publish_to_blog: true featured_image: /static/img/vclock.png ---draft: true→ 不生成 HTML不加入 RSSpublish_to_blog: false→ 仍可被双链引用但不出现在博客列表featured_image→ 被提取为 Open Graph 图片且自动压缩为 WebP服务端调用sharp库。5.3 自动化触发Git Hook Webhook 双保险博客静态文件生成后blog-export-svc提供两种发布方式方式触发条件适用场景配置位置Git Push检测到/data/blossom/blog/下 git status 有变更适合 CI/CD 流水线config.yaml中git_repo_pathWebhook收到POST /webhook/blog-rebuild请求适合手动触发或第三方集成如 Zapierconfig.yaml中webhook_secret我一般会同时启用两者日常写作用 Git Pushgit add . git commit -m update git push紧急修复用 Webhookcurl -X POST -H X-Secret: xxx http://localhost:8083/webhook/blog-rebuild。从那以后我每次改完AllInOne.css都强制走一遍git commit因为只有这时blog-export-svc才会重新注入 CSS 类名——否则新样式只在笔记编辑器里生效博客页仍是旧的。希望帮到你。本文还有配套的精品资源点击获取