mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-26 16:02:49 +00:00
291 lines
13 KiB
Markdown
291 lines
13 KiB
Markdown
# 架构审计报告
|
||
|
||
> 审计时间:2026-04-15
|
||
>
|
||
> 基于 Phase 8-14 全部完成后的代码状态。
|
||
|
||
## 1. 总体评估
|
||
|
||
**评级:中等偏上**——骨架扎实,Schema-driven 扩展能力强,但缺少生产级配套(审计、可观测性、缓存)。
|
||
|
||
### 做得好的
|
||
|
||
| 领域 | 评价 | 关键文件 |
|
||
|------|------|----------|
|
||
| 分层架构 | Handler→Service→Repository 三层清晰一致 | `src/handlers/` `src/services/` `src/repositories/` |
|
||
| Content Type 引擎 | TOML 定义即得自动建表 + CRUD API + Admin UI | `src/content_type/` |
|
||
| 多租户隔离 | `tenant_id` 贯穿 repo/service/handler 三层,`ResolvedTenant` 中间件统一解析 | `src/db/tenant.rs` `src/middleware/tenant.rs` |
|
||
| 插件系统 | WASM/JS/Lua 三引擎,HookPoint 泛化,VFS 隔离 | `src/plugins/` |
|
||
| 错误处理 | 统一 `AppError` 枚举 + i18n + JSON envelope | `src/errors/app_error.rs` |
|
||
| 输入校验 | `validator` crate + DTO 层一致调用 `validation::validate()` | `src/handlers/dto.rs` `src/errors/validation.rs` |
|
||
| 事件驱动 | EventBus + Worker + Cron 完整闭环 | `src/eventbus/` `src/worker/` |
|
||
| RBAC | 细粒度权限矩阵,条件权限(`author_id == $user.id`) | `src/services/rbac.rs` `src/middleware/permission.rs` |
|
||
|
||
### 成为复杂系统的关键短板
|
||
|
||
| 短板 | 严重度 | 说明 |
|
||
|------|--------|------|
|
||
| 审计日志 | **高** | EventBus 有事件但无持久化订阅者,admin 操作无 "谁做了什么" 记录 |
|
||
| 可观测性 | **高** | 无 `#[instrument]`、无 metrics、无 request ID 串联 |
|
||
| 缓存层不完整 | **高** | 只有 posts 有缓存,worker 缓存失效 handler 是 stub |
|
||
| 事务覆盖 | 中 | media 删除(DB+文件)非原子,options 批量更新无事务 |
|
||
| API 版本策略 | 中 | 只有 `/api/v1`,无 v2 迁移计划 |
|
||
| Webhook 不完整 | 中 | 无订阅管理、无 HMAC 签名、无事件过滤 |
|
||
| 测试覆盖 | 中 | Options/Plugin/RBAC/Stats/Tenant Admin 无集成测试 |
|
||
| 限流策略 | 低 | 内存级,无法多实例共享,admin 无独立限流 |
|
||
|
||
---
|
||
|
||
## 2. 详细分析
|
||
|
||
### 2.1 错误处理
|
||
|
||
**状态:强——设计完善,有少量缺口**
|
||
|
||
`AppError` 枚举(`src/errors/app_error.rs:49`)有 6 个变体:`BadRequest`、`Unauthorized`、`Forbidden`、`NotFound`、`Conflict`、`Internal`,带 `#[non_exhaustive]` 防止破坏性变更。
|
||
|
||
`From<sqlx::Error>` 自动映射:`RowNotFound` → `NotFound`,唯一约束违反 → `Conflict`。
|
||
|
||
`IntoResponse` impl 将每个变体映射到 HTTP 状态码 + i18n 消息 + 结构化 JSON body。
|
||
|
||
**缺口:**
|
||
|
||
- **缺少 `TooManyRequests`(429)变体。** 限流中间件(`src/middleware/rate_limit.rs:246-259`)手动构造 JSON 响应,绕过了 `AppError` 管线和 i18n。
|
||
- **缺少 `PayloadTooLarge`(413)变体。** `RequestBodyLimitLayer` 产生默认 Axum 响应,不走 `AppError` 格式。
|
||
- **缺少 `MethodNotAllowed`(405)和 `ServiceUnavailable`(503)变体。**
|
||
- **`sqlx::Error::RowNotFound` 映射到 `NotFound("resource")`**(`src/errors/app_error.rs:227`)丢失了具体资源类型上下文。
|
||
|
||
### 2.2 事务支持
|
||
|
||
**状态:关键路径有事务,部分多步操作缺失**
|
||
|
||
已有事务的操作:
|
||
|
||
| 文件 | 行号 | 操作 |
|
||
|------|------|------|
|
||
| `src/repositories/sqlx_post.rs` | 104, 116 | Post 创建/更新 + tag 同步 |
|
||
| `src/services/auth.rs` | 316 | Refresh token 轮换(删除旧 + 插入新) |
|
||
| `src/worker/sqlite_queue.rs` | 113 | Job 出队 |
|
||
| `src/worker/scheduler.rs` | 370, 633 | Cron 清理和种子数据 |
|
||
|
||
**缺失事务但需要的场景:**
|
||
|
||
- **Media 删除**(`src/services/media.rs`)— 先删 DB 记录再删磁盘文件,两步非原子
|
||
- **Options 批量更新**(`src/services/options.rs`)— 逐条执行,无整体事务
|
||
- **Content Type 字段校验 + 写入** — 校验通过后写入,中间无事务保护
|
||
|
||
### 2.3 分页一致性
|
||
|
||
**状态:基本一致,两个端点缺少分页**
|
||
|
||
`PaginationParams`(`src/utils/pagination.rs`)统一用于 6 个列表端点:posts(公开+admin)、comments、media、users。
|
||
|
||
**不一致:**
|
||
|
||
- **`tag::list`** 和 **`category::list`** 返回 `Vec<T>` 无分页,数据量大时成为性能瓶颈
|
||
- Post 列表 handler 手动构造分页参数(`.max(1)` `.clamp()`),其他 handler 直接用 `Query(mut params)`
|
||
- `PaginatedData` 无 `total_pages` 字段或 `Link` header
|
||
|
||
### 2.4 输入校验
|
||
|
||
**状态:强——模式一致,有少量缺口**
|
||
|
||
所有接受 body 的 handler 统一调用 `validation::validate(&req)?`(`src/errors/validation.rs:42`),DTO 用 `validator` crate 注解。
|
||
|
||
**缺口:**
|
||
|
||
- Post/Comment 的 `status` 字段无枚举校验,任意字符串(如 `"hacked"`)都会被接受
|
||
- `category_id`、`tag_ids[]` 等 UUID 字段无格式校验
|
||
- Media 上传无 MIME 类型/扩展名白名单
|
||
- `RefreshRequest.refresh_token` 无 `length(min = 1)` 校验
|
||
|
||
### 2.5 可观测性
|
||
|
||
**状态:日志良好,无 metrics,无分布式追踪**
|
||
|
||
**已有:**
|
||
|
||
- `tower_http::TraceLayer` 记录请求/响应(method, URI, status, latency)
|
||
- 业务逻辑关键点有 `tracing::info!` / `tracing::error!`
|
||
- `AppError::into_response` 用 `tracing::warn!`(4xx)和 `tracing::error!`(5xx)
|
||
|
||
**缺失:**
|
||
|
||
- **无 `#[instrument]`**——全代码库 0 使用,无法 span 级关联 handler→service→repository 调用链
|
||
- **无 metrics**——无 Prometheus / OpenTelemetry,无法追踪请求率、错误率、P95/P99 延迟
|
||
- **无 request ID**——`TraceLayer` 不注入 `X-Request-ID`,无法跨服务关联
|
||
- **无 pool utilization 监控**——无法知道连接池使用率
|
||
- **日志字段不一致**——部分用格式化字符串,部分用结构化字段
|
||
|
||
### 2.6 限流
|
||
|
||
**状态:可用但有限**
|
||
|
||
`RateLimiterSet`(`src/middleware/rate_limit.rs:153`)包含 4 个命名限流器:`global`、`register`、`login`、`comment`,滑动窗口算法。
|
||
|
||
**限制:**
|
||
|
||
- **纯内存**——多实例部署时限流独立,等效于倍增限额
|
||
- **4 个限流器硬编码**——新增需改 struct + `from_config()` + 中间件函数
|
||
- **Admin 端点无独立限流**——全局 60 req/min 覆盖,持有 admin token 的攻击者可暴力请求
|
||
- **IP 提取信任 `X-Forwarded-For`**——无配置化信任代理
|
||
- **响应无 `Retry-After` / `X-RateLimit-*` header**
|
||
- **RateLimitStore trait 已定义但只有 MemoryStore 实现**
|
||
|
||
### 2.7 缓存
|
||
|
||
**状态:仅 posts 有缓存,失效 handler 是 stub**
|
||
|
||
`CacheStore` trait + `MemoryCache` 实现(`src/cache/mod.rs`)。`CachedPostRepository<P>` 装饰器(`src/repositories/cached_post.rs`)为 posts 提供读穿透缓存 + 写穿透失效,租户感知 cache key。
|
||
|
||
**缺口:**
|
||
|
||
- **`InvalidateCacheHandler` 是 stub**——`src/worker/handlers/cache.rs:35` 只有 `// TODO` 注释和日志,不实际删除缓存
|
||
- **只有 posts 有缓存**——categories、tags、users、comments、media、options 无缓存
|
||
- **Options 缓存无 TTL 刷新**——启动时加载到 HashMap,更新时写穿透,但无定期刷新机制
|
||
- **MemoryCache 无后台清理线程**——过期条目仅在 get/set 时惰性跳过
|
||
- **无 Redis 后端**——CacheStore trait 可扩展但只有内存实现
|
||
|
||
### 2.8 数据库连接管理
|
||
|
||
**状态:可用但配置最小**
|
||
|
||
`init_pool`(`src/db/connection.rs:14`)配置 `max_connections`,SQLite 设置 `journal_mode = WAL` + `foreign_keys = ON`。
|
||
|
||
**缺失:**
|
||
|
||
- 无 `min_connections` 配置
|
||
- 无 `acquire_timeout`——请求可能无限等待连接
|
||
- 无 `idle_timeout` / `max_lifetime`
|
||
- 无连接池利用率监控
|
||
- SQLite WAL 模式仅在启动时设置一次,非 per-connection
|
||
- 无数据库重连逻辑
|
||
|
||
### 2.9 测试覆盖
|
||
|
||
**状态:覆盖面广,深度中等**
|
||
|
||
| Handler | 有集成测试? | 测试数 |
|
||
|---------|-------------|--------|
|
||
| health | 有 | 1 |
|
||
| auth | 有 | 9 |
|
||
| user | 有 | 7 |
|
||
| category | 有 | 5 |
|
||
| tag | 有 | 4 |
|
||
| post | 有 | 12 |
|
||
| comment | 有 | 8 |
|
||
| media | 有 | 4 |
|
||
| rss | 有 | 2 |
|
||
| cron | 有 | 6 |
|
||
| sse | 有 | 2 |
|
||
| tenant_e2e | 有 | 4 |
|
||
| **options** | **无** | 0 |
|
||
| **plugin** | **无** | 0 |
|
||
| **rbac** | **无** | 0 |
|
||
| **stats** | **无** | 0 |
|
||
| **tenant admin** | **部分**(仅 e2e 间接测试) | — |
|
||
|
||
单元测试总计 422 个,覆盖 content_type、plugins、search、worker 等核心模块。
|
||
|
||
**缺口:**
|
||
|
||
- Options、Plugin、RBAC、Stats、Tenant Admin CRUD 无集成测试
|
||
- 无负面/边界测试(限流溢出、并发请求等)
|
||
- 无负载/压力测试
|
||
- `src/cache/mod.rs`、`src/errors/validation.rs` 无单元测试
|
||
|
||
### 2.10 API 版本策略
|
||
|
||
**状态:单版本,无迁移策略**
|
||
|
||
所有路由嵌套在 `/api/v1`(`src/server/mod.rs:335`),无版本中间件、无 Accept header 协商、无版本感知序列化。
|
||
|
||
添加 `/api/v2` 需要复制或重构整个路由注册块(204-315 行)。无版本迁移策略文档。
|
||
|
||
### 2.11 Webhook 系统
|
||
|
||
**状态:基础出站 webhook**
|
||
|
||
`WebhookNotifyHandler`(`src/worker/handlers/webhook.rs`)通过 job queue 发起 HTTP POST,10 秒超时,非 2xx 触发重试。
|
||
|
||
**缺口:**
|
||
|
||
- 无 webhook 订阅管理 API(无法动态注册/注销 URL)
|
||
- 无 HMAC 签名(接收方无法验证真实性)
|
||
- 无事件类型过滤(无法订阅特定事件)
|
||
- 无死信队列 UI(永久失败的 webhook 无检查/重放界面)
|
||
- 无出站限流
|
||
|
||
### 2.12 审计日志
|
||
|
||
**状态:缺失——无持久化审计轨迹**
|
||
|
||
- **无 `audit_log` 表**——所有 migration 中均不存在
|
||
- **无 audit 模块**——`src/` 中无任何 audit 相关文件
|
||
- **EventBus 无持久化订阅者**——唯一订阅者转发给插件系统,不做审计持久化
|
||
- **Admin 操作无日志记录**——用户角色变更、插件启停、配置修改、租户 CRUD、RBAC 变更、Cron 调度变更均无审计记录
|
||
- **无 "谁做了什么、什么时候" 追踪**
|
||
|
||
---
|
||
|
||
## 3. 快速扩展能力评估
|
||
|
||
### 能快速扩展的
|
||
|
||
| 扩展方向 | 说明 |
|
||
|----------|------|
|
||
| 新 Content Type | 创建 TOML 文件即得自动建表 + CRUD API + Admin 页面 |
|
||
| 新插件 | 写 plugin.toml + 入口文件,自动加载 |
|
||
| 新租户 | INSERT 一行数据即生效 |
|
||
| 前端新页面 | Admin 页面复用统一 layout + API client |
|
||
| 新字段类型 | FieldType 枚举加一个变体 + 前端一个组件 |
|
||
|
||
### 扩展时会卡住的
|
||
|
||
| 场景 | 卡点 |
|
||
|------|------|
|
||
| 高并发 | 缓存不完整 + 无 Redis 后端 |
|
||
| 多实例部署 | 限流/缓存全内存不共享 |
|
||
| 安全合规 | 无审计日志 |
|
||
| 运维排查 | 无分布式追踪、无 metrics |
|
||
| API 演进 | 无版本策略 |
|
||
| 外部集成 | Webhook 无订阅管理 |
|
||
|
||
---
|
||
|
||
## 4. 改进优先级建议
|
||
|
||
| 优先级 | 改进项 | 工作量估计 | 收益 |
|
||
|--------|--------|-----------|------|
|
||
| **P0** | 审计日志(EventBus 持久化订阅者 + audit_log 表) | 2-3 天 | 安全合规必需 |
|
||
| **P0** | 可观测性(`#[instrument]` + Prometheus metrics + request ID) | 3-5 天 | 运维必需 |
|
||
| **P0** | 缓存补全(Category/Tag/Options 缓存 + InvalidateCacheHandler 实现) | 2-3 天 | 性能扩展基础 |
|
||
| **P1** | 测试补全(Options/RBAC/Stats/Plugin/Tenant 集成测试) | 3-5 天 | 质量保障 |
|
||
| **P1** | Webhook 订阅管理 API + HMAC 签名 | 2-3 天 | 外部集成能力 |
|
||
| **P2** | AppError 补全(429/413/405 变体) | 0.5 天 | 一致性 |
|
||
| **P2** | 事务补全(media 删除、options 批量、content type 校验+写入) | 1-2 天 | 数据一致性 |
|
||
| **P2** | Redis 缓存/限流后端 | 3-5 天 | 多实例部署 |
|
||
| **P2** | API 版本策略文档 + v2 路由架构 | 1-2 天 | 长期演进 |
|
||
| **P3** | 分页补全(tags/categories 加分页) | 0.5 天 | 性能 |
|
||
| **P3** | 输入校验补全(status 枚举、UUID 格式、MIME 白名单) | 1 天 | 安全 |
|
||
| **P3** | DB 连接池配置完善(acquire_timeout、min_connections) | 0.5 天 | 稳定性 |
|
||
|
||
---
|
||
|
||
## 5. 与同类系统对比
|
||
|
||
| 能力 | 本系统 | Strapi | WordPress |
|
||
|------|--------|--------|-----------|
|
||
| Schema-driven Content Type | TOML → 自动建表/API | JSON Schema → 自动建表/API | PHP 注册 → 自动建表 |
|
||
| 插件系统 | WASM/JS/Lua 三引擎沙箱 | JS 插件(无沙箱) | PHP 插件(无沙箱) |
|
||
| 多租户 | tenant_id 隔离 | 单租户(Enterprise 有) | Multisite |
|
||
| RBAC | 细粒度 + 条件权限 | 细粒度 | 基于角色 |
|
||
| 全文搜索 | Tantivy(内置) | 外部 | 外部 |
|
||
| 审计日志 | 缺失 | 有 | 有插件 |
|
||
| 可观测性 | 缺失 | 有 | 有插件 |
|
||
| API 版本 | 无 | 无 | 无 |
|
||
| Webhook | 基础出站 | 有 | 有插件 |
|
||
| 缓存 | 仅 posts | 内存 + Redis 可选 | 对象缓存 + Redis |
|
||
| Admin UI | Next.js(前后端分离) | React SPA | PHP SSR |
|
||
| 性能(RPS) | Rust 级(待压测) | Node.js 级 | PHP 级 |
|