## `domain` 目录深度讲解

`domain` 是项目的领域层(Domain Layer),遵循 **DDD(Domain-Driven Design)** 分层架构,承载所有业务实体、数据访问对象、业务服务、外观整合与 Web 控制器,是项目的核心业务逻辑层。

---

### 📦 目录结构总览(与实际代码一致)

```
src/domain/
├── mod.rs               # 入口:导出子模块 + serde 序列化辅助(i64→字符串)
├── rudb/                # 核心数据库域
│   ├── dbentity/        #   领域实体(SeaORM 表结构映射)
│   │   ├── plat_menu.rs / user_credits.rs       SeaORM 自动生成实体
│   │   ├── t_sys_user.rs / sys_dept.rs          数据库表实体
│   │   ├── person.rs                            手写示例实体(BaseEntity)
│   │   ├── db_multi.rs                          多实例模式示例
│   │   ├── *_init.rs                            实体 Bean 自动注册(DI)
│   │   └── *_test.rs                            #[cfg(test)] 单元测试
│   ├── dbdao/           #   数据访问对象(DAO)
│   │   ├── plat_menu_dao.rs / t_sys_user_dao.rs DAO(基于 GeneralDb)
│   │   ├── *_dao_init.rs                        DAO 自动注册(DI)
│   │   └── *_dao_test.rs                        #[cfg(test)]
│   ├── dbpage/          #   分页查询处理器
│   │   ├── user_credits_page.rs / t_sys_user_page.rs / plat_menu_page.rs
│   │   ├── config_page.rs                       HTTP 配置展示页
│   │   ├── *_page_init.rs                       分页 Bean 自动注册(DI)
│   │   └── *_page_test.rs                       #[cfg(test)]
│   ├── dbfacade/        #   外观整合层(Facade)
│   │   ├── db_facade.rs                         组合多个 DAO/实体
│   │   ├── db_facade_init.rs                    外观 Bean 自动注册
│   │   └── db_facade_test.rs                    #[cfg(test)]
│   └── dbweb/           #   Web 控制器(Ctl)
│       ├── user_credits_ctl.rs                  对外暴露 REST 接口
│       └── mod.rs
└── ruservice/           # 业务服务层(扩展)
    ├── ru_config_ext.rs + run_config_ext_init.rs  配置扩展
    ├── ru_cache_ext.rs + ru_cache_ext_init.rs     缓存扩展
    ├── web_client_ext.rs + web_client_ext_init.rs Web 客户端扩展
    └── 各 *_test.rs                               #[cfg(test)]
```

---

### 1️⃣ `mod.rs` — 入口与序列化辅助

[mod.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/mod.rs) 除了声明 `rudb`、`ruservice` 子模块并 `pub use rudb::dbfacade::*` 外,定义了**两个关键的 serde 序列化辅助模块**:

- `serde_i64_string`:将 `i64` 以**字符串形式**序列化/反序列化
- `serde_option_i64_string`:同上,但支持 `Option<i64>`

> **为什么?** JS 端 `Number` 无法精确表示超过 `2^53` 的整数,直接下发 i64 会丢失精度。转成字符串可保证数据库大 id 安全传向前端。

```rust
use serde::{self, Deserialize, Deserializer, Serializer};

pub mod serde_i64_string {
    pub fn serialize<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer {
        serializer.serialize_str(&value.to_string())
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<i64, D::Error>
    where D: Deserializer<'de> {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(serde::de::Error::custom)
    }
}
```

---

### 2️⃣ `rudb` — 核心数据库域

[rudb/mod.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/mod.rs) 聚合 5 个子模块:`dbentity`、`dbdao`、`dbpage`、`dbfacade`、`dbweb`。

#### ① `dbentity` — 领域实体

分两类:

**A. SeaORM 自动生成实体**(配 `*_init.rs` 自动注册)

[plat_menu.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbentity/plat_menu.rs):

```rust
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "plat_menu")]
#[serde(rename_all = "camelCase")]          // 对外使用 camelCase
pub struct Model {
    #[sea_orm(primary_key)]
    #[serde(with = "serde_i64_string")]     // 大 id → 字符串
    pub id: i64,
    pub menu_code: Option<String>,
    pub menu_name: Option<String>,
    pub created_at: Option<DateTimeWithTimeZone>,
    ...
}
```

[user_credits.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbentity/user_credits.rs) 是另一个典型大表实体,包含大量 `Decimal/Option` 字段和唯一键 `idx_user_credits_user_currency_id`。

