Files
raisfast/docs/protocol-design-v2.md
T
2026-05-07 13:09:37 +08:00

37 KiB
Raw Blame History

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

#[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

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct JoinPointId {
    pub layer: Layer,
    pub operation: Operation,
    pub when: When,
}

5.3 TargetMatcher

#[derive(Debug, Clone)]
pub enum TargetMatcher {
    /// 匹配所有目标
    All,
    /// 精确匹配表名列表
    Tables(Vec<String>),
}

5.4 Pointcut

#[derive(Debug, Clone)]
pub struct Pointcut {
    pub layer: Layer,
    pub operation: Operation,
    pub when: When,
    pub target: TargetMatcher,
}

5.5 Advice(三态语义)

#[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 抽象)

#[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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnDef {
    pub name: String,
    pub sql_type: SqlType,
    pub default: Option<String>,
}

6. Aspect trait(胖 trait 模式)

#[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 LayerPhase 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 LayerPhase 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 LayerPhase 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

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 ExtensionsAspect 间通信)

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

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

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

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 数据结构

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 注册

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 运行时开关

impl AspectEngine {
    /// 启用指定 Aspect(按名称查找)
    pub fn enable(&self, name: &str) -> bool;
    /// 禁用指定 Aspect(按名称查找)
    pub fn disable(&self, name: &str) -> bool;
}

8.4 查询

impl AspectEngine {
    /// 返回所有 enabled 的 Aspect
    pub fn aspects(&self) -> Vec<Arc<dyn Aspect>>;

    /// 返回指定表相关的系统列(去重)
    pub fn columns_for(&self, table: &str) -> Vec<ColumnDef>;
}

8.5 调度方法

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

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

#[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 字段支持两种语法:

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProtocolRef {
    Simple(String),
    WithConfig {
        name: String,
        #[serde(flatten)]
        config: HashMap<String, String>,
    },
}

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

每个协议文件末尾一行自注册:

// src/protocols/sortable.rs 末尾
crate::register_protocol!(
    crate::protocols::sortable::SortableProtocol,
    crate::protocols::sortable::SortableProtocol
);

lib.rs 中一行完成所有注册:

protocol_registry.register_from_inventory();

9.7 列冲突检测

columns_for() 对同名不同类型的列采用 first-wins + warn(不阻断启动),因为 sortable 可能用已有列排序。

9.8 ProtocolRegistry

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 守卫:

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 配置
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 校验合法值
存储模式 字符串模式(默认)或数字映射模式
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=100json_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 统一使用:

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_idct.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 行:

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

pub struct AppState {
    pub pool: Pool,
    pub config: Arc<AppConfig>,
    pub aspect_engine: Arc<AspectEngine>,
    pub protocol_registry: Arc<ProtocolRegistry>,
    // ... 其他字段
}

14.2 启动流程

pub async fn build_app_state(config: &AppConfig) -> anyhow::Result<AppState> {
    // 1. 创建 ProtocolRegistryinventory 自注册所有内置 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 测试会红灯提醒