Files
raisfast/docs/pocketbase-analysis.md
T

204 lines
7.9 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.
# PocketBase 底层技术架构深度分析
> 对标项目:PocketBase v0.37.1Go vs raisfastRust
> 目的:分析 PocketBase 的核心架构设计,识别值得借鉴的特性,制定实施优先级。
## 一、架构概览
| 维度 | PocketBase (Go) | raisfast (Rust) |
|------|----------------|-----------------|
| 语言 | Go 1.25 | Rust 2024 |
| 数据库 | 嵌入式 SQLitemodernc.org/sqlite,纯 Go 无 CGO | 嵌入式 SQLitesqlx + libsqlite3-sys |
| HTTP 框架 | 自研路由(`core.ServeEvent.Router` | Axum 0.8 |
| 二进制体积 | ~12MB(单文件可执行) | ~28MB |
| 扩展方式 | Go 框架 / 内嵌 JS VMgoja | Lua / JS / WASM 插件 |
| 无状态 JWT | 不存储 token,纯 HS256 无状态 | 存储 refresh token 到 DB |
| 实时推送 | SSE 内建 | 无 |
| Admin UI | 内建(`/_/` | 无(仅有博客前台) |
## 二、核心设计理念
### 1. 极简单文件部署
PocketBase 的核心哲学是 **"下载即用"**:单个二进制文件包含所有功能(SQLite、Web UI、API server、Auth、文件上传),零配置启动。Go 的交叉编译特性使其天然适合这个模型。
### 2. 双数据库架构
PocketBase 使用两个 SQLite 文件:
- **`data.db`** — 业务数据(collections/records
- **`auxiliary.db`** — 日志和临时系统数据
这样做的好处是日志写入不会干扰业务查询的 WAL 锁,值得借鉴。
### 3. Collection-Driven Schema
PocketBase 的核心抽象是 **Collection**(等价于 raisfast 的 ContentType)。每个 Collection 有:
- 字段定义(schema
- 5 条 API Rulelist/view/create/update/delete
- 自动的 CRUD REST API
这与 raisfast 的 TOML schema 驱动方式高度一致。
## 三、值得借鉴的 6 个关键设计
### 借鉴 1:表达式级 API Rule 系统(高优先级)
这是 PocketBase 最强大的功能。每个 Collection 的 5 条 RulelistRule/viewRule/createRule/updateRule/deleteRule)都是**运行时表达式**,不是简单的公开/私有标志。
```
// 示例:只返回已发布且作者为自己的文章
status = "published" && author = @request.auth.id
// 示例:只有管理员可以删除
@request.auth.role = "admin"
// 支持跨 collection 查询
@collection.orders.user ?= @request.auth.id
```
**PocketBase 的 filter 引擎**`ganigeorgiev/fexpr`)支持:
- 比较:`= != > >= < <=`
- 模糊匹配:`~ !~`LIKE
- 多值操作:`?= ?!= ?~`ANY/AT LEAST ONE OF
- `@request.*` 上下文:auth user、body、query、headers
- `@collection.*` 跨表引用
- `@now` 时间宏
- `:isset` / `:changed` / `:length` / `:each` 修饰符
- `geoDistance()` 地理距离函数
- `strftime()` 时间格式化
**raisfast 现状**`check_api_access()` 只是简单的枚举(public/authenticated/admin/none),没有表达式级别的过滤。
**建议**:实现一个 `RuleEngine`,将 API Rule 解析为 SQL WHERE 子句。可以先支持基础子集:
- 字段比较:`status = "published"`
- 认证上下文:`@request.auth.id`
- 逻辑组合:`&&` / `||`
### 借鉴 2:SSE 实时订阅(高优先级)
PocketBase 内建了 **Server-Sent Events (SSE)** 实时订阅:
```
GET /api/realtime → 建立 SSE 连接
POST /api/realtime → 设置订阅主题
```
- 订阅粒度:整个 Collection 或单条 Record
- 自动权限检查:订阅 Collection 用 listRule,订阅 Record 用 viewRule
- 5 分钟无消息自动断开(防泄漏连接)
**raisfast 现状**:没有实时推送能力。
**建议**:基于 Axum 的 `tokio-stream` + SSE 实现。架构:
- `EventBus` 已存在,可复用
- 新增 `/api/realtime` SSE endpoint
- 客户端发送 `POST /api/realtime` 设置订阅
- 每次 CUD 操作后,通过 EventBus 广播 → SSE 推送
### 借鉴 3skipTotal 优化(中优先级,低工作量)
PocketBase 的列表 API 支持 `?skipTotal=true` 参数,跳过 `COUNT(*)` 查询,`totalItems``totalPages` 返回 `-1`
**为什么重要**SQLite 的 `COUNT(*)` 在大数据集上会全表扫描。当用户只是翻页浏览时,第二页开始不需要总数。
**raisfast 现状**:每次列表查询都执行 `SELECT COUNT(*)`
**建议**:在 `ContentQuery``skip_total: bool` 字段,`ListParams``skip_total` 参数,条件跳过 count 查询。
### 借鉴 4Batch API(中优先级)
PocketBase 支持 `POST /api/batch`,在一个事务中批量执行多个 create/update/upsert/delete 操作:
```json
{
"requests": [
{ "method": "POST", "url": "/api/collections/posts/records", "body": {"title": "test1"} },
{ "method": "PATCH", "url": "/api/collections/posts/records/xxx", "body": {"title": "test2"} },
{ "method": "DELETE", "url": "/api/collections/posts/records/yyy" }
]
}
```
- 整个 batch 在单个事务中执行,失败全部回滚
- 减少 HTTP 往返次数
**raisfast 现状**:没有批量操作 API。
**建议**:新增 `POST /api/v1/batch` endpoint,接收请求数组,在单个 SQLite 事务中执行。
### 借鉴 5:无状态 JWT(低优先级,架构差异)
PocketBase **不存储任何 JWT token**
- Access tokenHS256 签发后不存 DB
- 没有 refresh token 机制
- "登出" = 客户端删除 token
- `authRefresh` 接口不废止旧 token,只是返回新 token
**raisfast 现状**:有完整的 refresh token 机制(存 DB),更安全但也更慢。
**建议**:这是架构取舍,不需要改。raisfast 的双 token 方案更适合需要强安全控制的场景(企业 CMS),这是差异化优势。
### 借鉴 6:内置 Admin Dashboard UI(高优先级,差异化机会)
PocketBase 的杀手级特性是开箱即用的 Admin UI(`/_/`),功能包括:
- Collection 可视化管理(增删改字段)
- Record 数据浏览和编辑
- API Rule 配置界面
- Auth 用户管理
- 日志查看
- 系统设置
**raisfast 现状**:前端 `web/` 是博客前台,不是管理后台。
**建议**:基于现有的 Next.js + shadcn/ui 搭建 Admin Dashboard,核心页面:
1. Collection Manager(字段拖拽编辑)
2. Record Browser(表格 + 筛选)
3. API Rule Editor(表达式编辑器)
4. 用户管理
## 四、性能优化对照
| 优化项 | PocketBase | raisfast 现状 |
|-------|-----------|--------------|
| SQLite PRAGMA | busy_timeout=10000, WAL, synchronous=NORMAL, cache_size=-32000 | 已优化(Phase 1.1 |
| JWT 缓存 | 每次解析但 HS256 开销极小 | 已缓存 DecodingKeyPhase 1.2 |
| Filter→SQL 编译 | `fexpr` 解析为 AST → 直接映射 SQL | 尚未实现表达式过滤 |
| 列表缓存 | 无内置缓存(纯 SQL | DashMap TTL 缓存(Phase 1.3 |
| 并发数据结构 | Go map + sync.RWMutex | DashMap + ArcSwapPhase 2.1/2.2 |
| Relation 批量化 | WHERE IN 批量查询 | 已实现批量 OneToMany/ManyToManyPhase 2.3 |
## 五、PocketBase 核心依赖栈
| 组件 | 库 | 说明 |
|------|-----|------|
| SQLite 驱动 | modernc.org/sqlite | 纯 Go,无 CGO |
| 数据库抽象 | pocketbase/dbx | 自研 query builder |
| JS 引擎 | dop251/goja | ECMAScript 2020 |
| 表达式解析 | ganigeorgiev/fexpr | 自研 filter 表达式引擎 |
| JWT | golang-jwt/jwt/v5 | HS256 |
| 密码哈希 | golang.org/x/crypto | bcrypt |
| 图片处理 | disintegration/imaging | 缩略图生成 |
| MIME 检测 | gabriel-vasile/mimetype | 文件类型识别 |
| 邮件发送 | domodwyer/mailyak | SMTP |
| CLI | spf13/cobra | 命令行框架 |
| 文件监听 | fsnotify/fsnotify | 热重载 |
## 六、实施优先级建议
| 优先级 | 功能 | 工作量 | 用户价值 |
|-------|------|-------|---------|
| P0 | skipTotal 优化 | 1-2h | 立即提升列表查询性能 |
| P1 | SSE 实时订阅 | 1-2d | 前端实时更新的核心能力 |
| P1 | 表达式级 API Rule | 3-5d | 安全模型质变,对标 Strapi |
| P2 | Batch API | 1-2d | 减少前端 HTTP 往返 |
| P2 | Admin Dashboard UI | 1-2 周 | 可用性的关键差异 |
| P3 | 双数据库分离 | 0.5d | 日志写入不影响业务性能 |