Files
note-manager/API.md
T
Your Name 13c53fea0a feat: 管理员跨租户管理 + 注册开关
- 管理员可管理任意用户笔记(读取/修改/删除/回收站/标签/图谱/FTS 全平台)
- 普通用户仍数据隔离, 越权返回404
- 新增注册开关: 管理员后台⚙设置可开/关, 支持REGISTRATION_ENABLED环境变量
- 注册关闭时前台/登录页隐藏注册入口, 注册接口返回400
- 冒烟测试扩展到91用例全过
2026-08-11 13:07:56 +08:00

756 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 云笔记 API 文档
## 基础信息
- **Base URL**`http://localhost:8080/api`
- **认证方式**:Cookie(后台管理接口需要)
- **默认密码**`admin123`
---
## 公开接口
公开接口无需认证即可访问(只读)。
### 1. 获取笔记列表
获取分页的笔记列表。
**请求**
```
GET /api/notes
```
**Query 参数**
| 参数 | 类型 | 必填 | 说明 | 默认值 |
|------|------|------|------|--------|
| page | int | 否 | 页码 | 1 |
| page_size | int | 否 | 每页数量 | 20 |
| category | string | 否 | 按分类筛选 | - |
| tag | string | 否 | 按标签筛选 | - |
| pinned | bool | 否 | 只看置顶 | - |
| favorite | bool | 否 | 只看收藏 | - |
| parent_id | int | 否 | 按父目录筛选 | - |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"id": 1,
"title": "Go 语言教程",
"category": "技术",
"tags": "[\"Go\",\"编程\"]",
"is_pinned": true,
"is_favorite": false,
"is_folder": false,
"parent_id": 0,
"sort_order": 0,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"total": 50,
"page": 1,
"page_size": 20,
"total_pages": 3
}
}
```
---
### 2. 获取单条笔记
根据 ID 获取笔记详情。
**请求**
```
GET /api/notes/:id
```
**路径参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| id | int | 笔记 ID |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"id": 1,
"title": "Go 语言教程",
"content": "# Go 语言\\n\\nGo 是一门简洁高效的编程语言。",
"category": "技术",
"tags": "[\"Go\",\"编程\"]",
"is_pinned": true,
"is_favorite": false,
"is_folder": false,
"parent_id": 0,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
```
---
### 3. 搜索笔记
搜索标题和内容。
**请求**
```
GET /api/notes/search
```
**Query 参数**
| 参数 | 类型 | 必填 | 说明 | 默认值 |
|------|------|------|------|--------|
| q | string | 是 | 搜索关键词 | - |
| page | int | 否 | 页码 | 1 |
| page_size | int | 否 | 每页数量 | 20 |
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": {
"items": [...],
"total": 5,
"page": 1,
"page_size": 20,
"total_pages": 1
}
}
```
---
### 4. 获取分类列表
获取所有分类。
**请求**
```
GET /api/categories
```
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": ["技术", "生活", "工作", "随笔"]
}
```
---
### 5. 获取标签列表
获取所有标签。
**请求**
```
GET /api/tags
```
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": ["Go", "Python", "JavaScript", "编程", "笔记"]
}
```
---
### 6. 获取树形结构
获取目录树形结构(包含目录和笔记)。
**请求**
```
GET /api/tree
```
**响应示例**
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"title": "技术文档",
"is_folder": true,
"children": [
{
"id": 2,
"title": "Go 语言教程",
"is_folder": false
}
]
},
{
"id": 3,
"title": "生活随笔",
"is_folder": false
}
]
}
```
---
## 管理接口
管理接口需要先登录,获取 Cookie 认证。
### 7. 登录
**请求**
```
POST /admin/login
```
**Body 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 是 | 管理员密码 |
**响应示例**
```json
{
"code": 0,
"message": "登录成功"
}
```
---
### 8. 登出
**请求**
```
POST /admin/logout
```
**响应示例**
```json
{
"code": 0,
"message": "已退出登录"
}
```
---
### 9. 检查认证状态
**请求**
```
GET /admin/auth
```
**响应示例**
```json
{
"authenticated": true
}
```
---
### 10. 创建笔记/目录
**请求**
```
POST /api/notes
```
**Body 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| title | string | 是 | 标题 |
| content | string | 否 | 内容(Markdown |
| category | string | 否 | 分类 |
| tags | string | string | 标签(JSON 数组格式) |
| is_folder | bool | 否 | 是否为文件夹 |
| is_pinned | bool | 否 | 是否置顶 |
| is_favorite | bool | 否 | 是否收藏 |
| parent_id | int | 否 | 父目录 ID |
| sort_order | int | 否 | 排序顺序 |
**请求示例**
```json
{
"title": "新建笔记",
"content": "# 我的笔记\\n\\n这是笔记内容",
"category": "技术",
"tags": "[\"笔记\",\"教程\"]",
"is_folder": false
}
```
**响应示例**
```json
{
"code": 0,
"message": "笔记创建成功",
"data": {
"id": 10
}
}
```
---
### 11. 更新笔记/目录
**请求**
```
PUT /api/notes/:id
```
**Body 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| title | string | 否 | 标题 |
| content | string | 否 | 内容(Markdown |
| category | string | 否 | 分类 |
| tags | string | 否 | 标签 |
| is_pinned | bool | 否 | 是否置顶 |
| is_favorite | bool | 否 | 是否收藏 |
| parent_id | int | 否 | 父目录 ID |
| sort_order | int | 否 | 排序顺序 |
**响应示例**
```json
{
"code": 0,
"message": "笔记更新成功"
}
```
---
### 12. 删除笔记/目录
**请求**
```
DELETE /api/notes/:id
```
**说明**:删除目录时会同时删除该目录下的所有子项。
**响应示例**
```json
{
"code": 0,
"message": "笔记删除成功"
}
```
---
## 响应状态码
| code | 说明 |
|------|------|
| 0 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未授权(需要登录) |
| 404 | 笔记不存在 |
| 500 | 服务器内部错误 |
---
## 密码保护笔记接口
### 访问密码保护的笔记
**请求**
```
POST /api/notes/:id/access
```
**Body 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| password | string | 是 | 笔记访问密码 |
**响应示例**
```json
{
"code": 0,
"message": "访问成功",
"data": {
"id": 1,
"title": "受保护的笔记",
"content": "# 笔记内容..."
}
}
```
---
## 图片上传接口
### 上传图片
**请求**
```
POST /admin/api/upload
```
**说明**:需要登录认证。仅支持 jpg、png、gif、webp、bmp 格式,最大 5MB。
**Form 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| image | file | 是 | 图片文件 |
**响应示例**
```json
{
"code": 0,
"message": "上传成功",
"data": {
"url": "/uploads/1234567890_abc123.png"
}
}
```
**图片访问**:上传后的图片通过 `/uploads/文件名` 访问。
---
## 导入导出接口
### 导出笔记为 Markdown
**请求**
```
GET /admin/api/export/:id
```
**说明**:需要登录认证。导出的文件包含 YAML front matter。
**响应**:下载 `.md` 文件,文件名以笔记标题命名。
---
### 导入 Markdown 文件
**请求**
```
POST /admin/api/import
```
**说明**:需要登录认证。仅支持 `.md` 文件。
**Form 参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | file | 是 | Markdown 文件 |
**响应示例**
```json
{
"code": 0,
"message": "导入成功",
"data": {
"id": 15,
"title": "导入的笔记标题"
}
}
```
---
## 错误响应示例
```json
{
"code": 401,
"message": "请先登录后台管理"
}
```
---
## 使用示例
### cURL
```bash
# 登录
curl -X POST http://localhost:8080/admin/login \
-d "password=admin123" \
-c cookies.txt
# 获取笔记列表
curl http://localhost:8080/api/notes
# 搜索笔记
curl "http://localhost:8080/api/notes/search?q=Go"
# 创建笔记(需要认证)
curl -X POST http://localhost:8080/api/notes \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"title":"新笔记","content":"# 标题\\n\\n内容"}'
# 更新笔记(需要认证)
curl -X PUT http://localhost:8080/api/notes/1 \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"title":"更新后的标题"}'
# 删除笔记(需要认证)
curl -X DELETE http://localhost:8080/api/notes/1 -b cookies.txt
```
### JavaScript (Fetch API)
```javascript
// 登录
await fetch('/admin/login', {
method: 'POST',
body: new URLSearchParams({ password: 'admin123' }),
credentials: 'include'
});
// 获取笔记列表
const res = await fetch('/api/notes');
const data = await res.json();
// 创建笔记
await fetch('/api/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: '新笔记', content: '# 内容' }),
credentials: 'include'
});
```
---
# 新增功能 APIv2
## 管理接口认证(安全优化)
后台管理写操作(创建/更新/删除/回收站/版本/分享/导入导出/上传)统一走 `/admin/api`
需登录后携带服务端会话 cookie(随机 token,非固定字符串)。未登录返回 401。
## 安全策略调整
### 公开接口 `GET /api/notes/:id`
仅返回 **公开且未设置密码** 的笔记完整内容。带密码或未公开的笔记返回 403(需走密码验证接口),
防止绕过密码保护直接读取内容。
## 回收站(软删除)
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/trash` | 回收站列表(含已删除的目录和笔记) |
| POST | `/admin/api/restore/:id` | 恢复笔记/目录(目录连子树一起恢复) |
| POST | `/admin/api/purge/:id` | 彻底删除(不可恢复,含版本历史) |
| POST | `/admin/api/empty-trash` | 清空回收站 |
删除笔记 `DELETE /admin/api/notes/:id` 现为软删除(进入回收站),目录删除会连带软删除所有子项。
## 版本历史
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/notes/:id/versions` | 获取笔记全部历史版本 |
| POST | `/admin/api/notes/:id/restore-version` | 恢复指定版本(form: `version_id` |
每次保存(标题或内容变化)自动生成快照。
## 分享链接
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/admin/api/notes/:id/share` | 创建分享(form: `expire_hours`0=永久) |
| POST | `/admin/api/notes/:id/revoke-share` | 撤销分享 |
| GET | `/api/share/:token` | 公开获取分享笔记 JSON(有密码需带 `?password=` |
| GET | `/share/:token` | 分享阅读页(HTML) |
## 批量导出
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/export-all` | 全部笔记导出为 zip(按目录结构 + YAML front matter |
## 图片上传(安全增强)
上传会做**内容嗅探**(magic bytes 校验),不仅检查扩展名。伪装成图片的脚本会被拒绝。
## 后台管理笔记接口
管理端读取/写笔记统一走 `/admin/api/notes`(可访问私有、带密码笔记):
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/admin/api/notes` | 创建 |
| GET | `/admin/api/notes/:id` | 详情(完整内容) |
| PUT | `/admin/api/notes/:id` | 更新 |
| DELETE | `/admin/api/notes/:id` | 软删除 |
## 前端新增功能
- **深色模式**:前台/后台均可切换(🌙/☀️ 按钮),偏好存 localStorage
- **Mermaid 图表**:前台 Markdown 中 ` ```mermaid ` 代码块渲染为流程图/时序图等
- **待办清单**:前台渲染 `- [ ]` / `- [x]` 可勾选清单
- **字数统计**:前台笔记详情显示字数与预估阅读时长
- **回收站/版本历史/分享**:后台工具栏按钮 + 面板
## v3 新增功能(2026-08-11
### FTS5 全文搜索
`GET /api/notes/search?q=关键词&page=1` 现使用 **SQLite FTS5 全文索引**,支持中文短词(1~2 字)与英文。
- 采用 `unicode61` 分词器 + Go 层对中文做逐字分词(`segmentCJK`),使每个汉字独立成词元,支持短词搜索
- 索引在创建/更新/标签变更时增量维护,启动时全量重建
- 搜索结果按 `bm25` 相关度排序,排除已删除笔记与目录
- FTS 查询失败时自动回退到传统 LIKE 搜索
- 构建需启用 `-tags=sqlite_fts5`(否则报 `no such module: fts5`
### 双向链接 / 知识图谱
支持 `[[笔记标题]]` wiki 链接语法:
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/notes/:id/backlinks` | 反向链接(谁链接到了该笔记) |
| GET | `/admin/api/graph` | 知识图谱数据 `{nodes:[{id,title}], edges:[{source,target}]}` |
- 后台编辑器支持点击 `[[链接]]` 跳转并高亮
- 后台工具栏「🕸 知识图谱」以 SVG 图形化展示节点与链接关系
### 自动保存草稿
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/admin/api/notes/:id/draft` | 保存草稿(body `{content}`),不触发版本历史 |
| DELETE | `/admin/api/notes/:id/draft` | 清除草稿(保存正文成功后调用) |
- 后台编辑器每 8 秒自动保存未落盘内容
- 打开笔记时若存在未保存草稿会提示恢复
### 标签管理
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/tags/usage` | 标签使用统计 `[{name,count}]` |
| POST | `/admin/api/tags/rename` | 重命名 `{old_name,new_name}` |
| POST | `/admin/api/tags/merge` | 合并 `{from,to}`from 并入 to |
| DELETE | `/admin/api/tags` | 删除 `{name}` |
- 后台工具栏「🏷 标签管理」图形化操作
- 重命名/合并/删除会同步更新所有含该标签的笔记及其全文索引
### 富文本所见即所得(编辑器增强)
- 后台编辑器新增**实时预览**:编辑右侧即时渲染 Markdown
- 新增**格式工具栏**:加粗/斜体/标题/列表/链接/代码/图片/表格/任务清单
- 仍以 Markdown 为存储源(保证 FTS/反向链接/导出兼容),预览所见即所得
### 移动端 / PWA
- 新增 PWA 支持:`/manifest.json``/sw.js`、图标 `icon-192.png`/`icon-512.png`
- 前台页面响应式适配手机(侧栏折叠、字号/按钮优化)
- 可添加到主屏幕离线使用
> 注:FTS5 需用 `-tags=sqlite_fts5` 编译;`note_search` 虚拟表由 Go 代码维护(手动管理而非 SQL 触发器),保证中文分词正确。
---
## v4 多租户账号体系
### 认证(用户名 + 密码,bcrypt)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/auth/register` | 注册 `{username,password,display_name}`;**首个用户自动成为 admin 并接管历史遗留(user_id=0)笔记** |
| POST | `/api/auth/login` | 登录 `{username,password}`,写入 `note_token` cookie |
| POST | `/api/auth/logout` | 登出 |
| GET | `/api/auth/me` | 当前登录用户信息(游客返回 null) |
### 多租户数据隔离
- 每篇笔记归属 `user_id`;所有查询(笔记/分类/标签/回收站/版本/草稿/图谱/FTS 搜索)均按当前用户隔离。
- **前台 `/api`**:登录用户看自己的笔记(前台可★收藏自己的笔记);游客只读所有用户的公开无密码笔记。
- **后台 `/admin/api`**:需登录,每个用户只能管理自己的笔记(越权访问他人笔记返回 404)。
- 旧账号固定密码登录已移除;会话与用户绑定,重启后需重新登录。
### 安全
- `note_token` 会话 cookieHttpOnly + SameSite=Lax(防 CSRF),随机 32 字节 token,7 天过期,服务端内存校验绑定用户。
- 密码 bcryptcost 10),兼容旧 SHA-256 自动升级。
### v5 管理员跨租户管理 + 注册开关
- **管理员跨租户管理**`admin` 角色的用户可读取/修改/删除/恢复/分享任意用户的笔记,树/列表/回收站/分类/标签/图谱/FTS 均显示全平台数据;普通用户仍只能操作自己的数据(越权 404)。
- **注册开关**:管理员可在后台「⚙ 设置」中开启/关闭注册。关闭后 `/api/auth/register` 返回 400「注册功能已关闭」,前台与登录页隐藏「注册」入口,仅已有账号可登录。
- 默认通过环境变量 `REGISTRATION_ENABLED`(默认 `true`)设置,运行时可在后台切换并持久化。
- 接口:
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/admin/api/settings/registration` | 获取注册开关状态(管理员) |
| PUT | `/admin/api/settings/registration` | 设置注册开关 `{enabled}`(管理员) |
| GET | `/api/auth/me` | 返回当前用户角色及 `registration_enabled` |