news 2026/10/2 12:54:01

ruwebframe应用之一:`domain` 目录深度讲解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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)"的全链路低代码化。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 12:53:48

产品立项书核心要素与实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 12:52:45

Redis 接入 AI:从缓存中间件到 AI 数据底座的工程实践

Redis 和 AI 这两个词放在一起&#xff0c;很多人第一反应是"Redis 不是做缓存的吗&#xff0c;跟 AI 有什么关系"。我一开始也是这个反应。但仔细想想&#xff0c;这两年但凡做过一点 AI 应用后端的人都会发现&#xff0c;真正卡脖子的往往不是模型本身&#xff0c;…

作者头像 李华
网站建设 2026/10/2 12:50:51

Yarn安装卡死Building fresh packages?三步定位+六种解法全解

接手一个老项目&#xff0c;习惯性敲下yarn install后就去倒水&#xff0c;回来一看&#xff0c;终端还停在Building fresh packages...那一行&#xff0c;光标在闪&#xff0c;进度条纹丝不动。这种"装了等于没装、等了等于白等"的体验&#xff0c;我过去几年里前前…

作者头像 李华
网站建设 2026/10/2 12:50:29

Faiss向量检索性能调优与评估实践:从原理到参数配置

做过向量检索的朋友&#xff0c;一定绕不开Faiss&#xff08;Facebook AI Similarity Search&#xff09;这个名字。我之前在公司内部搭建过一套基于Faiss封装的服务&#xff0c;也就是后来我们内部叫“Easy-VectorDB”的轻量级向量数据库&#xff0c;专门用来处理商品特征向量…

作者头像 李华
网站建设 2026/10/2 12:49:58

通达信短线突击

短线:(CLOSE-MA(CLOSE,13))/MA(CLOSE,13)*20,LINETHICK0,COLORWHITE; 中线:(CLOSE-MA(CLOSE,4))/MA(CLOSE,4)*20,LINETHICK0,COLORYELLOW; VAR200:(CLOSE-LLV(LOW,20))/(HHV(HIGH,20)-LLV(LOW,20))*100; VAR300:SMA(SMA(VAR200,3,1),3,1)/28.57; VAR400:EMA(VAR300,5); 操盘:3*…

作者头像 李华