一套面向中小团队的轻量级文档管理系统。
如果你熟悉 MinDoc,PlainDoc 的目标很直接:保留文档系统的核心价值,同时带来更现代的编辑体验和更省心的工程化部署。
开箱即可获得:
- Go 后端(Gin + GORM)
- React 前端编辑器(Vite + CodeMirror)
- 首页/分类页 SSR
- 空间阅读页 SSR(对 SEO 友好)
- 后台管理(用户、空间、文档、主题、系统配置、审计)
| 页面 | 截图 |
|---|---|
| 后台概览页 | ![]() |
| 后台个人信息页 | ![]() |
| 后台用户管理页面 | ![]() |
| 空间管理 | ![]() |
| 新建空间 | ![]() |
| 空间分类 | ![]() |
| 空间成员 | ![]() |
| 文档管理 | ![]() |
| 附件管理 | ![]() |
| 图片管理 | ![]() |
| 主题管理 | ![]() |
| 审计日志 | ![]() |
| 文档编辑 | ![]() |
| 首页 | ![]() |
| 文档阅读 | ![]() |
PlainDoc 是一个真正可落地的文档平台,而不是“仅能写 Markdown 的编辑器”。
我们希望它帮助团队实现三件事:
- 用 Markdown 快速搭建团队知识库/技术文档库
- 提供“编辑、阅读、管理”一体化能力,而不是单纯编辑器
- 以最低维护成本长期运行(单体部署、权限治理、可观测)
适用场景:
- 团队内部知识沉淀
- 技术文档中心
- 小中型项目文档门户
- 运维手册/流程文档管理
- 空间与文档树:支持空间、目录、文档的层级组织
- Markdown 编辑:左树 + 中间编辑 + 右侧预览的工作台体验
- 阅读系统:独立阅读页(
/r/:spaceId/:docId),适合分享与访问 - 权限模型:
owner / collaborator / reader多角色访问控制 - 版本能力:文档版本号、冲突检测、修订记录
- 图片链路:上传、鉴权、静态回源、Markdown 直接渲染
- 后台治理:用户、空间、文档、主题、系统配置、审计管理
- 轻量但完整:一套系统同时覆盖编辑、阅读、权限与后台治理
- 单体部署体验:Go 主服务 + Node SSR Worker 子进程,无需独立 Node SSR 服务
- 渲染一致性:阅读 SSR 与编辑器预览复用同一套 Markdown 渲染链路
- 双 SSR 架构:
- 首页/分类页:Go 模板 SSR
- 阅读页:React SSR(由 Go 调度 Worker)
- 开发和生产都顺手:前端统一走后端 API 模式(
http),联调与生产一致 - 工程化可维护:统一响应协议、错误码体系、Docker + Compose 开箱部署
已落地核心能力:
- 文档编辑器(左侧文档树 + 中间编辑 + 右侧预览)
- 空间与文档权限模型(
owner / collaborator / reader) - 图片上传与本地静态回源(
/uploads/*) - 首页与分类页 SSR(
/、/explore/:categoryId) - 空间阅读页 SSR(
/r/:spaceId、/r/:spaceId/:docId) - 阅读页友好错误页与登录重定向
- 后台管理台与空间治理能力
- 编辑与协作工作台:
- 左侧文档树 + 中间编辑 + 右侧预览的一体化工作台
- 文档版本号、冲突检测、修订记录
- Markdown 模板能力,可在后台自定义模板场景和模板内容
- 阅读与分享体验:
- 独立阅读页 SSR,适合知识门户、SEO 收录与外部访问
- 单页分享能力,支持阅读密码、过期时间与分享链路访问控制
- 阅读页友好错误页、登录重定向与 Office 文档源文件下载
- Office 文档支持:
- 可配置 ONLYOFFICE,实现 Word / Excel 在线编辑
- DOCX / XLSX 支持独立阅读页、本地 HTML 渲染与回退阅读方案
- 附件与图片体系:
- 编辑文档时可上传附件,阅读页支持下载鉴权与预览分流
- 后台可管理附件资源、图片资源与引用状态
- 对未引用图片可执行自动清理,降低存储冗余
- 搜索与平台能力:
- 基于 Golang 内置 Bleve 的全文索引,支持自定义词典
- 支持对接邮箱,可用于找回密码等系统通知场景
- 后台内置用户、空间、文档、主题、系统配置与审计治理能力
参考:
docs/DAILY_PROGRESS_2026-02-23.mddocs/HOMEPAGE_SSR_IMPLEMENTATION_PHASES.mddocs/SPACE_READER_SSR_SUBPROCESS_IMPLEMENTATION_PHASES.md
- Go
1.26 - Gin
1.11 - GORM
1.31 - 数据库驱动:SQLite / MySQL / PostgreSQL
- 默认 SQLite 驱动:
modernc.org/sqlite
- React
19 - Vite
7 - TypeScript
5.9 - CodeMirror
6 - React Markdown + remark/rehype 渲染链
- Mermaid / KaTeX / 语法高亮
- 首页/分类页:Go
html/template - 阅读页:Go 通过
stdin/stdout调用 Node SSR Worker(非独立 Node 服务)
.
├── apps
│ ├── server # Go 服务端
│ └── web # React 前端
├── docs # 方案、里程碑、日报、踩坑记录
├── Dockerfile # 多阶段构建镜像(web + ssr worker + go)
├── docker-compose.yml # 单容器部署(含 SSR Worker)
└── README.md
要求:
- Docker Engine + Docker Compose v2
在仓库根目录执行:
docker compose build
docker compose up -d访问地址:
http://localhost:8080
常用命令:
docker compose logs -f plaindoc
docker compose up -d --build
docker compose down
docker compose down -v默认持久化卷:
plaindoc_data->/app/data(SQLite 数据)plaindoc_uploads->/app/uploads(上传文件)
前置要求:
- Node.js
>= 20(当前 Docker 使用 Node 24) - npm
- Go
1.26+
npm installcd apps/server
cp .env.example .env
go mod tidy
go run ./cmd/server默认后端地址:http://localhost:8080
# 回到仓库根目录
npm run web:dev默认前端地址:http://localhost:3001
提示:开发时建议通过 Vite 反向代理访问后端,不要直接在浏览器跨端口访问
/api。
npm run web:build等价于执行:
build:client->apps/web/distbuild:ssr-worker->apps/web/dist-ssr
cd apps/server
go test ./...docker build -t plaindoc:latest .
docker run --rm -p 8080:8080 plaindoc:latest当你推送 tag 后,Release 工作流会上传以下文件:
plaindoc-server-linux-amd64:Linux amd64 的后端可执行文件(Go 编译产物)。plaindoc-server-linux-amd64-<tag>.tar.gz:一体化部署压缩包,包含后端可执行文件、apps/web/dist和apps/web/dist-ssr。plaindoc-web-<tag>.tar.gz:前端发布产物压缩包,包含dist(前端静态资源)和dist-ssr(阅读页 SSR Worker 产物)。checksums-<tag>.txt:上述产物的 SHA256 校验清单。
默认账号: admin@iminho.me 默认密码:123456
- 下载对应 tag 的核心文件:
plaindoc-server-linux-amd64-<tag>.tar.gz、checksums-<tag>.txt。
plaindoc-web-<tag>.tar.gz为可选文件(仅前端产物包,适合只替换前端时使用)。 - 校验文件完整性:
sha256sum -c checksums-<tag>.txt- 解压到部署目录:
mkdir -p /opt/plaindoc/apps/web
tar -xzf plaindoc-server-linux-amd64-<tag>.tar.gz -C /opt/plaindoc
chmod +x /opt/plaindoc/plaindoc-server-linux-amd64- 配置并启动(示例):
export APP_ENV=production
export APP_ADDR=:8080
export WEB_ORIGIN=http://your-domain-or-ip:8080
export WEB_DIST_DIR=/opt/plaindoc/apps/web/dist
export DB_DRIVER=sqlite
export DB_DSN='file:/opt/plaindoc/data/plaindoc.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)'
export SSR_WORKER_ENABLED=true
export SSR_WORKER_EXEC=node
export SSR_WORKER_ENTRY=/opt/plaindoc/apps/web/dist-ssr/worker-entry.js
/opt/plaindoc/plaindoc-server-linux-amd64提示:阅读页 SSR 依赖 Node 子进程。若服务器未安装 Node,请先安装 Node,或临时设置 SSR_WORKER_ENABLED=false。
说明:工作流会始终推送 <tag> 镜像标签;当 Git tag 以 v 开头时,额外推送 latest。
拉取镜像:
docker pull lifei6671/plaindoc:latest直接运行:
docker run -d \
--name plaindoc \
-p 8080:8080 \
-e APP_ENV=production \
-e WEB_ORIGIN=http://localhost:8080 \
-e DB_DRIVER=sqlite \
-e DB_DSN='file:/app/data/plaindoc.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)' \
-v plaindoc_data:/app/data \
-v plaindoc_uploads:/app/uploads \
--restart unless-stopped \
lifei6671/plaindoc:latest如果不想本地 build,可使用以下 compose:
services:
plaindoc:
image: lifei6671/plaindoc:latest
container_name: plaindoc
ports:
- "8080:8080"
environment:
APP_ENV: "production"
APP_ADDR: ":8080"
WEB_ORIGIN: "http://localhost:8080"
WEB_DIST_DIR: "/app/apps/web/dist"
DB_DRIVER: "sqlite"
DB_DSN: "file:/app/data/plaindoc.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)"
DB_AUTO_MIGRATE: "true"
SSR_WORKER_ENABLED: "true"
SSR_WORKER_EXEC: "node"
SSR_WORKER_ENTRY: "/app/apps/web/dist-ssr/worker-entry.js"
volumes:
- plaindoc_data:/app/data
- plaindoc_uploads:/app/uploads
restart: unless-stopped
volumes:
plaindoc_data:
plaindoc_uploads:启动:
docker compose up -d/:首页 SSR/explore/:categoryId:分类页 SSR/r/:spaceId:空间阅读入口(跳转首篇可读文档)/r/:spaceId/:docId:阅读页 SSR(Go -> Node Worker)/login、/register、/editor/*、/admin/*:Web SPA(生产由 Go 托管WEB_DIST_DIR)/uploads/*path:本地上传文件公开访问
/api/healthz/api/auth/*/api/spaces/*、/api/nodes/*、/api/docs/*/api/admin/*/api/uploads/images
完整注册点见:apps/server/internal/server/router.go
- 由 Go 模板直接渲染
- 模板静态资源走
/assets/*
流程:
- 浏览器请求
/r/:spaceId/:docId - Go 侧聚合阅读数据并鉴权
- Go 通过 JSONL 协议把 payload 发给 Node Worker
- Node Worker 返回 HTML/head/metrics
- Go 输出完整页面
关键配置(服务端):
SSR_WORKER_ENABLEDSSR_WORKER_EXECSSR_WORKER_ENTRYSSR_WORKER_COUNTSSR_RENDER_TIMEOUTSSR_WORKER_START_TIMEOUTSSR_WORKER_MAX_PAYLOAD_BYTESSSR_PROTOCOL_VERSION
当 Worker 不可用时,阅读页会降级到基础 HTML 兜底,而不是直接雪崩 500。
后端 API 统一返回结构:
{
"code": 0,
"message": "success",
"requestId": "xxxx",
"data": {}
}说明:
- 业务成功:
code = 0 - 业务失败:
code != 0(例如3001 ROUTE_NOT_FOUND) - 当前错误 HTTP 状态会收敛(认证类为 403,其它多为 200),前端应优先按
code处理
定义见:
apps/server/internal/server/response/error.goapps/server/internal/server/response/error_codes.go
样例文件:apps/server/.env.example
常用项:
APP_ENV、APP_ADDRWEB_ORIGIN(CORS)WEB_DIST_DIR(生产托管 SPA 的 dist 目录)DB_DRIVER、DB_DSN、DB_AUTO_MIGRATEJWT_*SSR_WORKER_*
.env 加载顺序(启动时自动读取):
.envapps/server/.envcmd/server/.envapps/server/cmd/server/.env
且系统环境变量优先级更高,不会被 .env 覆盖。
样例文件:apps/web/.env.example
核心项:
VITE_API_BASE_URLVITE_DEV_PROXY_TARGET
- 检查是否走了正确路径:
- 阅读:
/r/... - 图片:
/uploads/...
- 阅读:
- 开发模式确认 Vite 代理是否开启(
/r、/uploads、/api)
- 检查
SSR_WORKER_ENTRY是否存在 - 检查 Node 可执行文件是否在 PATH(
SSR_WORKER_EXEC) - 检查协议版本是否一致(
SSR_PROTOCOL_VERSION)
- 重启服务
- 查看
server starting日志中的配置摘要
- 当前实现已把预览样式统一注入阅读 SSR
- 如出现差异,优先检查
apps/web/src/ssr/render-space-reader.tsx与apps/web/src/styles.css
当前主文档(建议优先阅读):
docs/README.md(docs 导航总入口)docs/FRONTEND_DEVELOPER_GUIDE.mddocs/BACKEND_DEVELOPER_GUIDE.mddocs/ENGINEERING_STANDARDS.md
历史方案与阶段文档(用于追溯决策与实施细节):
docs/BACKEND_IMPLEMENTATION_PHASES.mddocs/HOMEPAGE_SSR_IMPLEMENTATION_PHASES.mddocs/SPACE_READER_SSR_SUBPROCESS_TECHNICAL_PROPOSAL.mddocs/SPACE_READER_SSR_SUBPROCESS_IMPLEMENTATION_PHASES.mddocs/ADMIN_CONSOLE_IMPLEMENTATION_PHASES.mddocs/ADMIN_CONSOLE_RELEASE_CHECKLIST.mddocs/SPACE_CATEGORY_REFACTOR_NOTES.mddocs/ADMIN_SPACE_CREATE_WITH_COVER_IMPLEMENTATION_PHASES.mddocs/DAILY_PROGRESS_2026-02-22.mddocs/DAILY_PROGRESS_2026-02-23.mddocs/ai-handoff-pitfalls.mddocs/backend-ai-handoff.md
本项目使用仓库根目录 LICENSE。














