主题
ZMBlog · 使用文档
适用版本:ZMBlog v2.3.1 阅读对象:站点所有者、内容编辑、主题与插件使用者
目录
1. 产品概述
ZMBlog是一套自研的轻量级博客内核,特点是:
| 特性 | 说明 |
|---|---|
| 零第三方依赖 | 仅需 Node.js,数据库用内置 node:sqlite,无npm 包、无编译步骤 |
| 伪静态路由 | 32 条内置规则,正则与路由表达式双栏配置,可视化排序与预览 |
| 插件系统 | 过滤器 / 动作两类钩子,可注入模板函数、注册后台配置面板 |
| 主题系统 | 模板文件 + 主题函数 + 主题配置,支持后台在线编辑与安全闸门 |
| 内容形态 | 文章、独立页面、分类、标签、评论、附件、自定义字段 |
| 发布控制 | 草稿、待审核、定时发布、私密、密码保护、置顶 |
| 安全基线 | scrypt 密码哈希、CSRF 校验、登录限流、上传白名单、安全响应头 |
程序目录职责:
themes/—— 主题(可自由增删)plugins/—— 插件(可自由增删)data/—— 运行时数据:zmblog.db数据库、uploads/上传文件、日志
2. 五分钟上手
2.1 启动
bash
node server.js默认监听 http://localhost:3000。若端口被占用:
bash
PORT=8080 node server.js2.2 完成安装
首次访问会自动跳转到安装向导,依次五步:
- 环境检测 —— 自动校验 Node 版本、
node:sqlite可用性、目录可写性、数据库可读写 - 创建管理员 —— 填写用户名、昵称、邮箱、密码(前端实时强度提示,后端要求≥8 位且强度达标)
- 站点信息 —— 站点标题、描述、站点地址、作者昵称、页脚文案
- 伪静态 —— 选择预设方案(默认 32 条规则),也可稍后在后台自定义
- 完成 —— 可勾选生成示例内容,便于熟悉各功能
安装完成后自动标记 installed,此后访问根路径直接进入站点,安装入口关闭。
2.3 登录后台
访问 http://localhost:3000/admin/,用第2 步创建的管理员账号登录。
3. 后台界面总览
后台为单页应用,左侧导航分区如下:
| 分区 | 功能 |
|---|---|
| 仪表盘 | 文章 / 页面 / 评论 / 分类 / 标签数量统计、主题与插件状态、最近操作 |
| 文章 | 列表检索、批量状态切换、删除、编辑器 |
| 页面 | 独立页面管理(关于、友链等) |
| 分类 | 分类增删改、排序、别名设置 |
| 标签 | 标签增删改、文章计数 |
| 评论 | 评论列表、审核(通过 / 待审 / 垃圾 / 删除)、回复查看 |
| 附件管理 | 上传、筛选、与文章 / 页面绑定、删除 |
| 用户 | 用户增删改、角色分配、重置密码 |
| 设置 | 站点信息、阅读设置、评论设置、伪静态、模板编辑器 |
| 主题 | 主题切换、主题配置、模板文件在线编辑 |
| 插件 | 插件列表、启用/停用、配置面板 |
| 日志 | 操作日志检索与清空 |
| 数据 | 导出 / 导入 |
4. 写作与内容管理
4.1 新建文章
后台「文章 → 新建」,主要字段:
| 字段 | 说明 |
|---|---|
| 标题 | 必填,留空自动记为「无标题」 |
| 别名 | URL标识,留空按标题生成;冲突时自动追加序号 |
| 正文 | 支持 Markdown 渲染 |
| 摘要 | 留空时自动截取;正文含 <!--more--> 时以该处为摘要终点 |
| 分类 / 标签 | 可多选,标签输入后回车确认 |
| 发布时间 | 可设为未来时间(定时发布) |
| 状态 | 发布 / 草稿 / 待审核 / 私密 |
| 访问密码 | 填写后访客需输入密码才能查看 |
| 允许评论 | 关闭后前台不显示评论表单 |
| 置顶 | 置顶文章在首页优先展示 |
| 模板 | 指定该篇使用的模板(默认 post) |
| 自定义字段 | 键值对,模板中通过 fields.xxx 访问 |
4.2 文章状态
| 状态 | 前台可见性 |
|---|---|
publish 发布 | 所有人可见;若发布时间在未来,到时间才对访客可见(作者与管理员可预览) |
draft 草稿 | 仅作者与管理员可见,对匿名访客返回 404 |
pending 待审核 | 同草稿 |
private 私密 | 仅已登录用户可见,匿名访客 404 |
安全设计:草稿、私密、定时未到的文章,其正文不会出现在列表页、归档页、搜索结果、RSS/Atom 输出或页面源码中。
4.3 密码保护文章
填写「访问密码」后:
- 访客访问该文章 URL 会看到密码输入页
- 输入正确密码后,本次会话内可访问
- 密码错误返回提示,不泄露正文
4.4 编辑与删除
- 列表页可直接改状态,无需进入编辑
PUT更新为局部更新:只提交需要修改的字段,未出现的字段保持原值(不会因漏传而清空正文)- 删除为级联友好:分类 / 标签下有文章时拒绝删除并提示数量;文章删除同时清理其自定义字段
4.5 自定义字段
在编辑器底部添加任意键值对,例如:
subtitle=第一版修订
project_ref=ZD-2041
rating=9模板中访问:
html
{{ post.fields.subtitle }}
{{ post.fields.rating }}5. 分类与标签
5.1 分类
分类支持多级父子结构与排序值,用于组织文章主脉络。
- 别名(slug)决定前台路径
/category/<别名>/ count为该分类下文章数,由内核自动重算- 分类下仍有文章时,删除请求被拒绝并返回文章数
- 删除父分类时会递归收集全部子孙分类
5.2 标签
标签无层级,以别名唯一标识,前台路径 /tag/<别名>/。
分类与标签使用独立的关联表,不会互相污染——一篇文章只属于它显式指定的分类与标签。
6. 评论管理
6.1 前台评论
访客在文章页填写昵称、邮箱、内容提交;邮箱不会公开显示。URL 为选填项。
6.2 审核状态
| 状态 | 前台是否显示 |
|---|---|
approved 已通过 | 显示 |
waiting 待审核 | 不显示 |
spam 垃圾 | 不显示 |
6.3 防垃圾
- 同一 IP 一分钟内提交上限(默认 10 条,可在「设置 → 评论」调整
commentRateLimit) - 评论内容中链接数超阈值自动判为垃圾
- 文章处于草稿 / 待审核 / 私密 / 已关闭评论时不接受留言
- 可在「设置 → 评论」开启「评论需审核」,新评论默认进入待审核
6.4 回复与嵌套
评论支持以 parent 指向父评论,形成树形展示。父评论被删除时,子回复自动上提到顶层,不会丢失内容。
后台评论接口支持新建、编辑、删除,删除时连带清理子回复。
7. 附件管理
「附件管理」页面(菜单图标 📎,旧「媒体」页已升级为此页)用于集中管理站内所有上传文件:
- 任意白名单类型:jpg / png / gif / webp / bmp / ico / avif / pdf / txt / md / csv / json / xml / zip / gz / tar / 7z / mp3 / wav / ogg / mp4 / webm / mov / woff / woff2 / ttf / otf 等(可在设置中调整)
- 始终拒绝 svg / html / js / php / sh / exe 等可执行或可内联执行类型(防存储型 XSS),后台设置也无法放开
- 无扩展名时按文件头嗅探类型
- 单文件上限默认 8 MB(可在「设置 → 附件上传」调整,范围 0.1–100 MB)
- 文件名形如
<时间戳>-<随机串>.<扩展名>,存于data/uploads/ - 下载地址形如
/upload/<文件名>,可直接插入正文
列表支持按 全部 / 已绑定 / 未绑定、文件类型(图片 / 其他)筛选,并可搜索原文件名; 每项显示缩略图、类型、大小、归属文章,可复制链接、绑定 / 改绑到文章或页面、删除。
7.1 与文章 / 页面绑定
- 每个附件有唯一归属
cid:cid = 0表示未绑定(游离素材),cid > 0表示绑定到某篇文章或页面。 - 在文章 / 页面编辑器里:点击「上传附件」上传后会直接绑定到本篇;也可「从附件库选」挑已有素材; 点「插入」会把链接写到正文光标处(图片则按 Markdown 图片语法插入)。保存时以当前列表为准, 被移出列表的附件自动解绑(文件保留)。
- 新建内容时允许先上传附件(暂为未绑定),保存成功后统一绑定。
- 删除文章只解绑、不删文件:素材完整留在附件库;删除附件才会连同磁盘文件一起删除。
7.2 前台展示
默认主题(zm-default)会在正文下方自动输出「附件下载」区块。自定义主题可用:
html
{{#if post.attachments}}
<ul>
{{#each post.attachments}}
<li><a href="{{ url }}" download>{{ original }}</a> <span>{{ sizeText }}</span></li>
{{/each}}
</ul>
{{/if}}8. 用户与权限
8.1 角色
| 角色 | 权限 |
|---|---|
administrator 管理员 | 全部权限,含用户管理、插件、模板编辑 |
editor 编辑 | 内容管理 |
contributor 投稿 | 仅本人内容 |
subscriber 订阅者 | 仅评论 |
visitor 访客 | 匿名 |
后台 API 除公开接口外,统一要求管理员或编辑角色。
8.2 密码与会话
- 新密码要求≥8 位并通过强度校验
- 修改密码后可选择「保留当前会话」或「注销全部会话」
- 注销后旧会话立即失效
- 「安全 → 会话」可查看当前账号的活跃会话(IP、User-Agent、创建时间)
8.3 登录保护
同一 IP 连续登录失败达到阈值(默认 8 次)即锁定 900 秒,锁定期间即使密码正确也会被拒。
9. 站点设置
「设置」分区包含:
| 分组 | 主要项 |
|---|---|
| 站点信息 | 站点标题、描述、站点地址、作者昵称、作者邮箱、页脚文案、备案号 |
| 阅读设置 | 每页文章数、摘要长度、日期格式、时间格式、永久链接风格 |
| 评论设置 | 是否需审核、防垃圾开关、IP 频率上限 |
| 媒体设置 | 上传大小上限 |
| 附件上传 | 允许的扩展名、单文件大小上限(0.1–100 MB) |
| SEO | keywords、description、站点地图与 robots 开关 |
永久链接风格:
| 值 | 生成路径 |
|---|---|
origin(默认) | 文章 /<article>/<编号>.html、页面 /<别名>.html(对齐原站 / Typecho 风格) |
default | /archives/<别名>/ |
date | /archives/<年>/<年-月>/<别名>/ |
id | /archives/<数字 ID>/ |
风格切换后,新生成的链接按所选风格输出;内置伪静态同时保留 /archives/... 系列的旧链接 (别名 / 数字 / 年月归档均可命中),因此切换风格不会让历史链接立即失效。
10. 主题使用与切换
默认随程序附带一套基础主题 zm-default;其余主题(zm-riven、zm-minimal)为独立分发,请到文档站「主题 / 插件」页下载,解压进 themes/ 目录后即可在后台看到。
官方主题:
| 主题 | 特点 |
|---|---|
zm-default 默认主题 | 随程序附带。三栏布局,含侧边栏、归档树、最新评论、目录样式,适合内容型站点 |
zm-riven Riven(移植版) | 需单独下载。移植自 Typecho 的 Riven 主题(对齐原站外观):首页 / 文章 / 归档 / 关于 / 友链 / 留言板 / 说说 / 走心等页面,含 PJAX 无刷新跳转、深色模式、评论徽章与 IP 属地、同源头像代理等 |
zm-minimal 极简主题 | 需单独下载。单栏窄版,正文优先,适合写作与文档 |
切换主题:「主题」页选中目标主题 →「启用」。启用后立即生效,无需重启。
主题设置:每个主题可在 theme.json 的 config 中声明配置项(文本框 / 多行文本 / 下拉 / 复选 / 单选等),选中主题后进入「主题设置」即可看到表单,保存即时写入 themeConfig,模板中通过 themeConfig.xxx 读取;「恢复默认」可一键回到主题初始配置。zm-riven 提供 60 余项配置(站点外观、侧栏名片、文章页、页脚与导航、友情链接、关于页、评论、CDN 等)。
11. 模板在线编辑
「主题 → 模板」可直接编辑主题内的模板文件:
- 选择文件(如
index.html) - 在编辑器中修改,点击保存
- 前台刷新即可看到效果
可编辑类型:.html .htm .css .txt .md .svg 及无扩展名文件。
禁止写入(安全闸门):.js .mjs .cjs .json .node .exe .sh .php。原因是主题的 functions.js 会被 require 执行,允许写入即等价于远程代码执行。写入非白名单类型时接口直接拒绝。
所有模板路径均经归一化校验,禁止越出主题目录。
建议:在线编辑适合小改与实验;大改动请在本地用编辑器完成,并纳入版本管理。
12. 插件使用
插件为独立分发:请到文档站「主题 / 插件」页下载所需插件,解压进 plugins/ 目录后即可在后台看到。官方插件:
| 插件 | 作用 |
|---|---|
reading-enhance 阅读增强 | 预计阅读时长、阅读速度、文章末尾附言、二级标题目录(演示内容过滤 / 模板函数 / 后台面板 / 配置项四种能力) |
welcome 欢迎卡片 | 首页欢迎卡片:访客属地 + 实时天气(和风天气或 Open-Meteo 免密钥)、自动收起、每 N 天一次 |
comment-notifier 评论邮件通知 | 新评论 / 回复 / 待审核时发邮件(零依赖 SMTP、多套模板、异步队列) |
links 友情链接 | 友链管理(分类、图标、导入导出),并提供 /page/links 友链模板 |
email-subscribe 邮件订阅 | 订阅确认 / 退订、人机验证、新文章群发 |
doge-upload 多吉云上传 | 接管上传到多吉云对象存储,前台直接走 CDN |
启用 / 停用:「插件」页面点击开关,即时生效,无需重启。
配置:每个插件可声明配置字段,插件页自动渲染表单(如阅读增强的阅读速度、欢迎卡片的天气 Key 等)。
停用后其所有过滤器、模板函数、面板一并移除,前台立即恢复为无插件状态。
插件的前台静态资源(
/plugins/<插件名>/assets/**)由内核单独放行,其余插件源码一律不可直接访问。
开发自己的插件见《插件开发文档》。
13. 伪静态设置
「设置 → 伪静态」提供规则的增删改、启停与排序。
13.1 规则结构
| 字段 | 说明 |
|---|---|
regex | 匹配请求路径的正则字符串 |
rule | 路由表达式,决定该路径映射到哪个模板与参数 |
enable | 是否启用 |
desc | 备注,仅用于识别 |
13.2 内置规则一览
默认规则集共 32 条,按「原站风格(origin)优先 + 旧链接兼容」组织:
| 类别 | 路径 | 说明 |
|---|---|---|
| 首页 | /、/page/<数字>/ | 首页与分页 |
| 文章 | /article/<数字编号>.html | 对齐原站 |
| 独立页面 | /<别名>.html | 对齐原站 |
| 分类 | /<分类别名>/、/<分类别名>/page/<数字>/ | 对齐原站 |
| 标签 | /tag/<名称>/、/tag/<名称>/page/<数字>/ | 对齐原站 |
| 搜索 | /search/、/search/<关键词>/、/search/<关键词>/page/<数字>/ | |
| 订阅 | /feed/、/feed/rss/、/feed/atom/、/atom | RSS 1.0(RDF)/ Atom |
| 评论订阅 | /feed/article/<编号>.html、/feed/<别名>.html、/feed/rss/….html、/feed/atom/….html | 单篇内容评论订阅 |
| 归档 | /archive/、/archives/<年>/<月>/ | 归档列表与年月归档 |
| 站点地图 | /sitemap、/sitemap.xml、/robots.txt | |
| 旧链接兼容 | /archives/<别名>、/archives/<年>/<月>/<别名>、/page/<别名>、/category/<分类> 等 | 保证老地址不 404 |
完整的正则与路由表达式可在后台「设置 → 伪静态」的规则列表里逐条查看与编辑。
13.3 优先级
规则顺序即优先级,自上而下首次命中即返回。 因此数字分页规则必须排在通用页面规则之前,日期型规则必须排在宽松别名规则之前。调整顺序时请保持这一约束。
13.4 自定义示例
新增「按年份 + 分类名」访问文章:
正则:^/notes/([0-9]{4})/([^/]+)/?$
表达式:index.php/[archives]/[date:y]/[slug:slug]新增后 /notes/2026/hello-world 即可访问该文章。
13.5 校验与重置
- 正则在保存时做语法校验,非法正则会被拒绝并返回具体错误
- 「预览」可输入任意路由表达式,实时查看对应的示例路径
- 「重置」恢复为 32 条默认规则
注意:修改规则后请同步调整反向链接策略。规则只影响 URL 生成与请求匹配,不自动重写已发布内容中的硬编码链接。
14. 数据导入导出
「数据」页提供:
- 导出 —— 生成完整 JSON 备份,含文章、页面、分类、标签、评论、选项等。为安全起见,导出内容不含
installed标志与用户密码哈希,导入到新站点后仍需自行创建管理员。 - 导入 —— 支持
merge(按 slug / 别名合并,覆盖同标识内容)与replace(清空后导入)两种模式,导入前会校验数据结构版本。
建议在升级或迁移前先导出备份。
15. 操作日志
「日志」页记录关键操作:登录成功/失败、文章增删改、评论审核、主题与插件启停、伪静态保存、模板写入、用户与设置变更等。
每条包含时间、类型、IP、简要信息。支持按类型筛选与一键清空。
排查问题时可先看日志,能快速定位是配置错误还是外部访问。
16. REST API 速查
后台界面即基于同一套 REST API 构建,脚本化运维可直接调用。
通用约定
- 基础路径
/api - 认证方式:登录后 Cookie(
zmblog_session) - 所有写操作需附带会话绑定的 CSRF 令牌,请求头
X-CSRF-Token或请求体csrf字段 - 响应统一为
{ "code": 0, "msg": "ok", "data": {...} };出错时{ "error": "说明" }并带相应 HTTP 状态码
认证
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/auth/login | 登录(公开) |
| POST | /api/auth/logout | 退出 |
| GET | /api/auth/me | 当前用户(公开) |
内容
| 方法 | 路径 |
|---|---|
| GET / POST | /api/posts |
| GET / PUT / DELETE | /api/posts/:cid |
| POST | /api/posts/:cid/fields |
| GET / POST | /api/categories |
| PUT / DELETE | /api/categories/:cid |
| GET / POST | /api/tags |
| PUT / DELETE | /api/tags/:mid |
| GET / POST | /api/comments |
| PUT / DELETE | /api/comments/:coid |
| POST | /api/comments/public(公开) |
| GET | /api/comments/recent |
外观
| 方法 | 路径 | 说明 |
|---|---|---|
| GET / POST | /api/options | 站点配置 |
| GET | /api/themes | 主题列表 |
| POST | /api/themes/activate | 启用主题 |
| GET / POST / DELETE | /api/themes/file | 模板文件读写删 |
| POST | /api/themes/config | 主题配置 |
| GET / POST | /api/rewrite | 伪静态规则 |
| POST | /api/rewrite/reset | 重置规则 |
| POST | /api/rewrite/preview | 规则预览 |
扩展
| 方法 | 路径 |
|---|---|
| GET | /api/plugins |
| POST | /api/plugins/:name/toggle |
| POST | /api/plugins/:name/config |
| GET / POST | /api/users |
| PUT / DELETE | /api/users/:uid |
| POST | /api/password |
| GET | /api/sessions |
运维
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/dashboard | 统计 |
| GET / POST | /api/logs | 日志查询 / 清空 |
| POST | /api/upload | 上传附件(可带 ?cid= 直接绑定) |
| GET / DELETE | /api/attachments | 附件列表(筛选)/ 删除(/api/media 为兼容别名) |
| POST | /api/attachments/bind | 绑定 / 解绑({aid, cid},cid=0 为解绑) |
| GET | /api/attachments/options | 当前允许类型与大小上限 |
| GET | /api/export | 导出 |
| POST | /api/import | 导入 |
调用示例
bash
# 登录并保存会话
curl -c cookie.txt -X POST http://localhost:3000/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"name":"admin","password":"你的密码"}'
# 读取 CSRF 令牌
CSRF=$(curl -b cookie.txt -s http://localhost:3000/api/auth/me | grep -o '"csrf":"[^"]*' | cut -d'"' -f4)
# 新建文章
curl -b cookie.txt -X POST http://localhost:3000/api/posts \
-H 'Content-Type: application/json' \
-H "X-CSRF-Token: $CSRF" \
-d '{"title":"命令行文章","text":"正文内容","status":"publish"}'17. 缓存与响应头
程序在输出层统一约定缓存策略,便于经 CDN / 反向代理加速而不串数据:
- 接口与后台(
/api/*、/admin/*):一律Cache-Control: no-store, no-cache, must-revalidate, max-age=0—— 绝不缓存,避免串号 / 串数据。 - HTML 页面:
Cache-Control: no-cache,并对注入 nonce 之前的 HTML 计算弱ETag;请求带If-None-Match且命中时返回304(省流量)。 - 静态资源:
/admin/*下未带?v=指纹的 CSS/JS 为no-cache(保证升级即时生效);带?v=指纹或图片 / 字体等不可变资源则长缓存。 - 安全响应头:CSP(每请求 nonce、只放行本站与自有 CDN)、
X-Content-Type-Options、Referrer-Policy等。
提示:若前面挂了 CDN,请让 CDN「按源站 Cache-Control」缓存;动态接口 / 后台已显式
no-store,不会被误缓存。
18. 常见问题
Q:前台页面标题显示的是默认值,站点标题没生效。
A:属于历史版本缺陷。若在旧版本安装过,请到「设置 → 站点信息」重新保存一次站点标题即可修正。
Q:文章发布后前台还是 404。
A:检查三点——状态是否为「发布」;发布时间是否为未来时间(定时发布未到时间对访客不可见);URL 中的别名是否与后台一致。
Q:分类页 404。
A:确认已启用该分类、且文章状态为发布。分类路径使用分类别名而非名称。
Q:上传后文件不显示 / 下载 404。
A:确认使用的是上传接口返回的完整 /upload/<文件名> 路径;单文件需在大小上限内(默认 8 MB,可在设置调整); 扩展名需在允许名单内(SVG / HTML / JS 等出于安全考虑始终不支持)。
Q:忘记管理员密码。
A:服务器上删除 data/zmblog.db 后重新启动会进入安装向导重新初始化(会清空全部数据,请先备份)。
Q:修改伪静态规则后部分页面 404。
A:检查规则顺序。数字分页规则须在通用规则之前,日期型规则须在宽松别名规则之前;不确定时可点「重置」恢复默认再逐步调整。
Q:插件启用后前台报错。
A:查看 plugins/<插件名>/index.js 是否有语法错误(node --check 可验证),并在操作日志中确认报错发生时机。
Q:能否让某篇文章使用不同模板。
A:可以。编辑器「模板」字段可指定主题内已声明的模板名,如 post、page。
Q:多站点部署会不会互相干扰。
A:不会。为每个实例设置不同的 ZMBLOG_DATA_DIR 即可完全隔离数据目录。