> **关键**:一个 `Model` 既是 SeaORM 数据库映射,又(因 `Serialize/Deserialize`)直接充当 API 的 DTO,配 `serde_i64_string` 解决前端精度问题,从而让实体即 DTO、无需额外转换层。

**B. 手写示例实体**

[person.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbentity/person.rs)(`BaseEntity` 多实例)与 [db_multi.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbentity/db_multi.rs):

```rust
// person.rs
impl BaseEntity for Person {}      // 多实例:每次 DI 获取新实例
impl GetSelf for Person {}

// db_multi.rs
impl BaseEntity for DbMulti {}     // same
impl DbMulti {
    pub fn execute(&self) -> String { ru_log::info2("RuMulti execute 123 {}", self.id); ... }
}
```

#### ② `dbdao` — 数据访问对象(DAO)

[plat_menu_dao.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbdao/plat_menu_dao.rs):

```rust
pub struct PlatMenuDao {
    #[serde(skip)]
    pub general_db: GeneralDb<plat_menu::Entity>,   // 泛型数据库操作器
}
impl BaseEntity for PlatMenuDao {}
impl PlatMenuDao {
    pub fn new() -> Self { Self { general_db: GeneralDb::<plat_menu::Entity>::new() } }
}

// ⭐ Deref 委托:DAO 自动获得 GeneralDb 的全部 CRUD 能力
impl Deref for PlatMenuDao {
    type Target = GeneralDb<plat_menu::Entity>;
    fn deref(&self) -> &Self::Target { &self.general_db }
}
impl DerefMut for PlatMenuDao { ... }
```

> **Deref 委托模式**:DAO 无需逐个转发 `find_by_id / insert / update / delete / query_model` 等方法,通过 `Deref/DerefMut` 自动透明继承 `GeneralDb<T>` 的能力。

**DI 自动注入**([plat_menu_dao_init.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbdao/plat_menu_dao_init.rs)):

```rust
#[ctor(unsafe)]                    // 程序启动时自动执行,无需手动调用
pub fn init() { PlatMenuDao::register_bean(); }

impl BeanSimple for PlatMenuDao {
    fn new_bean() -> Self { let mut bean = PlatMenuDao::new(); bean.init(); bean }
}

pub fn find_bean_plat_menu_dao() -> Option<Arc<Mutex<PlatMenuDao>>> { PlatMenuDao::find_bean() }  // 线程安全
pub fn find_bean_plat_menu_dao_simple() -> PlatMenuDao { PlatMenuDao::new_bean() }                 // 便捷获取
```

#### ③ `dbpage` — 分页查询处理器

[user_credits_page.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbpage/user_credits_page.rs) 通过 `Deref` 委托 `SimplePage`,并提供业务化的分页查询编排:

```rust
pub type SimplePageUserCredits = SimplePage<UserCreditsRequest, user_credits::Entity, user_credits::Model>;

pub struct UserCreditsPage { pub simple_page: SimplePageUserCredits }
impl Deref for UserCreditsPage { ... target = simple_page ... }

impl UserCreditsPage {
    pub async fn list(&mut self) -> PageResult {
        self.build_query();
        self.init_query();       // 业务查询条件(如 id>0 时过滤)
        self.general_db.query_model().await
    }
    pub async fn ui_list(&mut self) -> PageResult { self.list().await }
}
```

[config_page.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbpage/config_page.rs) 是 `BaseEntitySingle`(单例),直接作为 HTTP Responder 暴露 `/conf` 配置接口,展示"领域层直接绑定 HTTP 路由"的用法。

#### ④ `dbfacade` — 外观整合层

[db_facade.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbfacade/db_facade.rs) 组合多个组件,对外提供统一入口:

```rust
#[derive(Debug)]
pub struct DbFacade {
    pub multi: DbMulti,       // 业务实体
    pub dao: PlatMenuDao,     // DAO
}
impl BaseEntitySingle for DbFacade {}        // 单例
impl DbFacade {
    pub fn new() -> Self {
        Self {
            dao: find_bean_plat_menu_dao_simple(),   // 从 DI 拉取
            multi: find_bean_db_multi_simple(),
        }
    }
    pub fn execute(&self) -> String { ... }
}
```

[db_facade_init.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbfacade/db_facade_init.rs) 使用 `register_bean_singleton()` 注册**单例**:

```rust
#[ctor(unsafe)] pub fn init() { DbFacade::register_bean_singleton(); }
impl BeanSingle for DbFacade {
    fn new_bean() -> Self { let mut bean = DbFacade::new(); bean.init(); bean }
}
```

> **对比**:`BeanSimple`/`register_bean` = 多实例;`BeanSingle`/`register_bean_singleton` = 全局单例。`DbFacade` 是无状态整合对象,故用单例。

