哪里不会点哪里,AI快速创造个人专属文档、交付应用,连服务器部署都省了 _百万前端向前冲
发布网址: nodejs-zh-docs.app.workbuddy.host/
夜间模式
Node.js 中文知识站
一个以 Node.js / JavaScript 为核心的全栈知识站点:Express 5 提供 REST API,SQLite(Node 内置 node:sqlite)承载内容与中文全文检索,原生 ESM 前端以 History 路由渲染。
内容覆盖 5 大板块、38 篇原创中文文档,参考 Node.js 官方 API 文档 的组织脉络重新编排,每篇都包含可运行代码、真实踩坑点与最佳实践,而不是 API 签名的翻译搬运。
一、功能一览
| 能力 | 说明 |
|---|---|
| 知识库浏览 | 38 篇文档,按「板块 → 顺序」组织的目录树,支持折叠与状态记忆 |
| 中文全文检索 | SQLite FTS5 + trigram 分词,支持「背压」「微任务」这类中文子串搜索 |
| 多维筛选 | 按板块 / 难度 / 标签筛选,按课程顺序 / 最近更新 / 篇幅 / 难度排序 |
| 快捷搜索面板 | Ctrl + K 唤起,防抖 + 竞态保护,支持 ↑↓ Enter Esc 全键盘操作 |
| 文章阅读体验 | 阅读进度条、本页目录(ScrollSpy)、代码高亮 + 一键复制、上下篇、相关推荐 |
| 数据看板 | 板块篇幅条形图、难度分布环形图、标签云(全部原生 SVG/CSS,零图表库) |
| 双主题 | 浅色 / 深色一键切换,首次绘制前完成主题判定,无闪白 |
| 响应式 | 桌面三栏 → 平板两栏 → 移动端抽屉式目录 |
| 健康探针 | /api/health(存活)与 /api/ready(就绪,校验数据库)分离 |
二、技术栈
后端
- Node.js ≥ 22.13(依赖内置
node:sqlite,该版本起免实验性 flag) - Express 5 · marked(Markdown 渲染)
- SQLite + FTS5(
trigram分词器)
前端
- 原生 HTML / CSS / ES Modules,零构建步骤
- History API 路由 + 动态
import()代码分割 - highlight.js(本地 vendor,不依赖 CDN,可离线运行)
依赖极简:运行时只有 2 个 npm 依赖(express、marked)。数据库、加密、测试工具全部走 Node 内置模块,没有原生编译环节。
三、快速开始
# 1. 安装依赖
npm install
# 2. 一条命令完成初始化:复制前端资源 → 建表 → 导入 content 目录内容
npm run setup
# 3. 启动
npm start # 生产模式
npm run dev # 开发模式(--watch 自动重载)
打开 http://127.0.0.1:3000 即可。
第 2 步其实可以省略:
server.js检测到数据库尚未就绪时,会自动完成建表与内容导入。npm run setup保留给「想在启动前先把库建好」以及 CI 场景使用。
全部可用命令
| 命令 | 作用 |
|---|---|
npm start | 启动服务 |
npm run dev | 启动并监听文件变化自动重启 |
npm run setup | 完整初始化(vendor + migrate + seed) |
npm run migrate | 只建表 / 执行数据库迁移 |
npm run seed | 只把 content/ 导入数据库(幂等,只有内容变化才写库) |
npm run seed -- --prune | 导入并删除库中已从 content/ 移除的文章 |
npm run vendor | 只复制 highlight.js 等前端第三方资源 |
npm run smoke | 后端端到端冒烟测试(20 项断言,自动起停服务) |
npm run verify:ui | 前端真实渲染验证(无头 Chrome,27 项断言) |
四、目录结构
nodejs-zh-docs/
├── server.js # 服务入口:启动前校验配置与数据库,优雅停机
├── src/
│ ├── config/index.js # 环境变量集中校验,失败快速退出
│ ├── lib/
│ │ ├── errors.js # 类型化错误体系 + 统一错误响应
│ │ ├── logger.js # 结构化 JSON 日志(含敏感字段脱敏)
│ │ ├── markdown.js # Markdown → HTML(含 XSS 防护、锚点、TOC)
│ │ ├── respond.js # 统一响应封装
│ │ └── validate.js # 入参校验与 FTS/LIKE 查询串构造
│ ├── db/
│ │ ├── index.js # 连接、事务、健康检查、优雅关闭
│ │ └── migrations/001_init.sql
│ ├── middleware/ # requestContext / httpLogger / securityHeaders
│ │ # cors / rateLimit / errorHandler
│ ├── features/ # 按功能组织,每个模块三层:routes → service → repository
│ │ ├── articles/
│ │ ├── categories/
│ │ ├── search/
│ │ ├── stats/
│ │ └── health/
│ └── routes/index.js # API 路由聚合
├── scripts/
│ ├── migrate.js # 迁移执行器(schema_migrations 记录版本)
│ ├── seed.js # 内容导入(差异报告 + 事务 + 幂等)
│ ├── copy-vendor.js # 复制前端第三方资源
│ ├── smoke.js # 后端冒烟测试
│ └── verify-ui.js # 前端渲染验证
├── content/ # ★ 内容源,内容即代码
│ ├── categories.json # 分类元数据(中文名、图标、排序、简介)
│ ├── 01-入门基础/*.md
│ ├── 02-核心模块API/*.md
│ ├── 03-异步与并发/*.md
│ ├── 04-工程与工具链/*.md
│ └── 05-进阶主题/*.md
└── public/ # 前端静态资源(无构建)
├── index.html
├── assets/css/style.css
├── assets/js/ # api / router / ui / sidebar / search-panel / app + views/
└── vendor/ # highlight.js(由 npm run vendor 生成)
五、API 接口
统一响应结构,客户端一个分支即可处理全部接口:
// 成功
{ "ok": true, "data": {}, "meta": {} }
// 失败
{ "ok": false, "error": { "code": "NOT_FOUND", "message": "…", "details": [] }, "requestId": "…" }
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api | 接口索引 |
| GET | /api/health | 存活探针 |
| GET | /api/ready | 就绪探针(校验数据库可读) |
| GET | /api/home | 首页聚合(概览 + 分类 + 最近更新 + 入门推荐 + 热门搜索) |
| GET | /api/stats | 完整统计(板块篇幅、难度分布、标签) |
| GET | /api/categories | 全部分类(含文章数、字数、阅读时长) |
| GET | /api/categories/:slug | 单个分类 |
| GET | /api/articles | 文章列表,支持 category tag difficulty sort page pageSize |
| GET | /api/articles/recent | 最近更新 |
| GET | /api/articles/:slug | 文章详情(含渲染 HTML、目录、上下篇、相关推荐) |
| GET | /api/search?q= | 全文搜索(返回高亮片段与命中策略) |
| GET | /api/search/hot | 热门搜索词 |
| GET | /api/tags | 标签及出现次数 |
| GET | /api/difficulties | 难度分布 |
错误码:VALIDATION_ERROR(400)、NOT_FOUND(404)、TOO_MANY_REQUESTS(429)、INTERNAL_ERROR(500)、SERVICE_UNAVAILABLE(503)。
六、如何新增一篇文档
内容与代码分离:只需要往 content/ 目录放一个 Markdown 文件,再跑一次 npm run seed,无需改动任何代码。
# content/02-核心模块API/19-worker_threads.md
---
title: "worker_threads 多线程"
slug: "worker-threads-api"
category: "core-modules"
order: 19
summary: "60 字以内的中文摘要,会展示在卡片与搜索片段里。"
difficulty: "高级"
readTime: 10
tags: ["多线程", "CPU 密集"]
since: "v12.11.0"
---
## 概述
……
字段规则:
| 字段 | 必填 | 说明 |
|---|---|---|
title | ✔ | 中文标题 |
slug | ✔ | 全小写英文/连字符,决定文章 URL(/docs/<slug>) |
category | ✔ | 分类 slug,须与 categories.json 一致 |
order | ✔ | 分类内排序(也可用文件名的数字前缀) |
summary | ✔ | 摘要,用于卡片与搜索片段 |
difficulty | ✔ | 入门 / 进阶 / 高级(数据库有 CHECK 约束) |
readTime | ✔ | 预计阅读分钟数 |
tags | — | 标签数组,用于筛选与相关推荐 |
since | — | 该 API 在 Node.js 中引入的版本号 |
导入脚本会做内容校验(缺字段、正文过短、slug 与文件名不一致、分类不匹配、slug 重复),问题会以清单形式打印出来;--prune 才会真正删除库中多余的文章。
新增分类:在 categories.json 里加一项并创建对应目录即可。
七、设计决策记录
几个「为什么这么选」的说明,避免后来者误改:
-
为什么用
node:sqlite而不是 better-sqlite3 内置模块,零原生编译、零额外依赖,clone 下来npm install即可跑。代价是绑定了 Node 版本,node:sqlite的可用性分三档:Node 版本 node:sqlite可用性≥ 22.13.0(含 23.4+) 直接可用,无需任何参数 22.5.0 ~ 22.12.x、23.0 ~ 23.3 可用,但必须加 --experimental-sqlite< 22.5.0 不存在此模块,只能用 better-sqlite3 启动时会自动检测并把结论写进日志,所以部署到新机器上出现问题时,看一眼启动日志即可判断是不是版本问题。
-
为什么
tags用 JSON 字符串而不是关联表 标签量小、只用于筛选不做聚合统计,关联表属于过度设计。查询侧用json_each()展开即可;若日后要做标签维度的复杂分析,再迁移到关联表。 -
为什么全文索引用
trigram分词器 SQLite 默认的unicode61会把一整段中文当成一个 token,「背压」这类子串搜索会全部失配。trigram按三字符滑窗建索引,中文子串才能命中。代价是查询必须 ≥ 3 个字符——所以搜索服务层做了策略分流:≥ 3 字符走 FTS5,1–2 字符自动降级为LIKE模糊匹配。 -
为什么前端不用框架、不用构建 本站是读多写少的文档站,原生 ESM + History 路由足够,省掉打包与依赖升级的长期成本。视图按路由动态
import(),首屏只加载首页需要的代码。 -
为什么 Markdown 在服务端渲染 渲染结果带 LRU 缓存(键含
updated_at,内容一变自动失效),比每次在浏览器里解析更快;同时 API 会同时返回原始 Markdown 与渲染 HTML,便于后续导出或二次加工。 -
为什么 CSP 里没有
'unsafe-inline'(script 部分) 全站没有一行内联脚本,主题初始化也刻意做成独立外部文件。这是防 XSS 最有效的一条;代价是不能写内联<script>,改代码时需要注意。 -
已知约束
node:sqlite是同步 API。本站是低写入的文档站,同步查询反而更快;若将来引入高频写入场景,需评估是否切换到异步驱动。
八、配置
复制 .env.example 为 .env 后按需修改(.env 已在 .gitignore 中):
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT | 3000 | 监听端口 |
HOST | 自动推断 | 不填时:本机启动 → 127.0.0.1;检测到平台注入的 PORT 或 NODE_ENV=production → 0.0.0.0。需要固定绑定时再显式指定 |
NODE_ENV | development | development / test / production |
DB_FILE | data/nodejs-zh-docs.db | SQLite 文件路径 |
CORS_ORIGINS | 本机两个地址 | 允许来源,逗号分隔;生产禁用 * |
LOG_LEVEL | debug / 生产 info | debug / info / warn / error |
配置在启动时集中校验,任何非法值都会一次性全部报出并拒绝启动。
九、验证
npm run smoke # 后端 20 项断言:接口、分页、中文搜索两条分支、错误路径、安全头
npm run verify:ui # 前端 27 项断言:真实浏览器渲染 + 路由 + 控制台报错
两者都会自己拉起服务并在结束时关闭,可直接接入 CI。verify:ui 需要本机有 Chrome/Edge,可用 CHROME_PATH 指定路径。
十、部署
已部署实例
| 项 | 值 |
|---|---|
| 访问地址 | nodejs-zh-docs.app.workbuddy.host/ |
| 托管形态 | 单端口 HTTP 服务(Node.js 运行时,SQLite 随应用走) |
| 内容规模 | 38 篇文章 / 5 个板块 |
首次启动自愈
部署时不需要单独执行 npm run setup:server.js 会先检查数据库是否就绪,若表不存在则自动依次执行 scripts/migrate.js 与 scripts/seed.js,最后复核一次再开始监听。
这样设计的原因:data/*.db 属于运行期产物(已在 .gitignore 中),容器与云平台打包时通常不会带上它。把初始化绑在「安装阶段」会让部署多出一个隐式前置条件。自愈逻辑复用已经过测试的两个脚本、不重复实现,并会透传 process.execArgv,保证 --experimental-sqlite 这类运行参数不会丢。
云端 / 容器部署
- 服务监听
PORT环境变量并绑定0.0.0.0—— 未显式设置HOST时会自动判断,无需手工配置。 - 只占用一个 HTTP 端口,没有任何外部服务依赖(数据库是随应用走的单文件 SQLite),可直接放进容器或 Serverless 平台。
- 打包时排除
node_modules/与data/,上传后执行npm install即可;首次启动自动完成建表与内容导入。
自有服务器部署
NODE_ENV=production,明确列出CORS_ORIGINS(禁止*)。- 用反向代理(Nginx)终止 TLS,服务监听
127.0.0.1(显式设置HOST=127.0.0.1);trust proxy已按环境自动设置。 - 数据文件在
data/目录,注意纳入备份;数据库使用 WAL 模式,备份时把-wal/-shm一并处理或使用VACUUM INTO。 - 用进程管理器(systemd / PM2)托管,
SIGTERM已实现优雅停机(等待在途请求 → 关闭数据库 → 退出)。 - 内容更新流程:修改
content/→npm run seed→ 重启服务(渲染缓存按updated_at自动失效,重启不是必须的)。
若服务器上没有 Nginx(由 Node.js 直接对外提供 HTTPS),把上面第 2 条替换为:在 server.js 之外用 node:https 承载 TLS,createApp() 产出的 Express 实例可直接作为其 handler 传入;服务本身仍监听回环地址,TLS 与证书轮换交给外层。这样 React/Express 侧代码零改动,也不用为了一个静态知识站额外引入 Nginx。