ruwebframe应用之一:`domain` 目录深度讲解
·
## `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)"的全链路低代码化。

更多推荐




所有评论(0)