mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-24 00:02:28 +00:00
1047 lines
37 KiB
Markdown
1047 lines
37 KiB
Markdown
# AOP + Protocol 设计文档 — v2.0 实现版
|
||
|
||
> 版本:v2.0 | 最后更新:2026-05-07
|
||
> 状态:Data Layer 已实现,11 个内置协议全部完成
|
||
|
||
## 1. 为什么需要 AOP
|
||
|
||
当前系统的横切面逻辑散落在四层中,没有统一抽象:
|
||
|
||
```
|
||
一个 POST /cms/articles 实际经过的逻辑:
|
||
|
||
Middleware 层
|
||
├─ CORS 检查
|
||
├─ Rate Limit
|
||
├─ JWT 解析
|
||
|
||
Handler 层
|
||
├─ access control 检查 ← handler 内 if 判断
|
||
├─ 参数校验 ← validation.rs
|
||
├─ 缓存检查(read 时) ← handler 内 if cache
|
||
├─ 缓存清除(write 后) ← handler 内 if cache
|
||
├─ 字段过滤(response) ← filter_fields()
|
||
│
|
||
├─ Aspect before_create ← Aspect 引擎 dispatch
|
||
├─ Aspect after_create ← Aspect 引擎 dispatch
|
||
│
|
||
Repository 层
|
||
├─ 协议声明驱动 SQL ← ProtocolDeclaration
|
||
├─ INSERT/UPDATE/DELETE SQL
|
||
│
|
||
EventBus 层
|
||
├─ 审计日志 ← subscriber
|
||
├─ Webhook 通知 ← subscriber
|
||
└─ 搜索索引 ← 未实现
|
||
```
|
||
|
||
**目标:** 每个 Aspect(横切面)定义一次,所有数据路径自动生效。
|
||
|
||
## 2. 概念模型
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ Aspect │
|
||
│ "我叫什么" (name) │
|
||
│ "我关心什么事件" (pointcut) │
|
||
│ "我排在第几位" (priority) │
|
||
│ "我需要什么系统列" (columns) │
|
||
│ "我做什么" (advise) │
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ Protocol Aspect 的命名别名,1:N 组合多个 Aspect │
|
||
│ ProtocolDeclaration 协议的声明式效果(纯数据) │
|
||
│ Layer Aspect 关心的系统层级 │
|
||
│ Pointcut Aspect 关心的拦截点(精确匹配或模式匹配) │
|
||
│ Advice Aspect 在拦截点上的具体行为 │
|
||
│ Context 拦截点的上下文数据(可读/可写) │
|
||
│ Engine 调度器,管理 Aspect 注册和执行 │
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 两个正交维度
|
||
|
||
每个协议将行为拆分为:
|
||
|
||
| 维度 | 实现方式 | 示例 |
|
||
|------|----------|------|
|
||
| **命令式**(注入值) | Aspect + Pointcut | `on_data_before_create` 注入 `created_at` |
|
||
| **声明式**(影响 SQL 行为) | `ProtocolDeclaration` 纯数据 | `query_filters` → WHERE、`default_sort` → ORDER BY |
|
||
|
||
## 3. 三层架构
|
||
|
||
```
|
||
┌─ aspects.rs + aspects/ ──────────────────────────────────┐
|
||
│ 纯框架层,不知道 Protocol 存在 │
|
||
│ Aspect trait + AspectEngine + Context 类型 + Advice │
|
||
└─────────────────────────────────────────────────────────┘
|
||
▲
|
||
│ register_from_arc()
|
||
┌─ protocols.rs + protocols/ ─────────────────────────────┐
|
||
│ 业务 Protocol 实现(1:N 组合 Aspect) │
|
||
│ Protocol trait + ProtocolRegistry + ProtocolDeclaration │
|
||
│ ownable.rs / timestampable.rs / ... (11 个) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
▲
|
||
│ register_from_inventory()
|
||
┌─ lib.rs ────────────────────────────────────────────────┐
|
||
│ protocol_registry.register_from_inventory() 一行注册 │
|
||
│ protocol_registry.register_aspects_into(&engine) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 4. Layer(系统层级)
|
||
|
||
```
|
||
┌─ HTTP Layer ──────────────────────────────────────────┐
|
||
│ HTTP 请求/响应拦截 │
|
||
│ 适用:CORS、Rate Limit、请求日志、请求追踪 │
|
||
│ 状态:Phase 5,未实现 │
|
||
└───────────────────────────────────────────────────────┘
|
||
|
||
┌─ Access Layer ────────────────────────────────────────┐
|
||
│ 路由级 + 数据级权限检查 │
|
||
│ 适用:角色校验、RBAC、API Rule 数据过滤、字段级 ACL │
|
||
│ 状态:Phase 5,未实现 │
|
||
└───────────────────────────────────────────────────────┘
|
||
|
||
┌─ Data Layer ──────────────────────────────────────────┐
|
||
│ 数据 CRUD 操作前后拦截 │
|
||
│ 适用:字段注入、校验、版本快照、搜索索引 │
|
||
│ 状态:已实现 ✅ │
|
||
└───────────────────────────────────────────────────────┘
|
||
|
||
┌─ Event Layer ─────────────────────────────────────────┐
|
||
│ 事件发布/消费拦截(异步,事务外) │
|
||
│ 适用:审计日志、Webhook、通知、搜索索引更新 │
|
||
│ 状态:未实现 │
|
||
└───────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 4.1 同步 vs 异步边界
|
||
|
||
| 层 | 执行方式 | 事务 | 失败影响 |
|
||
|---|---|---|---|
|
||
| HTTP | 同步 | 无 | 阻断请求 |
|
||
| Access | 同步 | 无 | 阻断请求 |
|
||
| Data Before | 同步 | **在事务内** | 回滚整个操作 |
|
||
| Data After | 同步 | **在事务内** | 回滚整个操作 |
|
||
| Event | 异步 | 事务外 | 仅记日志,不影响主操作 |
|
||
|
||
## 5. 核心数据模型
|
||
|
||
### 5.1 Layer / Operation / When
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
pub enum Layer { Http, Access, Data, Event }
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
pub enum Operation {
|
||
Create, Read, Update, Delete,
|
||
Publish, Consume, Check, Filter, Request, Response,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||
pub enum When { Before, After }
|
||
```
|
||
|
||
### 5.2 JoinPointId
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||
pub struct JoinPointId {
|
||
pub layer: Layer,
|
||
pub operation: Operation,
|
||
pub when: When,
|
||
}
|
||
```
|
||
|
||
### 5.3 TargetMatcher
|
||
|
||
```rust
|
||
#[derive(Debug, Clone)]
|
||
pub enum TargetMatcher {
|
||
/// 匹配所有目标
|
||
All,
|
||
/// 精确匹配表名列表
|
||
Tables(Vec<String>),
|
||
}
|
||
```
|
||
|
||
### 5.4 Pointcut
|
||
|
||
```rust
|
||
#[derive(Debug, Clone)]
|
||
pub struct Pointcut {
|
||
pub layer: Layer,
|
||
pub operation: Operation,
|
||
pub when: When,
|
||
pub target: TargetMatcher,
|
||
}
|
||
```
|
||
|
||
### 5.5 Advice(三态语义)
|
||
|
||
```rust
|
||
#[derive(Debug)]
|
||
pub enum Advice {
|
||
/// 继续执行下一个 Aspect
|
||
Continue,
|
||
/// 跳过剩余 Aspect(break),原始操作继续执行
|
||
Skip,
|
||
/// 短路返回(仅 before hook 生效)
|
||
Return(serde_json::Value),
|
||
}
|
||
|
||
pub type AspectResult = Result<Advice, anyhow::Error>;
|
||
```
|
||
|
||
**dispatch 行为:**
|
||
|
||
| 场景 | before dispatch | after dispatch |
|
||
|---|---|---|
|
||
| `Ok(Continue)` | 继续下一个 Aspect | 继续下一个 Aspect |
|
||
| `Ok(Skip)` | **break**,跳过剩余 Aspect | 不适用 |
|
||
| `Ok(Return(val))` | 短路返回 `Some(val)` | 不适用 |
|
||
| `Ok(_)` (after hook) | — | 全部 continue |
|
||
| `Err(e)` | 中断,返回 Err | 中断,返回 Err |
|
||
|
||
### 5.6 SqlType(跨 DB 抽象)
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum SqlType {
|
||
Text, Integer, BigInt, Real, Boolean, Blob,
|
||
}
|
||
|
||
impl SqlType {
|
||
pub fn as_str(self) -> &'static str {
|
||
match self {
|
||
SqlType::Text => { /* SQLite: TEXT / PostgreSQL: TEXT / MySQL: VARCHAR(255) */ }
|
||
SqlType::Integer => { /* SQLite: INTEGER / PostgreSQL: INTEGER / MySQL: INT */ }
|
||
SqlType::BigInt => { /* SQLite: INTEGER / PostgreSQL: BIGINT / MySQL: BIGINT */ }
|
||
SqlType::Real => { /* SQLite: REAL / PostgreSQL: DOUBLE PRECISION / MySQL: DOUBLE */ }
|
||
SqlType::Boolean => { /* SQLite: BOOLEAN / PostgreSQL: BOOLEAN / MySQL: TINYINT(1) */ }
|
||
SqlType::Blob => { /* SQLite: BLOB / PostgreSQL: BYTEA / MySQL: BLOB */ }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.7 ColumnDef
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
pub struct ColumnDef {
|
||
pub name: String,
|
||
pub sql_type: SqlType,
|
||
pub default: Option<String>,
|
||
}
|
||
```
|
||
|
||
## 6. Aspect trait(胖 trait 模式)
|
||
|
||
```rust
|
||
#[async_trait]
|
||
pub trait Aspect: Send + Sync + 'static {
|
||
fn name(&self) -> &str;
|
||
fn priority(&self) -> i32 { 0 }
|
||
fn pointcuts(&self) -> Vec<Pointcut>;
|
||
fn columns(&self) -> Vec<ColumnDef> { vec![] }
|
||
|
||
// ─── Data Layer(已实现)───
|
||
async fn on_data_before_create(&self, _ctx: &mut DataBeforeCreateContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_after_create(&self, _ctx: &mut DataAfterCreateContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_before_read(&self, _ctx: &mut DataBeforeReadContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_after_read(&self, _ctx: &mut DataAfterReadContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_before_update(&self, _ctx: &mut DataBeforeUpdateContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_after_update(&self, _ctx: &mut DataAfterUpdateContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_before_delete(&self, _ctx: &mut DataBeforeDeleteContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_data_after_delete(&self, _ctx: &mut DataAfterDeleteContext) -> AspectResult { Ok(Advice::Continue) }
|
||
|
||
// ─── Access Layer(Phase 5)───
|
||
async fn on_access_check(&self, _ctx: &mut AccessCheckContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_access_filter(&self, _ctx: &mut AccessFilterContext) -> AspectResult { Ok(Advice::Continue) }
|
||
|
||
// ─── Event Layer(Phase 4)───
|
||
async fn on_event_before_publish(&self, _ctx: &mut EventContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_event_after_publish(&self, _ctx: &mut EventContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_event_before_consume(&self, _ctx: &mut EventContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_event_after_consume(&self, _ctx: &mut EventContext) -> AspectResult { Ok(Advice::Continue) }
|
||
|
||
// ─── HTTP Layer(Phase 5)───
|
||
async fn on_http_before(&self, _ctx: &mut HttpBeforeContext) -> AspectResult { Ok(Advice::Continue) }
|
||
async fn on_http_after(&self, _ctx: &mut HttpAfterContext) -> AspectResult { Ok(Advice::Continue) }
|
||
}
|
||
```
|
||
|
||
### 6.1 为什么用 "胖 trait"
|
||
|
||
1. **统一注册:** `Vec<Arc<dyn Aspect>>` 一个列表管理所有 Aspect
|
||
2. **跨层 Aspect:** 一个 Aspect 可以同时拦截 HTTP + Data 层(如安全审计)
|
||
3. **简洁:** 不需要多 trait object 转换
|
||
4. **性能可接受:** Engine 在注册时通过 `pointcuts()` 预过滤,运行时只调用匹配的 hook
|
||
|
||
## 7. Context 类型体系
|
||
|
||
### 7.1 BaseContext
|
||
|
||
```rust
|
||
pub struct BaseContext {
|
||
pub user_id: Option<String>,
|
||
pub user_role: Option<String>,
|
||
pub tenant_id: String,
|
||
pub now: String, // ISO 8601
|
||
pub request_id: String,
|
||
pub extensions: Extensions,
|
||
pub pool: Option<crate::db::pool::Pool>,
|
||
}
|
||
```
|
||
|
||
`pool` 字段让 Aspect 可以在 hook 内执行数据库操作(如版本快照)。
|
||
|
||
### 7.2 Extensions(Aspect 间通信)
|
||
|
||
```rust
|
||
pub struct Extensions {
|
||
map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
|
||
}
|
||
|
||
impl Extensions {
|
||
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T);
|
||
pub fn get<T: 'static>(&self) -> Option<&T>;
|
||
pub fn remove<T: 'static>(&mut self) -> Option<T>;
|
||
}
|
||
```
|
||
|
||
### 7.3 Data Layer Contexts
|
||
|
||
```rust
|
||
pub type Record = serde_json::Map<String, Value>;
|
||
|
||
pub struct DataBeforeCreateContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub record: Record, // 可修改
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataAfterCreateContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub record: Record, // 只读
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataBeforeReadContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub query: ReadQuery, // 可修改
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataAfterReadContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub records: Vec<Record>, // 可修改
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataBeforeUpdateContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub old_record: Record, // 只读
|
||
pub new_record: Record, // 可修改
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataAfterUpdateContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub old_record: Record,
|
||
pub new_record: Record,
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataBeforeDeleteContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub record: Record, // 只读
|
||
pub soft_delete: bool, // Aspect 可设为 true
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
|
||
pub struct DataAfterDeleteContext {
|
||
pub base: BaseContext,
|
||
pub table: String,
|
||
pub record: Record,
|
||
pub schema: Option<Arc<ContentTypeSchema>>,
|
||
}
|
||
```
|
||
|
||
### 7.4 ReadQuery
|
||
|
||
```rust
|
||
pub struct ReadQuery {
|
||
pub filters: Vec<(String, String)>,
|
||
pub order_by: Option<String>,
|
||
pub page: u64,
|
||
pub page_size: u64,
|
||
pub fields: Option<Vec<String>>,
|
||
}
|
||
```
|
||
|
||
### 7.5 Access / Event / HTTP Layer Contexts
|
||
|
||
```rust
|
||
pub struct AccessCheckContext {
|
||
pub base: BaseContext,
|
||
pub route: String, pub method: String, pub table: Option<String>, pub action: String,
|
||
}
|
||
|
||
pub struct AccessFilterContext {
|
||
pub base: BaseContext,
|
||
pub table: String, pub conditions: Vec<String>, pub params: Vec<String>,
|
||
}
|
||
|
||
pub struct EventContext {
|
||
pub base: BaseContext,
|
||
pub event_type: String, pub payload: Value, pub table: Option<String>,
|
||
}
|
||
|
||
pub struct HttpBeforeContext {
|
||
pub base: BaseContext,
|
||
pub method: String, pub path: String, pub headers: HashMap<String, String>,
|
||
}
|
||
|
||
pub struct HttpAfterContext {
|
||
pub base: BaseContext,
|
||
pub status_code: u16, pub response_body: Option<Value>,
|
||
}
|
||
```
|
||
|
||
## 8. AspectEngine
|
||
|
||
### 8.1 数据结构
|
||
|
||
```rust
|
||
pub struct AspectEngine {
|
||
/// JoinPointId → 排序后的 Aspect 列表(注册时预计算,运行时 O(1) 查找)
|
||
dispatch_table: DashMap<JoinPointId, Vec<Arc<dyn Aspect>>>,
|
||
/// 所有 Aspect 的注册信息
|
||
registry: RwLock<Vec<AspectEntry>>,
|
||
}
|
||
|
||
pub struct AspectEntry {
|
||
pub aspect: Arc<dyn Aspect>,
|
||
pub pointcuts: Vec<Pointcut>,
|
||
pub enabled: bool,
|
||
}
|
||
```
|
||
|
||
### 8.2 注册
|
||
|
||
```rust
|
||
impl AspectEngine {
|
||
pub fn new() -> Self;
|
||
|
||
/// 从具体类型注册
|
||
pub fn register(&self, aspect: impl Aspect);
|
||
|
||
/// 从 Arc 注册(Protocol 层使用)
|
||
pub fn register_from_arc(&self, arc: Arc<dyn Aspect>);
|
||
}
|
||
```
|
||
|
||
注册流程:
|
||
1. 读取 `pointcuts()` 确定关心的 JoinPointId
|
||
2. 按 `priority()` 插入到 `dispatch_table` 对应列表
|
||
3. 保存到 `registry`
|
||
|
||
### 8.3 运行时开关
|
||
|
||
```rust
|
||
impl AspectEngine {
|
||
/// 启用指定 Aspect(按名称查找)
|
||
pub fn enable(&self, name: &str) -> bool;
|
||
/// 禁用指定 Aspect(按名称查找)
|
||
pub fn disable(&self, name: &str) -> bool;
|
||
}
|
||
```
|
||
|
||
### 8.4 查询
|
||
|
||
```rust
|
||
impl AspectEngine {
|
||
/// 返回所有 enabled 的 Aspect
|
||
pub fn aspects(&self) -> Vec<Arc<dyn Aspect>>;
|
||
|
||
/// 返回指定表相关的系统列(去重)
|
||
pub fn columns_for(&self, table: &str) -> Vec<ColumnDef>;
|
||
}
|
||
```
|
||
|
||
### 8.5 调度方法
|
||
|
||
```rust
|
||
impl AspectEngine {
|
||
// Data Layer — before 返回 Option<Value>(短路值),after 返回 ()
|
||
pub async fn dispatch_data_before_create(&self, table: &str, ctx: &mut DataBeforeCreateContext) -> Result<Option<Value>>;
|
||
pub async fn dispatch_data_after_create(&self, table: &str, ctx: &mut DataAfterCreateContext) -> Result<()>;
|
||
pub async fn dispatch_data_before_read(&self, table: &str, ctx: &mut DataBeforeReadContext) -> Result<Option<Value>>;
|
||
pub async fn dispatch_data_after_read(&self, table: &str, ctx: &mut DataAfterReadContext) -> Result<()>;
|
||
pub async fn dispatch_data_before_update(&self, table: &str, ctx: &mut DataBeforeUpdateContext) -> Result<Option<Value>>;
|
||
pub async fn dispatch_data_after_update(&self, table: &str, ctx: &mut DataAfterUpdateContext) -> Result<()>;
|
||
pub async fn dispatch_data_before_delete(&self, table: &str, ctx: &mut DataBeforeDeleteContext) -> Result<Option<Value>>;
|
||
pub async fn dispatch_data_after_delete(&self, table: &str, ctx: &mut DataAfterDeleteContext) -> Result<()>;
|
||
}
|
||
```
|
||
|
||
### 8.6 优先级约定
|
||
|
||
```
|
||
优先级范围 用途 示例
|
||
─────────────────────────────────────────────────────────
|
||
-9999 ~ -1000 核心基础设施 tenantable (-600)
|
||
-999 ~ -500 数据注入 ownable (-500), timestampable (-400)
|
||
-499 ~ 0 默认
|
||
1 ~ 499 业务逻辑 validation, slug 生成
|
||
500 ~ 999 副作用(事务内) 版本快照、搜索索引
|
||
1000 ~ 9999 事后处理(事务内) 缓存清除
|
||
10000+ 非关键(可降级) 统计、监控
|
||
```
|
||
|
||
## 9. Protocol 层
|
||
|
||
### 9.1 概念
|
||
|
||
Protocol = Aspect 组合 + 声明式效果(ProtocolDeclaration)。
|
||
|
||
```
|
||
ContentTypeSchema.implements = ["ownable", "timestampable", "soft_deletable"]
|
||
│
|
||
▼
|
||
ProtocolRegistry.get("ownable")
|
||
├─ name: "ownable"
|
||
├─ description: "创建和更新时自动注入操作者 ID"
|
||
├─ aspects: [OwnableAspect]
|
||
├─ columns: [created_by, updated_by]
|
||
├─ behaviors: ["track_owner"]
|
||
├─ declaration() → ProtocolDeclaration { ... }
|
||
├─ apply_config() → 用户配置应用到声明
|
||
├─ register_routes() → 协议自注册 API 路由
|
||
└─ on_after_delete() → 异步清理 hook
|
||
```
|
||
|
||
### 9.2 Protocol trait
|
||
|
||
```rust
|
||
pub trait Protocol: Send + Sync + 'static {
|
||
fn name(&self) -> &str;
|
||
fn description(&self) -> &str { "" }
|
||
fn aspects(&self) -> Vec<Arc<dyn Aspect>>;
|
||
fn columns(&self) -> Vec<ColumnDef> {
|
||
self.aspects().iter().flat_map(|a| a.columns()).collect()
|
||
}
|
||
fn behaviors(&self) -> Vec<&'static str> { vec![] }
|
||
fn built_in(&self) -> bool { false }
|
||
|
||
/// 协议声明式效果(纯数据)
|
||
fn declaration(&self) -> ProtocolDeclaration {
|
||
ProtocolDeclaration::default()
|
||
}
|
||
|
||
/// 将用户配置应用到声明(如 sortable 的 field/direction)
|
||
fn apply_config(&self, _config: &HashMap<String, String>, _decl: &mut ProtocolDeclaration, _all_columns: &[&str]) {}
|
||
|
||
/// 注册协议所需的额外 API 路由
|
||
fn register_routes(&self, _router: Router<AppState>, _plural: &str, _admin_prefix: &str) -> Router<AppState> { _router }
|
||
|
||
/// 删除记录后的异步回调
|
||
fn on_after_delete(&self, _pool: &Pool, _singular: &str, _id: &str) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> { Box::pin(async { Ok(()) }) }
|
||
}
|
||
```
|
||
|
||
### 9.3 ProtocolDeclaration
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct ProtocolDeclaration {
|
||
/// 查询时自动追加的 WHERE 过滤条件: (column, SQL_condition)
|
||
pub query_filters: Vec<(String, String)>,
|
||
/// 删除策略
|
||
pub delete_strategy: DeleteStrategy,
|
||
/// 更新前是否获取当前记录快照
|
||
pub snapshot_before_update: bool,
|
||
/// 是否提供版本历史 API 路由
|
||
pub revision_routes: bool,
|
||
/// 乐观锁列名
|
||
pub lock_column: Option<String>,
|
||
/// 列表查询的默认排序 (column, direction)
|
||
pub default_sort: Option<(String, SortDir)>,
|
||
/// statusable: 允许的状态值列表
|
||
pub status_values: Option<Vec<String>>,
|
||
/// statusable: 数字映射 (label → number)
|
||
pub status_map: Option<Vec<(String, i64)>>,
|
||
/// statusable: 默认状态值
|
||
pub status_default: Option<String>,
|
||
/// statusable: 存储模式
|
||
pub status_mode: StatusMode,
|
||
}
|
||
```
|
||
|
||
### 9.4 merge() 策略
|
||
|
||
多个协议的 `ProtocolDeclaration` 通过 `merge()` 聚合:
|
||
|
||
| 字段 | 策略 |
|
||
|------|------|
|
||
| `query_filters` | 累积(extend) |
|
||
| `delete_strategy` | Soft 优先 Hard |
|
||
| `snapshot_before_update` / `revision_routes` | OR |
|
||
| `lock_column` / `default_sort` | last-wins + warn on conflict |
|
||
| `status_*` | last-wins |
|
||
|
||
### 9.5 ProtocolRef
|
||
|
||
`implements` 字段支持两种语法:
|
||
|
||
```rust
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(untagged)]
|
||
pub enum ProtocolRef {
|
||
Simple(String),
|
||
WithConfig {
|
||
name: String,
|
||
#[serde(flatten)]
|
||
config: HashMap<String, String>,
|
||
},
|
||
}
|
||
```
|
||
|
||
TOML 中使用:
|
||
|
||
```toml
|
||
# 简单语法
|
||
implements = ["ownable", "timestampable"]
|
||
|
||
# 带配置语法
|
||
implements = [
|
||
{ name = "sortable", field = "priority", direction = "desc" },
|
||
{ name = "statusable", values = "draft=1,published=10", default = "1", mode = "numeric" },
|
||
]
|
||
```
|
||
|
||
### 9.6 协议自注册(inventory)
|
||
|
||
每个协议文件末尾一行自注册:
|
||
|
||
```rust
|
||
// src/protocols/sortable.rs 末尾
|
||
crate::register_protocol!(
|
||
crate::protocols::sortable::SortableProtocol,
|
||
crate::protocols::sortable::SortableProtocol
|
||
);
|
||
```
|
||
|
||
`lib.rs` 中一行完成所有注册:
|
||
|
||
```rust
|
||
protocol_registry.register_from_inventory();
|
||
```
|
||
|
||
### 9.7 列冲突检测
|
||
|
||
`columns_for()` 对同名不同类型的列采用 **first-wins + warn**(不阻断启动),因为 sortable 可能用已有列排序。
|
||
|
||
### 9.8 ProtocolRegistry
|
||
|
||
```rust
|
||
pub struct ProtocolRegistry {
|
||
protocols: HashMap<String, Arc<dyn Protocol>>,
|
||
}
|
||
|
||
impl ProtocolRegistry {
|
||
pub fn new() -> Self;
|
||
pub fn register(&mut self, protocol: impl Protocol);
|
||
pub fn register_from_inventory(&mut self);
|
||
pub fn get(&self, name: &str) -> Option<&Arc<dyn Protocol>>;
|
||
pub fn names(&self) -> Vec<&str>;
|
||
|
||
/// 按名称查询列(自动去重,first-wins + warn)
|
||
pub fn columns_for(&self, names: &[String]) -> Vec<ColumnDef>;
|
||
|
||
/// 按名称查询 Aspect(自动去重)
|
||
pub fn aspects_for(&self, names: &[String]) -> Vec<Arc<dyn Aspect>>;
|
||
|
||
/// 聚合多个协议的声明
|
||
pub fn declaration_for(&self, names: &[String]) -> ProtocolDeclaration;
|
||
|
||
/// 对聚合后的声明应用用户配置
|
||
pub fn apply_config_for(&self, impl_refs: &[ProtocolRef], decl: &mut ProtocolDeclaration, all_columns: &[&str]);
|
||
|
||
/// 注册所有协议的额外路由
|
||
pub fn register_routes_for(&self, names: &[String], router: Router<AppState>, plural: &str, admin_prefix: &str) -> Router<AppState>;
|
||
|
||
/// 删除后回调
|
||
pub async fn dispatch_after_delete(&self, names: &[String], pool: &Pool, singular: &str, id: &str) -> Result<()>;
|
||
|
||
/// 将所有 Protocol 的 Aspect 注册到 AspectEngine(去重)
|
||
pub fn register_aspects_into(&self, engine: &AspectEngine);
|
||
}
|
||
```
|
||
|
||
## 10. 内置 Protocol 实现(11 种)
|
||
|
||
### 10.1 ownable
|
||
|
||
**文件:** `src/protocols/ownable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `OwnableAspect` (priority: -500) |
|
||
| 注入列 | `created_by` TEXT, `updated_by` TEXT |
|
||
| behaviors | `["track_owner"]` |
|
||
| Pointcuts | Data Create Before + Data Update Before |
|
||
|
||
Aspect 使用 `is_protocol_column` 守卫:
|
||
```rust
|
||
ctx.schema.as_ref().is_none_or(|s| s.is_protocol_column(COL_CREATED_BY))
|
||
```
|
||
|
||
### 10.2 timestampable
|
||
|
||
**文件:** `src/protocols/timestampable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `TimestampableAspect` (priority: -400) |
|
||
| 注入列 | `created_at` TEXT, `updated_at` TEXT |
|
||
| behaviors | `["track_timestamps"]` |
|
||
| Pointcuts | Data Create Before + Data Update Before |
|
||
|
||
### 10.3 soft_deletable
|
||
|
||
**文件:** `src/protocols/soft_deletable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `SoftDeletableAspect` |
|
||
| 注入列 | `deleted_at` TEXT, `deleted_by` TEXT |
|
||
| behaviors | `["soft_delete"]` |
|
||
| Declaration | `query_filters: [("deleted_at", "IS NULL")]`, `delete_strategy: Soft { column: "deleted_at" }` |
|
||
|
||
### 10.4 versionable
|
||
|
||
**文件:** `src/protocols/versionable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `VersionableAspect` |
|
||
| 注入列 | `version` INTEGER |
|
||
| behaviors | `["versioning"]` |
|
||
| Declaration | `snapshot_before_update: true`, `revision_routes: true` |
|
||
| register_routes | `/revisions`, `/revisions/{rev_id}`, `/revisions/{rev_id}/restore`, `/revisions/{rev_a}/diff/{rev_b}` |
|
||
| on_after_delete | 清理 `content_revisions` 表中关联记录 |
|
||
|
||
### 10.5 lockable
|
||
|
||
**文件:** `src/protocols/lockable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| 注入列 | `lock_version` INTEGER (DEFAULT 0) |
|
||
| behaviors | `["optimistic_lock"]` |
|
||
| Declaration | `lock_column: Some("lock_version")` |
|
||
| Repository 行为 | UPDATE WHERE lock_version = ? + SET lock_version += 1,冲突返回 409 |
|
||
|
||
### 10.6 sortable
|
||
|
||
**文件:** `src/protocols/sortable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| 注入列 | 无(使用已有列排序) |
|
||
| behaviors | `["sortable"]` |
|
||
| Declaration | `default_sort: Some(("created_at", Desc))` |
|
||
| apply_config | 支持 `field` / `direction` 配置 |
|
||
|
||
```toml
|
||
implements = [{ name = "sortable", field = "priority", direction = "desc" }]
|
||
```
|
||
|
||
### 10.7 expirable
|
||
|
||
**文件:** `src/protocols/expirable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `ExpirableAspect` (priority: -200) |
|
||
| 注入列 | `expires_at` TEXT |
|
||
| behaviors | `["expirable"]` |
|
||
| Declaration | `query_filters: [("expires_at", "IS NULL OR expires_at > datetime('now')")]` |
|
||
|
||
### 10.8 nestable
|
||
|
||
**文件:** `src/protocols/nestable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| 注入列 | `parent_id` TEXT, `depth` INTEGER, `position` INTEGER |
|
||
| behaviors | `["nestable"]` |
|
||
|
||
### 10.9 statusable
|
||
|
||
**文件:** `src/protocols/statusable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `StatusableAspect` (priority: -150) |
|
||
| 注入列 | `status` TEXT |
|
||
| behaviors | `["statusable"]` |
|
||
| apply_config | 支持 `values` / `default` / `mode` 配置 |
|
||
| Aspect 行为 | create 注入默认值 + create/update 校验合法值 |
|
||
| 存储模式 | 字符串模式(默认)或数字映射模式 |
|
||
|
||
```toml
|
||
implements = [{ name = "statusable", values = "draft=1,published=10,archived=99", default = "1", mode = "numeric" }]
|
||
```
|
||
|
||
> 查询过滤不由协议处理,由 API rule engine 控制:`[api.list] filter = 'status = "published"'`。
|
||
|
||
### 10.10 metaable
|
||
|
||
**文件:** `src/protocols/metaable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| Aspect | `MetaableAspect` |
|
||
| 注入列 | `__meta` TEXT (DEFAULT '{}') |
|
||
| behaviors | `["metaable"]` |
|
||
| 查询支持 | `?__meta.views=100` → `json_extract(__meta, '$.views') = '100'` |
|
||
|
||
> `__meta` 不再硬编码到所有表,由 `implements = ["metaable"]` 控制。
|
||
|
||
### 10.11 tenantable
|
||
|
||
**文件:** `src/protocols/tenantable.rs`
|
||
|
||
| 属性 | 值 |
|
||
|------|------|
|
||
| 注入列 | `tenant_id` TEXT (NOT NULL DEFAULT 'default') |
|
||
| behaviors | `["tenantable"]` |
|
||
| Repository 行为 | `ct.implements_protocol("tenantable")` 判断,自动注入和过滤 |
|
||
| Aspect | 纯列声明(pointcuts 为空),tenant_id 值由 Repository 统一注入 |
|
||
|
||
> `tenant_id` 不再硬编码到所有表,由 `implements = ["tenantable"]` 控制。不再运行时检测 DB 列,改为 schema 级别判断。
|
||
|
||
## 11. Aspect `is_protocol_column` 守卫
|
||
|
||
所有 Aspect 的 `on_data_before_create` / `on_data_before_update` 统一使用:
|
||
|
||
```rust
|
||
ctx.schema.as_ref().is_none_or(|s| s.is_protocol_column(COL_XXX))
|
||
```
|
||
|
||
- 有 schema → 只在该协议列真正需要创建时注入
|
||
- 无 schema → 放行(向后兼容单元测试)
|
||
|
||
## 12. ProtocolDeclaration 消费方
|
||
|
||
Repository 层直接读取 `ct.declaration()` 获取聚合后的声明:
|
||
|
||
| 消费方 | 使用的字段 |
|
||
|--------|-----------|
|
||
| `find()` (列表查询) | `query_filters` → WHERE, `default_sort` → ORDER BY |
|
||
| `create()` | `tenant_id` 由 `ct.implements_protocol("tenantable")` 判断 |
|
||
| `update()` | `snapshot_before_update` → 保存快照, `lock_column` → 乐观锁 |
|
||
| `delete()` | `delete_strategy` → Soft/Hard 判断 |
|
||
| `soft_delete()` | `delete_strategy` → 软删除列名 |
|
||
| migration | `columns_for()` → 动态列 |
|
||
| handler | `is_soft_delete()` / `has_revision_routes()` / `declaration()` |
|
||
|
||
## 13. AspectDispatch helper
|
||
|
||
**文件:** `src/services/aspect_dispatch.rs`
|
||
|
||
内置表 Service 层的轻量 dispatch helper,每个操作只需 3 行:
|
||
|
||
```rust
|
||
pub struct AspectDispatch<'a> {
|
||
pub engine: &'a AspectEngine,
|
||
pub pool: &'a Pool,
|
||
pub table: &'a str,
|
||
pub user_id: Option<&'a str>,
|
||
pub tenant_id: Option<&'a str>,
|
||
}
|
||
|
||
impl AspectDispatch<'_> {
|
||
pub async fn before_create(&self, record: Record) -> AppResult<()>;
|
||
pub async fn after_create(&self, record: Record);
|
||
pub async fn before_update(&self, old_record: Record, new_record: Record) -> AppResult<()>;
|
||
pub async fn after_update(&self, new_record: Record);
|
||
pub async fn before_delete(&self, record: Record) -> AppResult<()>;
|
||
pub async fn after_delete(&self);
|
||
}
|
||
|
||
pub fn id_record(id: &str) -> Record;
|
||
```
|
||
|
||
## 14. 系统集成
|
||
|
||
### 14.1 AppState
|
||
|
||
```rust
|
||
pub struct AppState {
|
||
pub pool: Pool,
|
||
pub config: Arc<AppConfig>,
|
||
pub aspect_engine: Arc<AspectEngine>,
|
||
pub protocol_registry: Arc<ProtocolRegistry>,
|
||
// ... 其他字段
|
||
}
|
||
```
|
||
|
||
### 14.2 启动流程
|
||
|
||
```rust
|
||
pub async fn build_app_state(config: &AppConfig) -> anyhow::Result<AppState> {
|
||
// 1. 创建 ProtocolRegistry,inventory 自注册所有内置 Protocol
|
||
let mut protocol_registry = ProtocolRegistry::new();
|
||
protocol_registry.register_from_inventory();
|
||
let protocol_registry = Arc::new(protocol_registry);
|
||
|
||
// 2. 创建 AspectEngine,通过 ProtocolRegistry 注册所有 Aspect
|
||
let aspect_engine = Arc::new(AspectEngine::new());
|
||
protocol_registry.register_aspects_into(&aspect_engine);
|
||
}
|
||
```
|
||
|
||
### 14.3 Content Type Handler 集成
|
||
|
||
handler.rs — `do_create` / `do_update` / `do_delete` / `do_list` / `do_get` 接入 AspectEngine dispatch。
|
||
|
||
Aspect 编排放在 Handler 层(不放在 Repository 层):
|
||
|
||
```
|
||
handler 调 aspect.before → repo (纯数据操作) → aspect.after
|
||
```
|
||
|
||
Repository 层保持纯粹的数据操作,不包含横切面逻辑。
|
||
|
||
### 14.4 Content Type Migration 集成
|
||
|
||
migration.rs — 协议列由 `ProtocolRegistry::columns_for()` 动态获取,不再硬编码任何系统列。
|
||
|
||
### 14.5 Content Type Repository 集成
|
||
|
||
repository.rs — 基于 `ct.declaration()` 驱动 SQL 行为:
|
||
- `resolve_tenant()` — `ct.implements_protocol("tenantable")` 判断(同步,零 IO)
|
||
- `build_order_by()` — `declaration().default_sort`
|
||
- `find()` — `query_filters` + `meta_filters` (json_extract)
|
||
- `update()` — `lock_column` + `snapshot_before_update`
|
||
- `delete()` — `delete_strategy`
|
||
|
||
### 14.6 内置表集成
|
||
|
||
已覆盖的内置表:
|
||
|
||
| 内置表 | create | update | delete | 文件 |
|
||
|---|---|---|---|---|
|
||
| posts | ✅ | ✅ | ✅ | `src/services/post.rs` |
|
||
| pages | ✅ | ✅ | ✅ | `src/services/page.rs` |
|
||
| comments | ✅ | ✅ | ✅ | `src/services/comment.rs` |
|
||
| categories | ✅ | ✅ | ✅ | `src/services/post.rs` |
|
||
| tags | ✅ | — | ✅ | `src/services/post.rs` |
|
||
| reusable_blocks | ✅ | ✅ | ✅ | `src/services/page.rs` |
|
||
|
||
## 15. 与旧机制的关系
|
||
|
||
| 旧机制 | 当前替代 |
|
||
|---|---|
|
||
| `auto_fill: UserId` | **废弃**,由 OwnableAspect 替代 |
|
||
| `auto_fill: CurrentTimestamp` | **废弃**,由 TimestampableAspect 替代 |
|
||
| `timestamps: true` (ContentTypeSchema) | **废弃**,由 timestampable 协议替代 |
|
||
| `draft_publish` 字段 | **废弃**,由 statusable 协议替代 |
|
||
| `list_view` 配置 | **废弃**,由 sortable 协议的 default_sort 替代 |
|
||
| `cacheable` 协议 | **删除**,缓存由 handler 内置 DashMap TTL 处理 |
|
||
| `__meta` 硬编码 | **废弃**,由 metaable 协议控制 |
|
||
| `tenant_id` 硬编码 | **废弃**,由 tenantable 协议控制 |
|
||
| `has_tenant_id()` DB 检测 | **废弃**,改为 `ct.implements_protocol("tenantable")` |
|
||
|
||
## 16. 迁移计划
|
||
|
||
### Phase 1 — 基础设施 ✅ 已完成
|
||
|
||
1. `src/aspects.rs` — Aspect trait + Context 类型 + Advice + Extensions + Pointcut + SqlType
|
||
2. `src/aspects/engine.rs` — AspectEngine 注册/调度/匹配 + enable/disable
|
||
3. Content Type 表接入 AspectEngine
|
||
|
||
### Phase 2 — 扩展 Protocols ✅ 已完成
|
||
|
||
11 个内置协议全部实现:ownable、timestampable、soft_deletable、versionable、lockable、sortable、expirable、nestable、statusable、metaable、tenantable。
|
||
|
||
### Phase 3 — 内置表迁移 ✅ 已完成
|
||
|
||
1. 内置表 service 层接入 AspectEngine
|
||
2. `AspectDispatch` helper 减少重复代码
|
||
|
||
### Phase 4 — 插件 Aspect ⏳ 待做
|
||
|
||
1. 插件 manifest.toml 的 `[[aspect]]` 声明
|
||
2. JS/Lua/WASM Aspect 执行器
|
||
3. 性能隔离和错误降级
|
||
|
||
### Phase 5 — Access Layer 和 HTTP Layer ⏳ 待做
|
||
|
||
1. access_check / access_filter 集成
|
||
2. 替换现有 rule_engine 为 Access Aspect
|
||
3. HTTP Layer Aspect(统一 middleware 注册)
|
||
|
||
## 17. 文件结构
|
||
|
||
```
|
||
src/
|
||
aspects.rs — Aspect trait, Context 类型, Advice, Extensions, Pointcut, SqlType, ColumnDef
|
||
aspects/
|
||
engine.rs — AspectEngine 注册/调度/匹配/enable/disable/Debug
|
||
|
||
protocols.rs — Protocol trait + ProtocolRegistry + ProtocolDeclaration + DeleteStrategy + SortDir + StatusMode + inventory 自注册
|
||
protocols/
|
||
ownable.rs — OwnableAspect + OwnableProtocol
|
||
timestampable.rs — TimestampableAspect + TimestampableProtocol
|
||
soft_deletable.rs — SoftDeletableAspect + SoftDeletableProtocol
|
||
versionable.rs — VersionableAspect + VersionableProtocol (register_routes + on_after_delete)
|
||
lockable.rs — LockableProtocol
|
||
sortable.rs — SortableProtocol (apply_config)
|
||
expirable.rs — ExpirableAspect + ExpirableProtocol
|
||
nestable.rs — NestableProtocol
|
||
statusable.rs — StatusableAspect + StatusableProtocol (apply_config)
|
||
metaable.rs — MetaableAspect + MetaableProtocol
|
||
tenantable.rs — TenantableProtocol (纯列声明)
|
||
|
||
constants.rs — COL_CREATED_BY, COL_UPDATED_BY, COL_CREATED_AT, COL_UPDATED_AT, COL_DELETED_AT, COL_DELETED_BY, COL_VERSION, COL_LOCK_VERSION, COL_SORT_KEY, COL_STATUS, COL_EXPIRES_AT, COL_PARENT_ID, COL_DEPTH, COL_POSITION, COL_META, COL_TENANT_ID, COL_ID
|
||
|
||
services/
|
||
aspect_dispatch.rs — AspectDispatch helper + id_record
|
||
|
||
content_type/
|
||
handler.rs — do_create/do_update/do_delete/do_list 接入 AspectEngine + DashMap 缓存 + meta_filters
|
||
migration.rs — 协议列动态注入(不硬编码任何系统列)
|
||
repository.rs — ProtocolDeclaration 驱动 SQL + schema 级 tenantable 判断
|
||
schema.rs — ContentTypeSchema + ProtocolRef (Simple/WithConfig) + implements_protocol()
|
||
|
||
lib.rs — protocol_registry.register_from_inventory() + register_aspects_into()
|
||
```
|
||
|
||
## 18. 设计原则
|
||
|
||
1. **一次定义,全局生效** — 每个 Aspect 定义一次,对所有表自动生效
|
||
2. **声明式 + 命令式分离** — ProtocolDeclaration 纯数据驱动 SQL 行为,Aspect 处理命令式副作用
|
||
3. **协议组合** — 多个协议通过 `merge()` 聚合,first-wins/last-wins 策略明确
|
||
4. **优先级驱动** — 明确的执行顺序,避免隐式依赖
|
||
5. **类型安全** — 每个 JoinPoint 有专属 Context 类型
|
||
6. **可扩展** — 新协议只需 1 个文件 + 1 行 `register_protocol!`
|
||
7. **事务感知** — Data 层在事务内,Event 层在事务外
|
||
8. **优雅降级** — after hook 失败只记 warn 日志,不阻断主操作
|
||
9. **数据驱动** — 扩展 ProtocolDeclaration 字段时,`merge_covers_all_declaration_fields` 测试会红灯提醒
|