#### ⑤ `dbweb` — Web 控制器

[user_credits_ctl.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/rudb/dbweb/user_credits_ctl.rs) 是领域层对外暴露的 REST 层,覆盖标准 CRUD:

| 方法 | 路由 | 作用 |
|------|------|------|
| `uilist`   | `POST /api/v1/user_credits/uilist`   | 分页列表 |
| `uifind`   | `GET  /api/v1/user_credits/uifind/{id}` | 按 id 查询 |
| `uidel`    | `GET  /api/v1/user_credits/uidel/{id}`  | 删除 |
| `uisave`   | `POST /api/v1/user_credits/uisave`   | 新增(id=0 → NotSet) |
| `uiupdate` | `POST /api/v1/user_credits/uiupdate` | 更新 |

```rust
pub async fn uilist(req: web::Json<SimpleRequest<UserCreditsRequest>>) -> impl Responder {
    let mut page = find_bean_user_credits_page_simple();   // DI 获取分页处理器
    page.set_uireq(req.into_inner());
    let ret = page.ui_list().await;
    res_ok(ret)                                             // 统一响应包裹
}

pub fn register() {
    reg_route!(api_v1_user_credits_uilist, "/api/v1/user_credits/uilist", post, UserCreditsCtl::uilist);
    // ... 其余路由
}
```

---

### 3️⃣ `ruservice` — 业务服务层(扩展)

[ruservice/mod.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/ruservice/mod.rs) 提供三个扩展服务,均以"实现文件 + 独立 `_init` 自动注册"组织。

- [ru_config_ext.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/ruservice/ru_config_ext.rs) `RunConfigExt`:包装 `RuConfig`,通过 `Deref` 透明委托,提供 `read_nats()` 等按 DTO 读取配置的方法;`BaseEntitySingle` 单例。
- [web_client_ext.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/ruservice/web_client_ext.rs) `WebClientExt`:包装 `WebClient` + 内置 `RuCache`(超时 900s),`Deref` 委托 `WebClient`。
- [ru_cache_ext.rs](file:///E:/soft/gitee.com/ruwebframe/src/domain/ruservice/ru_cache_ext.rs) `RuCacheExt`:缓存扩展。

```rust
// ru_config_ext.rs —— "Go 式组合/嵌入"的 Rust 表达
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RunConfigExt { pub conf: RuConfig }

impl BaseEntitySingle for RunConfigExt {}
impl Deref  for RunConfigExt { type Target = RuConfig; fn deref(&self) -> &RuConfig { &self.conf } }
impl DerefMut for RunConfigExt { ... }

impl RunConfigExt {
    pub fn new() -> Self { let mut c = RunConfigExt { conf: RuConfig::new() }; c.init(); c }
    pub fn read_nats(&self) -> NatsDto { self.parse_object::<NatsDto>("nats") }
}
```

每个扩展都有对应的 `*_init.rs`,用 `#[ctor(unsafe)]` 在启动时注册 Bean,业务代码通过 `find_bean_*()` 获取。

---

### 🧩 数据流:一次请求的完整旅程

```
浏览器
  → POST /api/v1/user_credits/uilist            [dbweb/user_credits_ctl.rs]
  → find_bean_user_credits_page_simple()         [dbpage/ 经 DI 获取 Page]
  → page.ui_list() → list()                       [dbpage/ 业务编排 + 构建查询]
  → general_db.query_model()                      [dbdao/ 经 Deref 委托的 CRUD]
  → user_credits / plat_menu 实体                  [dbentity/ SeaORM 映射]
  → PostgreSQL 数据库
```

### 🧩 三大核心设计模式总结

| 模式 | 位置 | 作用 |
|------|------|------|
| **serde 辅助序列化** | `mod.rs` 的 `serde_i64_string` | i64 转字符串,解决前端精度丢失 |
| **Deref/DerefMut 委托** | DAO、Page、各类 Ext | 组合内嵌对象并透明继承其能力,零胶水代码 |
| **#[ctor] 自动注入(DI)** | 各 `*_init.rs` | 启动时自动注册 Bean,业务通过 `find_bean_*()` 获取 |

模块组织遵循统一的**四件套生成式骨架**:`xx.rs`(实现)+ `xx_init.rs`(DI 注册)+ `xx_test.rs`(`#[cfg(test)]`)+ `mod.rs`(导出),从而让"数据库表 → 实体 → DAO → Page → Ctl(HTTP)"的全链路低代码化。

Logo

一站式 AI 云服务平台

更多推荐