Compare commits

...

79 Commits

Author SHA1 Message Date
oyo
e6be173673 更新 knowledge.md 2025-10-29 16:35:27 +08:00
oyo
510309832f 更新 knowledge.md 2025-10-29 16:16:04 +08:00
oyo
d15bb5e10f 更新 knowledge.md 2025-10-29 16:14:55 +08:00
oyo
06f315a654 更新 knowledge.md 2025-10-29 16:01:30 +08:00
oyo
245551961d 更新 knowledge.md 2025-10-29 15:51:16 +08:00
oyo
636ae4d415 更新 knowledge.md 2025-10-29 15:40:11 +08:00
oyo
3a2f856eff 更新 knowledge.md 2025-10-29 15:29:49 +08:00
oyo
eb5d055e14 更新 knowledge.md 2025-10-29 15:26:22 +08:00
oyo
5771ffe388 更新 knowledge.md 2025-10-29 15:15:09 +08:00
oyo
d59e59c17c 更新 knowledge.md 2025-10-29 13:54:23 +08:00
oyo
4b2bba2ae9 更新 knowledge.md 2025-10-27 17:01:19 +08:00
oyo
bebfcfa3f1 更新 knowledge.md 2025-10-27 15:20:12 +08:00
oyo
41489ba150 更新 knowledge.md 2025-10-27 15:16:49 +08:00
oyo
6c8032e25c 更新 knowledge.md 2025-10-27 15:03:37 +08:00
oyo
9edce3e80c 更新 knowledge.md 2025-10-27 14:58:00 +08:00
oyo
29570e7156 更新 knowledge.md 2025-10-27 11:04:22 +08:00
oyo
4be514aff0 更新 knowledge.md 2025-10-23 14:11:54 +08:00
oyo
bad7025b00 更新 knowledge.md 2025-10-22 19:27:53 +08:00
oyo
a3947239ad 更新 knowledge.md 2025-10-22 19:15:24 +08:00
oyo
f1ec30a7dd 更新 knowledge.md 2025-10-22 18:46:01 +08:00
oyo
3699f4833c 更新 knowledge.md 2025-10-22 17:21:58 +08:00
oyo
8ecdfbab6e 更新 knowledge.md
去掉 "可以作为静态自定义字段逻辑编写位置(复用性好,被其他convert方法调用,但处于底层,不建议在内部进行数据库查询等操作,只建议做静态计算)"
2025-10-22 15:44:56 +08:00
oyo
57c4fcc6bc 更新 knowledge.md
放开自定义字段的实现位置
2025-10-22 15:03:30 +08:00
oyo
a5e38099c6 更新 knowledge.md 2025-10-21 15:33:53 +08:00
oyo
854275fbda 更新 knowledge.md 2025-10-21 15:26:08 +08:00
oyo
2794f1e10e 更新 knowledge.md 2025-10-20 19:39:06 +08:00
oyo
50898d781e 更新 knowledge.md 2025-10-20 19:36:55 +08:00
oyo
19efc38e5a 更新 knowledge.md 2025-10-20 19:29:36 +08:00
oyo
9bc4cbf799 更新 knowledge.md 2025-10-20 19:29:27 +08:00
ycl
49a8165aff 更新 knowledge.md 2025-10-20 19:14:11 +08:00
oyo
ba9318c2ba 更新 knowledge.md 2025-10-20 14:01:45 +08:00
oyo
c50bb46f20 更新 knowledge.md 2025-10-19 17:57:24 +08:00
ycl
9042527142 更新 knowledge.md 2025-10-15 18:43:49 +08:00
ycl
1bf734f700 更新 knowledge.md 2025-10-15 18:20:07 +08:00
ycl
49ca7a2e72 更新 knowledge.md 2025-10-15 11:30:32 +08:00
ycl
44f7ffda1d 更新 knowledge.md 2025-10-15 11:22:54 +08:00
ycl
ec56219b83 更新 knowledge.md 2025-10-15 10:53:12 +08:00
ycl
4f3ca7936b 更新 knowledge.md 2025-10-14 11:21:48 +08:00
oyo
44cc048899 更新 knowledge.md 2025-10-13 11:13:35 +08:00
oyo
3c00525d67 更新 knowledge.md 2025-09-29 15:14:46 +08:00
ycl
e44351a362 更新 knowledge.md 2025-09-25 15:53:00 +08:00
ycl
51c2b14694 更新 knowledge.md 2025-09-25 15:07:49 +08:00
ycl
7eac1bbeb6 更新 knowledge.md 2025-09-25 15:05:19 +08:00
oyo
546e45aa97 更新 knowledge.md 2025-09-25 10:04:26 +08:00
oyo
7da8e3eb34 更新 knowledge.md 2025-09-23 10:59:37 +08:00
oyo
2384649928 更新 knowledge.md 2025-09-22 10:41:21 +08:00
oyo
2e2cb599cb 更新 knowledge.md 2025-09-19 18:10:12 +08:00
oyo
00cf12ea3c 删除 knowledge-design.md 2025-09-19 18:06:42 +08:00
oyo
b870568483 更新 knowledge.md 2025-09-18 11:52:37 +08:00
ycl
ce7c0ad997 更新 knowledge.md 2025-09-18 10:11:42 +08:00
oyo
7e07894422 更新 knowledge-brief.md 2025-09-12 17:04:24 +08:00
oyo
c3aaa9d2c0 更新 knowledge-brief.md 2025-09-12 16:26:00 +08:00
oyo
73c82a4f97 更新 knowledge.md 2025-09-10 15:37:08 +08:00
ycl
dcff8d4701 更新 knowledge-brief.md 2025-09-07 15:51:18 +08:00
ycl
040a1999e3 更新 knowledge.md 2025-09-07 14:33:01 +08:00
ycl
636cb8a226 更新 knowledge.md 2025-09-07 14:30:26 +08:00
ycl
b8255ca3a0 更新 knowledge.md 2025-09-04 16:43:02 +08:00
ycl
e839e1e8a0 更新 knowledge.md 2025-09-04 11:56:02 +08:00
ycl
5d89a2907b 更新 knowledge.md 2025-09-04 11:53:34 +08:00
ycl
db667aefda 更新 knowledge.md 2025-09-04 11:23:07 +08:00
ycl
25b04583e8 更新 knowledge.md 2025-09-04 11:19:26 +08:00
ycl
506b07d1ac 更新 knowledge-brief.md 2025-09-03 19:18:13 +08:00
ycl
555f1767a9 更新 knowledge.md 2025-09-03 19:16:54 +08:00
ycl
f8d5a68fbc 更新 knowledge.md 2025-09-03 19:02:14 +08:00
oyo
8dd43e784f 更新 knowledge.md 2025-09-03 18:50:12 +08:00
oyo
eb2bb0b62f 更新 knowledge.md 2025-09-03 10:07:07 +08:00
oyo
f945ec49bb 更新 knowledge.md 2025-09-01 20:09:51 +08:00
oyo
6026fa7774 更新 knowledge.md 2025-09-01 17:03:59 +08:00
oyo
b049e69454 更新 knowledge.md 2025-08-31 16:02:26 +08:00
oyo
e9a6bc4d7e 更新 knowledge-design.md 2025-08-28 15:22:43 +08:00
oyo
b54b3149a2 更新 knowledge.md 2025-08-27 15:57:52 +08:00
ycl
e448e3a9e1 更新 knowledge.md 2025-08-25 16:07:01 +08:00
dayjoy
f86f1b74e4 feat: 知识库前缀 2025-08-21 11:11:27 +08:00
oyo
23706f21b2 更新 knowledge.md 2025-08-21 10:18:10 +08:00
oyo
b4569dffb0 添加 knowledge-design.md 2025-08-20 13:57:42 +08:00
ycl
f2764cefc9 更新 knowledge.md 2025-08-20 11:57:23 +08:00
dayjoy
e4225795f9 fix: 项目结构 2025-08-20 11:46:16 +08:00
dayjoy
73803542ea fix: 代码格式问题 2025-08-20 11:37:57 +08:00
dayjoy
a3afd54f97 feat: 简化文字 2025-08-20 11:25:21 +08:00
2 changed files with 663 additions and 305 deletions

View File

@@ -15,6 +15,15 @@ TOCO采用严格的分层架构
技术栈Java + SpringBoot + MyBatis-plus+ Hibernate
##平台能力边界说明:
TOCO的设计模型旨在规范系统结构、统一数据契约与接口形态自动生成约80%的基础代码框架如控制器签名、DTO转换、读写方案执行器等
然而,业务行为逻辑(如条件判断、动态路由、状态流转、异常处理、多源聚合、性能优化路径等)无法由设计自动推导或生成,必须由人工编码实现。
因此:
设计元素仅表达“系统能做什么”(接口定义、数据结构、依赖关系);
**代码实现才是“系统正在做什么”**的唯一真实来源;
设计与代码之间存在不可逾越的行为鸿沟——设计不记录实现细节,代码不反馈回设计;
任何对系统行为的分析、影响评估或变更决策,都必须同时考察结构层面的设计信息与行为层面的代码实现,二者缺一不可。
## 重要设计元素详解
### 1. 模块Module
@@ -124,6 +133,7 @@ TOCO采用严格的分层架构
- 手动创建自定义RPC
- **参数限制**只能为QTO、BTO、Enum、基本类型
- **返回值限制**只能为DTO、Enum、基本类型
- **存储特点**: TOCO只存储RPC的方法签名不存储执行逻辑。了解API内部具体实现需阅读对应代码
### 14. 应用程序接口API
- **作用**定义对外暴露的HTTP接口
@@ -131,6 +141,7 @@ TOCO采用严格的分层架构
- **参数限制**只能为QTO、BTO、EO、Enum、基本类型
- **返回值限制**只能为VO、Enum、基本类型
- **分页处理**框架自动包装返回值code、message、data
- **存储特点**: TOCO只存储API的URI和方法签名不存储执行逻辑。了解API内部具体实现需阅读对应代码
### 15. 流程服务FunctionFlow
- **使用场景**
@@ -143,6 +154,12 @@ TOCO采用严格的分层架构
- 开始节点:流程起点
- **设计原则**:以数据内聚为首要考虑,每个节点围绕核心写服务
16. 领域消息 DomainMessage
可以监听聚合对象实体的创建、删除、更新事件;通过事件驱动的方式实现异步解耦;也是一种跨模块通信的方式;消息驱动的一种实现;
17. 普通消息 (CommonMessage)
自定义字段,自持事务、延时特性;是对领域消息的补充
## 数据获取流程
### DTO获取流程

View File

@@ -1,6 +1,8 @@
> 这是一份完整的TOCO平台知识库文档请先仔细阅读并理解其中的概念、规则和最佳实践。在后续的对话中我将为你提供具体的角色定位和任务目标请基于这份知识库来完成相关的设计和开发工作。
<TOCO知识库>
-----------------------------------------------------------------------------
### **1. TOCO 平台概览:**
### 1. TOCO 平台概览:
- **1.1 平台简介:** TOCO是软件设计和代码自动生成平台。基于DDD、分层架构、CQRS理论覆盖数据库到API的完整开发链路提升开发效率和代码质量
- **1.2 核心价值/目标用户:** 提供软件设计能力,设计成果直接生成标准代码,提高编码一致性和效率
- **1.3 主要特性概览:** 可视化设计、模型关联、多人协作、代码生成器
@@ -40,12 +42,12 @@
* **命名规则:** 类名以Enum结尾
* **类路径:** 位于 <code>**.common.enums</code> 包下
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则: ${Enum的uuid}|ENUM|DEFINITION
- **生成代码:** 在common层生成Enum文件如StatusEnum
- **修改建议:** 不建议修改
* **生成代码:** 在common层生成Enum文件如StatusEnum
* **禁止** 修改该类
#### **2.3 值对象 (EO)**
- **定义与用途:** EO是可复用的POJO数据结构可跨模块使用可作为实体字段类型。
- **关键属性/配置:** 名称以_eo结尾全局唯一。字段类型限制基本类型、List、EO、Enum。
- **关键属性/配置:** 名称以_eo结尾全局唯一。字段类型限制基本类型、List、EO、Enum。如果一个字段对应了某个Enum则应该尽量使用该Enum作为字段类型
- **与其他元素关系:** 可作为Entity、Dto、Vo、Bto、Qto的字段类型支持EO嵌套。
- **EO设计元素的表达**
- 使用Json格式表达Schema定义如下
@@ -81,7 +83,7 @@
* **命名规则:** 类名以Eo结尾
* **类路径:** 位于 `**.persist.eo` 包下
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则${Eo的uuid}|EO|DEFINITION
- **修改建议:** 不建议修改
* **禁止** 修改该类
#### **2.4 实体关系 (ER / Entity)**
- **定义与用途:** 实体对应数据库表,关系为实体间的外键依赖
@@ -93,6 +95,7 @@
* **职责:** 生成Mybatis-plus结构定义类文件
* **类路径:** 位于 `**.persist.dos` 包下
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则: ${Entity的uuid}|ENTITY|DEFINITION
* **禁止** 修改该类
- Mapper
* **生成产物**persist层的Mybatis-plus Mapper类
* **职责:** 为Mybatis-plus框架提供Mapper
@@ -127,14 +130,15 @@
* **命名规则**${entityName}BO
* **类路径:** `**.manager.bo`包下
* **唯一标识符位置:** 类注解@AutoGenerated中uuid规则: ${Entity的uuid}|BO|DEFINITION
- **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。校验方法由框架自动调用,不需要业务代码显式调用
* **聚合校验:** BO中包含一个validateAggregate(聚合根BO)或valid(非聚合根BO)方法用来编写用于验证业务不变性相关的聚合校验代码。在通过聚合对象操作数据库时TOCO会在框架自动生成对validateAggregate方法的调用所以禁止在业务代码显式调用以免造成重复校验
* **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。校验方法由框架自动调用不需要业务代码显式调用。
- BaseBO
* **生成产物**存在子BO时生成封装不变代码部分
* **职责:** 定义聚合对象,组合成层级结构实现充血模型,支持写链路数据变更、监听变更、数据校验
* **命名规则**${entityName}BO
* **类路径:** `**.manager.bo`包下
* **唯一标识符位置:** 类注解@AutoGenerated中uuid规则: ${Entity的uuid}|BO|DEFINITION
- **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。
* **禁止** 修改该类
#### **2.6 数据传输对象 (DTO)**
- **定义与用途:** DTO基于某个Entity构建通过外键关系关联多个Entity的数据结构。DTO隐含数据组装逻辑符合外键关系。分为BaseDTO和普通DTOBaseDTO直接派生自Entity包含Entity所有字段每个Entity只有一个BaseDTO普通DTO基于BaseDTO创建包含BaseDTO所有字段可增加扩展字段或自定义字段。DTO不能作为接口参数不能作为HTTP API返回值。
@@ -256,7 +260,7 @@ DateTime endTime; //结束时间
meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room_with_meetings_dto为已存在DTO带uuid。
- **预定义方法:** 每个DTO基于根Entity的唯一索引自动生成预定义RPC方法及实现预定义方法获取根Entity数据通过RPC自动获取所有扩展字段数据并拼装。如实体user有唯一索引username则为UserDto生成UserDto UserDtoService.getByUserName(String userName)和List<UserDto> UserDtoService.getByUserNames(List<String> userNames)。预定义方法内部基于外键关系自动生成复杂DTO数据的递归、Join拼装能力直接返回DTO内部所有继承字段和扩展字段数据。自定义字段数据获取不自动生成在对应convert方法中编写代码
- **预定义方法:** 每个DTO基于根Entity的唯一索引自动生成预定义RPC方法及实现预定义方法获取根Entity数据通过RPC自动获取所有扩展字段数据并拼装。如实体user有唯一索引username则为UserDto生成UserDto UserDtoService.getByUserName(String userName)和List<UserDto> UserDtoService.getByUserNames(List<String> userNames)。预定义方法内部基于外键关系自动生成复杂DTO数据的递归、Join拼装能力直接返回DTO内部所有继承字段和扩展字段数据。自定义字段数据获取不自动生成要在TOCO生成代码基础上编写额外代码实现实现时要注意代码的复用性以及避免N+1问题
- **公开性:** DTO可设置公开性DTO为公开时生成的预定义RPC方法也为公开RPC可被其他模块订阅调用DTO为非公开时生成的预定义RPC方法也为非公开RPC其他模块不可见。
- **跨模块依赖:** 如DTO内引用其他模块DTO需订阅其他模块RPC方法getBy${PrimaryKey}、getBy${PrimaryKey}s、getBy${foreignKey}等用于获取对应DTO。
- **复杂嵌套DTO获取流程:** 获取DTO有2种方式都可直接获取复杂嵌套DTO数据
@@ -277,6 +281,7 @@ meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room
* **禁止** 修改该类
* **类路径:** `**.manager.dto` 包路径下
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则${DTO在TOCO中的uuid}|DTO|DEFINITION
* **禁止** 修改该类
- **Manager**
* **生成产物:** Java接口及实现类
* **命名规则:** 接口类名以Manager结尾、实现类名以ManagerImpl结尾(${DtoName}Manager)、基类名以ManagerBaseImpl结尾(${DtoName}ManagerImpl)
@@ -296,13 +301,14 @@ meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room
- **例子:**
* 如UserDto、UserDtoManager、UserDtoConverter extends UserDtoBaseConverter、UserDtoService名称为${DtoName}Service内部包含getBy${PrimaryKey}、getBy${PrimaryKey}s等方法。如Dto为UserBaseDto则生成类名为UserBaseDtoService
- **修改建议:**
- 建议在Service与BaseConverter中扩展代码不建议修改结构定义文件和Manager文件。DTO的自定义字段不直接派生自Entity一般对应取数逻辑代码。涉及数据获取、计算和拼装批量处理性能最好代码位置必须放在BaseConverter中已自动生成的列表转换方法中批量取数组装如UserBaseDtoBaseConverter.convertUserToUserBaseDto(List<User>)或UserDtoBaseConverter.convertUserBaseDtoToUserDto(List<UserBaseDto>)
- 建议在Service与BaseConverter中扩展代码不建议修改结构定义文件和Manager文件。DTO的自定义字段不直接派生自Entity一般对应取数逻辑代码。涉及数据获取、计算和拼装实现时要注意代码的复用性以及避免N+1问题。
#### **2.7 视图对象 (VO)**
- **定义与用途:** VO基于BaseDTO或指定的其他DTO派生通过外键关系关联多个BaseDTO的数据结构。用于视图层与前端数据传输作为HTTP API返回值或读方案返回值。不能作为接口参数或RPC返回值。
- **关键配置:** 名称以Vo结尾全局唯一、根Entity、派生源、字段列表。字段分三种a.继承DTO字段基础类型/EO/Enum保持类型不变DTO类型转为对应VO类型b.扩展字段正向替换和反向注入类型为VO或List<VO>c.自定义字段基本类型或VO类型。可裁剪无用字段可通过外键关系扩展其他BaseDto。无法扩展时可增加自定义字段。
- **关键配置:** 名称以Vo结尾全局唯一、根Entity、所属模块、派生源、字段列表。字段分三种a.继承DTO字段基础类型/EO/Enum保持类型不变DTO类型转为对应VO类型b.扩展字段正向替换和反向注入类型为VO或List<VO>c.自定义字段基本类型或VO类型。可裁剪无用字段可通过外键关系扩展其他BaseDto。无法扩展时可增加自定义字段。
- **继承字段类型转换:** VO只与派生源VO有转换关系。当VO派生自某DTO且继承了派生源DTO中的DTO类型字段假设DTO-A则VO中对应字段类型必须为VO假设VO-A且**VO-A必须派生自DTO-A**,以维持转换关系。
- **与DTO的区别:** DTO用于服务层传输通常作RPC返回值与数据模型更近复用性强VO用于视图层传输通常作API返回值与UI展示更近可裁剪冗余字段复用性弱。
- **VO所属模块:** VO无法被跨模块引用模块A内部的API只能使用模块A内部的VO。VO可以与其派生源DTO不在同一模块meeting模块的API需要返回meeting_room_dto派生出的VO则需要在meeting模块创建派生自meeting_room_dto的VO以便被meeting模块的API使用注意创建VO时传入的moduleName字段,用于指定VO所在的模块。
- **创建方式:** VO通常基于某个BaseDTO及外键关系派生也可直接创建与DTO无关的全自定义字段VO尽量少用仅用于特殊页面组装无关数据的场景
- **根VO和子VO:** VO分两种1.根VO最外层VO结构需通过TOCO创建有uuid唯一标识可被其他根VO或子VO引用2.子VO根VO内部嵌套VO通过外键关系关联BaseDTO后自动创建仅附属于一个根VO无uuid。需描述VO字段和扩展关系通过**创建根VO**使TOCO**自动创建**子VO或引用其他根VO。
- **字段扩展方式:** 同DTO字段扩展方式通过任意**一个**派生源BaseDTO经外键扩展成复杂嵌套VO。满足条件即可扩展正向替换当前实体有指向其他实体的外键字段反向注入其他实体有指向当前实体的外键字段
@@ -349,7 +355,7 @@ meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room
}
}
```
- **JSON结构描述:** VO用json结构表示用于理解含义或作为创建、更新VO工具参数。字段含义expandList为正向替换reverseExpandList为反向注入extendFieldList为来自派生源DTO的字段customFieldList为自定义字段。expandList中foreignKeyInThisEntity为本表外键字段名voFieldName为替换后字段名reverseExpandList中foreignKeyInOtherEntity为他表外键字段名voFieldName为注入后字段名customFieldList中uuid为自定义字段UUID创建时不填更新时需传入定位typeUuid为类结构UUIDtype为List且innerType=Enum、Eo时包含extendFieldList中name为继承字段名若DTO字段非DTO类型则VO字段类型与DTO一致若DTO字段为DTO或List<DTO>类型由于VO字段不能为DTO**必须**将DTO转换为VO所以extendFieldList会有**vo结构**表示该字段DTO类型派生出的VO定义注意该VO字段中的VO类型必须派生自继承的DTO字段类型
- **JSON结构描述:** VO用json结构表示用于理解含义或作为创建、更新VO工具参数。字段含义expandList为正向替换reverseExpandList为反向注入extendFieldList为来自派生源DTO的字段customFieldList为自定义字段。expandList中foreignKeyInThisEntity为本表外键字段名voFieldName为替换后字段名reverseExpandList中foreignKeyInOtherEntity为他表外键字段名voFieldName为注入后字段名customFieldList中uuid为自定义字段UUID创建时不填更新时需传入定位typeUuid为类结构UUIDtype为List且innerType=Enum、Eo时包含extendFieldList中name为派生源dto中的字段名若DTO字段非DTO类型则VO字段类型与DTO一致若DTO字段为DTO或List<DTO>类型由于VO字段不能为DTO**必须**将DTO转换为VO所以extendFieldList会有**vo结构**表示该字段DTO类型派生出的VO定义注意该VO字段中的VO类型必须派生自继承的DTO字段类型无根VO不包含fromEntity、fromDto、fromDtoUuid字段派生自DTO的VO必包含fromEntity和fromDtoUuid字段
示例系统中存在meeting_detail_dto
```
@@ -380,6 +386,7 @@ meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room
"name": "metting_with_room_vo",
"description": "会议详情,包含会议室信息,以及会议室禁用列表",
"rootVo": "metting_with_room_vo",
"moduleName": "meeting",
"fromEntity": "meeting",
"fromDto": "meeting_detail_dto",
"fromDtoUuid": "cd55c96b-aa67-bfb2-7614-70b503a8f8bf",
@@ -489,8 +496,8 @@ meeting_with_room_dto无uuid为待创建DTO。meeting_base_dto和meeting_room
meeting_with_room_vo为根VO无uuid为待创建根VO。meeting_room_with_meetings_vo和meeting_base_vo为meeting_with_room_vo的子VO无法被其他根VO引用无uuid。注意metting_with_room_vo的extendFieldList中create_user结构含vo因为派生源meeting_detail_dto中create_user字段为user_base_dto类型所以metting_with_room_vo中该继承字段类型需变为由user_base_dto派生出的VO结构注意metting_with_room_vo中create_user对应的user_base_vo**必须派生自**DTO字段的user_base_dto类型。
- **派生源默认使用BaseDTO:** 除非用户指定VO的派生源DTO否则创建VO时只可用**BaseDTO**为派生源。
- **与DTO的转换关系:** 创建**有派生源**的VO后TOCO生成代码时自动生成2种convert方法1.基础convert方法从DTO转换为VO仅转换结构及基本类型字段的get/set方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map其中**Map转换方法**为底层批量方法,通常是自定义字段逻辑编写位置复用性好被其他convert方法调用单个和列表convert方法都通过**调用Map方法**实现2.带数据拼装逻辑的convert方法内部**自动**调用基础convert方法从DTO转换为VO并设置基本类型字段数据然后根据外键**自动**获取**扩展字段**数据拼装最终数据方法命名为convertAndAssembleData、convertAndAssembleDataList即这两个方法已**自动**获取所有**继承字段**和**扩展字段**数据。这2种方法代码生成在VO对应Converter类中。
- **字段数据获取:** 继承自DTO的字段及扩展字段TOCO在convert方法中自动生成数据获取代码无需手动实现获取和拼装逻辑。自定义字段则**必须**在最底层convertTo${VoName}Map方法中实现对应获取和拼装逻辑便于其他convert方法**复用**这段逻辑。**禁止**在其他convert方法中实现自定义字段逻辑会导致某些场景下数据拼装不完整。
- **与DTO的转换关系:** 创建**有派生源**的VO后TOCO生成代码时自动生成2种convert方法1.基础convert方法从DTO转换为VO仅转换结构及基本类型字段的get/set方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map其中**Map转换方法**为底层批量方法单个和列表convert方法都通过**调用Map方法**实现2.带数据拼装逻辑的convert方法内部**自动**调用基础convert方法从DTO转换为VO并设置基本类型字段数据然后根据外键**自动**获取**扩展字段**数据拼装最终数据方法命名为convertAndAssembleData、convertAndAssembleDataList即这两个方法已**自动**获取所有**继承字段**和**扩展字段**数据。这2种方法代码生成在VO对应Converter类中。
- **字段数据获取:** 继承自DTO的字段及扩展字段TOCO在convert方法中自动生成数据获取代码无需手动实现获取和拼装逻辑。
- **跨模块依赖:** VO内存在由其他模块DTO派生出的子VO需订阅其他模块RPC(getBy${PrimaryKey},getBy${PrimaryKey}s,getBy${ForeignKey}s等)方法获取对应DTO再转换为子VO。
- **复杂嵌套VO获取流程:** 获取VO通常有3种方式3种方式都可直接获取复杂嵌套VO数据
- 第1种先通过预定义方法获取VO派生源BaseDTO再通过convertAndAssembleData或convertAndAssembleDataList方法转换成VO
@@ -511,8 +518,9 @@ TOCO中判断使用方式的步骤
* **职责:** 表达VO数据结构
* **类路径:** `**.entrance.web.vo` 包路径下
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则${VO在TOCO中的uuid}|VO|DEFINITION
* **禁止** 修改该类
- **Converter**
* **生成产物:** controller层生成Java类**有派生源**的VO才有Converter和基类
* **生成产物:** controller层生成Java类**有派生源**的VO才有Converter无根VO没有Converter)和基类
* **命名规则:** 实现类名以Converter结尾(${VoName}Converter)基类名以BaseConverter结尾(${VoName}BaseConverter)
* **类路径:** `**.entrance.web.converter` 包路径下
* **职责:** 把DTO转换成VOConverter含2种convert方法1.基础convert方法从DTO转换为VO仅转换结构方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map其中**Map转换方法**为底层批量方法单个和列表convert方法都通过**调用Map方法**实现2.带数据拼装逻辑的convert方法内部调用基础convert方法从DTO转换为VO然后根据外键获取拼装最终数据方法命名为convertAndAssembleData、convertAndAssembleDataList
@@ -522,7 +530,7 @@ TOCO中判断使用方式的步骤
* **例子:**
* 如UserDetailVo、UserDetailVoConverter含convertToUserDetailVo、convertToUserDetailVoList、convertToUserDetailVoMap、convertAndAssembleData、convertAndAssembleDataList方法
- **修改建议:**
- 建议在Converter中扩展代码不建议修改结构定义文件。VO的**自定义字段**不直接派生自DTO一般对应取数逻辑代码。涉及数据获取、计算和拼装时批量处理性能最好,自定义字段对应代码位置**必须**放在Converter的**Map**基础转换方法convertTo${VoName}Map中批量取数组装如UserVoConverter.convertToUserVoMap
- 建议在Converter中扩展代码不建议修改结构定义文件。VO的**自定义字段**不直接派生自DTO一般对应取数逻辑代码。涉及数据获取、计算和拼装时要注意代码的复用性以及避免N+1问题。
#### **2.8 查询对象(WO)**
- **定义与用途:** WO基于Entity通过外键关系关联多个Entity的数据结构。WO表达数据取数拼装逻辑符合外键关系。WO作为ReadPlan查询上下文创建ReadPlan前需先创建WO对象。理解ReadPlan语义时需要WO作为上下文。
@@ -575,11 +583,12 @@ class MeetingDto {
```
- **创建/生成方式:**
- **创建思路** 按查询返回的DTO|VO结构构建同构WO对象扩展和反向扩展字段名保持一致根据查询需求和过滤需求对WO二次裁剪或扩展
- **创建思路** 按查询返回的DTO或从DTO派生出的VO结构构建同构WO对象扩展和反向扩展字段名保持一致根据查询需求和过滤需求对WO二次裁剪或扩展
- 去掉过滤和查询都不需要的扩展
- 补全查询或字段过滤需要的扩展
- **关键配置:** WO字段分三种a.继承Entity字段类型与Entity相同b.扩展字段含正向替换和反向注入字段类型为WO或List<WO>
- **字段扩展方式:** WO通过外键关系替换/注入对应Entity信息对象化表达有外键关系的Entity信息。存在外键关系且满足条件即可扩展a.正向替换当前实体有指向其他实体的外键字段b.反向注入:其他实体有指向当前实体的外键字段。
- 禁止从无根VO创建WO
例如两个Entity
```java
@@ -694,11 +703,14 @@ meeting_with_room_and_agenda_wo示例
- **自定义排序**:指定排序字段(需入参指定排序字段),满足列表头动态排序需求
- **排序字段来源**只能来源自根WO(不包括列表WO属性)以及根WO扩展出的非列表属性的WO字段。排序字段是从根节点到当前属性的路径例如ADto包含BDto bDtoBDto有name属性排序字段路径为bDto.name
- 提取需求中的查询信息,以输入的查询对象作为上下文,构建查询语句
- 如有列表属性过滤需求:提取需求中的过滤信息,针对可过滤字段(列表属性)分别创建过滤条件。过滤条件不能使用列表属性作为查询条件属性(不能使用contains语法)。属性字段是从根节点属性到当前属性的路径
- 如返回数据有列表属性过滤需求filter:提取需求中的过滤信息,针对可过滤字段(列表属性)分别创建过滤条件。过滤条件不能使用列表属性作为查询条件属性(不能使用contains语法)。属性字段是从根节点属性到当前属性的路径
- **查询语法和过滤语法**
- **核心规则**:所有一元和二元操作符的左侧必须是字段或对象属性
- 字段来自WO定义中的属性包括关联对象的字段
- 变量:以#开头的变量,由调用方传入
- 基本语法格式:属性名 操作符 变量或常量,变量格式为#变量名;枚举类型常量用单引号包围,例如:'MALE'
- 数值、时间类型属性操作符:!=, ==, >, <, <=, >=, in, notIn, isNullOrNot
- 文本类型属性操作符like, isNotNull, isNull, !=, ==, in, notIn, isNullOrNot
- 数值、时间类型属性操作符(左侧必须是字段,不能是#变量)!=, ==, >, <, <=, >=, in, notIn, isNullOrNot
- 文本类型属性操作符(左侧必须是字段,不能是#变量)like, isNotNull, isNull, !=, ==, in, notIn, isNullOrNot
- isNullOrNot是三元操作符根据入参判断过滤某值是否为null。例如gender isNullOrNot #genderIsNullOrNot若入参为true等价于where gender is null若为false等价于where gender is not null
- 对象属性或列表对象属性操作符isNull, isNotNull, isNullOrNot。本对象不能直接用此操作符
- 对象属性可对其子属性进行上述查询
@@ -706,8 +718,9 @@ meeting_with_room_and_agenda_wo示例
- 查询条件间可用AND, OR连接
- 可插入括号()对条件分组
- 列表对象属性只能使用contains、isNull、isNotNull操作符wo列表类型可用contains(子查询)表示列表属性中需包含至少一个满足子查询条件的对象其他列表类型只能用isNull或isNotNull
- TOCO框架自带动态能力具体原理如下查询条件中的变量可在运行时传入或不传入值不传入值表示该变量相关条件不起作用基于这种动态效果多条件联合查询可优先使用AND。特别注意这个动态能力是由自动完成的当变量不传入值直接自动生效绝对禁止再为动态性编写特定的判空语句!
- 通过and, or, not连接符拼装子查询完成查询。不要使用未提及的操作符号、连接符
- 查询条件中的入参可在运行时传入或不传入值不传入值表示该参数相关条件不起作用基于这种动态效果多条件联合查询可优先使用AND
- TOCO读方案查询语法和代码有本质区别不支持对变量的值进行判断如#nameLike isNull 或 #name == 'ABC' 或#name isNullOrNot false都属于非法语句
- 使用点号(.)访问当前对象的单值对象类型子属性,可多个点号组合访问嵌套单值对象属性
- 查询条件中的属性必须是当前查询对象的属性或单值对象属性或单值对象子属性
- 禁止使用filter语法
@@ -785,6 +798,53 @@ meeting_with_room_and_agenda_wo示例
}
@detectDelim
</code>
- **关键概念:变量(Variable)与字段(Field)的本质区别**
【变量(#param)】:
- 来源API 调用方传入的请求参数
- 性质:运行时的**入参值**,在查询执行前已经确定
- 在SQL中的角色参数值通过 PreparedStatement 绑定到 ? 占位符
- 能做什么:只能作为 WHERE 条件的**右侧值**,参与比较运算
- 能否判空:❌ 在 SQL 中不能判空,因为参数的有无应由调用层控制
【字段(field name)】:
- 来源:数据库表中的实际列
- 性质:存储在数据库中的**数据值**
- 在SQL中的角色被查询和过滤的目标
- 能做什么:可以进行各种操作,包括判空检查
- 能否判空:✅ 可以,因为我们需要查询字段是否为 NULL
【类比】:
变量就像"函数的参数",字段就像"函数体内的变量"
你不会在函数签名处检查参数是否为空(那是调用者的职责)
但你会在函数体内检查本地变量是否为空(那是函数的职责)
- **操作符使用的核心规范,必须遵循**
读方案查询条件遵循严格的操作符规范。以下是完整的操作符使用白名单:
- **一元操作符(仅作用于字段,绝对禁止作用于变量):**
- isNull判断字段值是否为null
用法field isNull
示例meeting_title isNull
- isNotNull判断字段值是否不为null
用法field isNotNull
示例creator_id isNotNull
- **二元操作符(字段 操作符 值/变量):**
- 比较类:==, !=, >, <, >=, <=
用法field == #param 或 field == 'constant'
示例meeting_date == #meetingDate
- 集合类in, notIn
用法field in #paramList 或 field notIn #presetConstants
示例status in #statusList
- 文本类like
用法field like #keyword
示例title like #searchKeyword
- **三元操作符仅isNullOrNot**
- isNullOrNot根据布尔变量判断字段值是否为null
用法field isNullOrNot #booleanParam
语义:如果#booleanParam为true等价于 field is null
如果#booleanParam为false等价于 field is not null
示例gender isNullOrNot #filterNullGender
- **禁止的错误用法(黑名单):**
- 错误1: #param isNull / isNotNull / isNullOrNot变量不能是操作符左侧
- 错误2: (field == #param OR #param isNull)(不能对变量使用判空操作符)
- 错误3: #param == 'constant'(变量只能作为右侧值)
- 错误4: #paramA in #paramB变量不能作为操作符左侧
- **读方案设计元素的表达**
- 以json格式表达json schema 定义如下
```json
@@ -900,6 +960,9 @@ meeting_with_room_and_agenda_wo示例
]
}
</code>
- **读方案设计元素表达关键原则,必须遵循**
- 读方案至少需要选择一种返回方式请设置supportPagination为true或者supportWaterfall为true或者supportQueryAll或者supportCount为true
- 如果读方案的supportPagination为true或者supportWaterfall为true则必须至少设置一个默认排序字段
- **代码产物和修改建议**
- **QTO查询参数对象**
* **生成产物:** Service层生成Java类
@@ -908,6 +971,7 @@ meeting_with_room_and_agenda_wo示例
* **职责:** 读方案的查询参数结构可作为API参数直接透传给RPC调用
* **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则: ${ReadPlan的uuid}|QTO|DEFINITION
* **内部结构:** QTO包含分页相关字段:
* **禁止** 修改该类
<code>
private String scrollId; //瀑布流游标
private Integer size; //每页记录数
@@ -920,7 +984,11 @@ meeting_with_room_and_agenda_wo示例
- **职责:** 读方案对应的数据库查询方法
- **唯一标识符位置:** 类注解@AutoGenerated中指定uuid规则: ${ReadPlan的uuid}|QTO|DAO
- **QueryExecutor**
- **生成产物** 返回**VO**的查询方案在controller层生成Java类包含独立函数:
- **生成规则**
- 只有当读方案明确指定返回VO时才生成QueryExecutor
- QueryExecutor的命名规则${VoName}QueryExecutor
- 如果读方案返回DTO则不生成QueryExecutor需要通过DtoQueryService + VoConverter组合实现
- **生成产物** 返回**VO**的查询方案在entrance层生成Java类包含独立函数:
- 分页查询: **Paged函数返回VSQueryResult<XxxVo>
- 不分页全量: query**函数返回List<XxxVo>
- 瀑布流: **Waterfall函数返回VSQueryResult<XxxVo>
@@ -929,6 +997,7 @@ meeting_with_room_and_agenda_wo示例
- **类路径:** `**.entrance.web.query.executor`包下
- **职责:** 提供VO查询入口将QtoService返回的id数据转化为目标**VO**
- **QueryService**
- **生成条件** 如果一个读方案返回的是DTO结构则会生成QueryService
- **生成产物** 返回**DTO**的查询方案在service层生成Java类包含独立函数:
- 分页查询: **Paged函数返回VSQueryResult<XxxDto>
- 不分页全量: query**函数返回List<XxxDto>
@@ -968,6 +1037,7 @@ meeting_with_room_and_agenda_wo示例
- **如何创建/生成:** 创建读方案后TOCO自动生成对应的QTO。无需单独创建
- **关键配置:** 名称(${ReadPlanNameQto}驼峰命名查询字段列表如idIsnameLikeschoolNameLike等
- **与API的关系:** QTO可作为API参数。API接收参数后直接透传给内部RPC调用。QTO只用于读操作参数**禁止用于写参数结构**
- **禁止** 修改该类
#### **2.11 写方案 (WritePlan单聚合操作)**
- **定义与用途:** 写方案是数据库写操作的唯一方式每个写方案只能变更一个聚合的数据。写方案可以一次操作聚合内的多张表。例如location(父对象/聚合根)和storey(子对象)是1:N关系可创建写方案同时更新location信息和操作storey列表(新增/删除/修改)也可创建写方案单独更新单个storey对象。
@@ -1005,44 +1075,120 @@ meeting_with_room_and_agenda_wo示例
- 父对象CREATE_ON_DUPLICATE_UPDATE子对象只能CREATE/CREATE_ON_DUPLICATE_UPDATE/FULL_MERGE/PARTIAL_MERGE
- 父对象FULL_MERGE子对象只能FULL_MERGE/PARTIAL_MERGE/CREATE
- 增量更新字段Long/BigDecimal/Float/Integer类型字段可设为增量更新设置在incrFields中配合UPDATE/CREATE_ON_DUPLICATE_UPDATE/PARTIAL_MERGE/FULL_MERGE使用。例如A实体count字段设为增量更新效果是A.count = A.count + ?,传入负值可减值
- 字段选择:
- 按需选择字段
- 当CREATE操作的时候如果主键不又入参指定不勾选主键字段主键字段的值由框架分配如果勾选了主键但是不传递参数值会造成运行时异常
- 自定义字段除了来自实体的字段外还可以添加自定义字段对生成物Bto对象进行扩展
- **写方案设计元素表达:**
```json
{
"type": "object",
"description": "写方案定义",
"properties": {
"uuid":{"type":"string","description":"写方案唯一标识,创建时不传,更新时必传"},
"name":{"type":"string","description":"写方案名称,英文+下划线32字符内"},
"description":{"type":"string","description":"写方案描述256字符内"},
"bo": {"type":"string","description":"聚合根名称"},
"uuid": {
"type": "string",
"description": "写方案唯一标识,创建时不传,更新时必传"
},
"name": {
"type": "string",
"description": "写方案名称,英文+下划线32字符内"
},
"description": {
"type": "string",
"description": "写方案描述256字符内"
},
"bo": {
"type": "string",
"description": "聚合根名称"
},
"operations": {
"type":"array","description":"实体操作定义,包括操作类型和字段",
"type": "array",
"description": "实体操作定义,包括操作类型和字段",
"items": {
"type": "object", "description":"具体实体操作定义",
"type": "object",
"description": "具体实体操作定义",
"properties": {
"bo":{"type":"string","description":"操作的实体"},
"action":{"type":"string","description":"实体操作类型"},
"bo": {
"type": "string",
"description": "操作的实体"
},
"action": {
"type": "string",
"description": "实体操作类型"
},
"uniqueKey": {
"type": "array",
"description": "唯一键字段列表,用于确定数据记录",
"items":{"type":"string","description":"字段名称"}
"items": {
"type": "string",
"description": "字段名称"
}
},
"fields": {
"type": "array",
"items":{"type":"string","description":"字段名称必须来自bo对象"},
"items": {
"type": "string",
"description": "字段名称必须来自bo对象"
},
"description": "操作字段列表"
},
"incrFields": {
"type": "array",
"items":{ "type":"string", "description":"增量字段名称"},
"items": {
"type": "string",
"description": "增量字段名称"
},
"description": "增量操作字段列表"
},
"required":["bo","action","fields"]
"customField": {
"type": "array",
"items": {
"type": "object",
"description": "自定义字段名称",
"properties": {
"name": {
"type": "string",
"description": "字段名称英文下划线分割不超过32字符"
},
"uuid": {
"type": "string",
"description": "字段类型为Enum或Eo时必填对应的Enum或Eo标识符"
},
"type": {
"type": "string",
"description": "字段类型String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal,List"
},
"innerUuid": {
"type": "string",
"description": "当innerType为Eo或Enum时对应的标识符"
},
"innerType": {
"type": "string",
"description": "List元素类型String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal"
}
},
"required": [
"name",
"type"
]
},
"description": "自定义字段列表"
},
"required": [
"bo",
"action",
"fields"
]
}
}
}
},
"required":["name","description","operations","bo"]
"required": [
"name",
"description",
"operations",
"bo"
]
}
```
- **示例:**
@@ -1123,54 +1269,48 @@ meeting_with_room_and_agenda_wo示例
- **示例:**
- 创建用户及设置的写方案create_user_and_setting生成CreateUserAndSettingBto在UserBOService中生成函数createUserAndSetting该函数调用BaseUserBOService中生成的createUserAndSetting其中BaseUserBOService还生成createUser和createSetting函数一起完成用户创建和设置创建逻辑。
- **修改建议:**
- 不能修改BaseBOService函数不建议修改BTO文件。建议在BOService中扩展代码处理可能被复用的前后逻辑如数据库前后值对比、常被复用的校验逻辑(业务不变性校验除外)、需要在一个事务内执行的其他写操作等。
- 父类函数返回BoResult类记录各Bto和Bo实例对应关系及Bto实例操作结果可通过以下接口获取。例如创建用户的写方案中BoService入参为CreateUserBto用户id由数据库生成需要返回创建用户id时通过boResult.getAddedResult(createUserBto).getBo().getId()返回新建用户id
- 不能修改BaseBOService中的函数不建议修改BTO文件。建议在BOService中扩展代码处理可能被复用的前后逻辑如数据库前后值对比、常被复用的校验逻辑(业务不变性校验除外)、需要在一个事务内执行的其他写操作等。
- BaseBOService中的函数返回BoResult类记录各Bto和Bo实例对应关系及Bto实例操作结果可通过以下接口获取。例如创建用户的写方案中BoService入参为CreateUserBto用户id由数据库生成需要返回创建用户id时通过boResult.getAddedResult(createUserBto).getBo().getId()返回新建用户id
```java
/**
* 获取更新成功的bto结果
*/
public UpdatedBto getUpdatedResult(final Object bto)
public UpdatedBto getUpdatedResult(final Object bto);
/**
* 获取成功插入的Bto结果
* 获取成功创建的Bto结果
*/
public AddedBto getAddedResult(final Object btoObj)
public AddedBto getAddedResult(final Object btoObj);
/**
* 获取Bto对应的删除结果
*/
public DeletedBto getDeletedResult(final Object btoObj)
public DeletedBto getDeletedResult(final Object btoObj);
```
```java
//记录Bto和它对应的更新的实体的前值以及更新后的Bo的
@Setter
@Getter
public class UpdatedBto<Bto,Entity,BO> {
//Bto入参
private Bto bto;
//Bto对应的Entity前项
private Entity entity;
//bo后项
private BO bo;
private Bto bto;//Bto入参
private Entity entity;//Bto对应的Entity更新前值
private BO bo;//bo更新后的值
}
```
```java
//记录删除记录情况
//记录Bto和它对应删除的实体
@Setter
@Getter
public class DeletedBto<Bto,Entity> {
//Bto入参
private Bto bto;
//Bto对应的Entity前项
private Entity entity;
private Bto bto; //Bto入参
private Entity entity; //Bto对应的Entity前项
}
```
```java
//记录Bto创建的前后值
//记录Bto和它对应创建出的Bo
@Setter
@Getter
public class AddedBto<Bto,BO> {
//Bto入参
private Bto bto;
//bo后项
private BO bo;
private Bto bto;//Bto入参
private BO bo;//根据bto创建出来的Bo
}
```
@@ -1203,8 +1343,8 @@ class CreateUserBto { //对应实体user
- **自定义RPC和手写方法关系:** 两者都可手动实现服务层方法。区别需要被其他模块订阅用自定义RPC只是API私有调用不需要外部开放用手写方法
- **关键配置:** 类名(驼峰首字母大写以Service结尾)、是否公开、方法名(驼峰,首字母小写)、请求参数、返回值。分页查询参数为Qto类型时Qto已包含from、size、scrollId属性无需额外参数
- **TOCO中RPC存储:** RPC在TOCO中只存储方法签名不存储执行逻辑。了解实现逻辑需阅读RPC对应代码
- **参数类型:** RPC参数**只能**为QTO、BTO、Enum、基本类型可为单值或列表。对象类型优先用QTO、BTO**禁用**VO和自定义结构如Object
- **返回值类型:** RPC返回值**只能**为DTO、Enum、基本类型可为单值或列表**禁用**VO、QTO、BTO、自定义结构如Object。对象类型优先用DTO
- **参数类型:** RPC参数**只能**为QTO、BTO、Enum、EO、基本类型可为单值或列表。对象类型优先用QTO、BTO**禁用**VO、Map、和自定义结构如Object
- **返回值类型:** RPC返回值**只能**为DTO、Enum、Eo、基本类型,可为单值或列表,**禁用**VO、QTO、BTO、Map、自定义结构如Object。对象类型优先用DTO
- **TOCO中json结构:** TOCO中DTO用json表示示例
```json
{
@@ -1225,16 +1365,16 @@ class CreateUserBto { //对应实体user
}
```
关键字段requestParams为请求参数列表response为返回结构参数和response结构相同其中name为参数名type为参数类型取值Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,Dto,Qto,Bto,List,PageResult,Void参数不能为Void和PageResult无返回值设为Void分页查询结果设为PageResult且innerType必为Dto对应VSQueryResult<XxxDto>description为描述typeUuid为类结构UUIDtype为Enum、Eo、Dto时传入对象uuidQto时传入读方案uuidBto时传入写方案uuidinnerType为List内部类型type为List或PageResult时包含innerUuid为List内部类结构UUIDtype为List或PageResult且innerType为Enum、Eo、Dto时传入对象uuidinnerType为Qto时传入读方案uuidBto时传入写方案uuid
- **生成代码:** RPC在service层生成类文件和实现函数包含DTO自动生成的RPC如UserDtoService.getById、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。公开RPC才能被其他模块使用订阅后生成RpcAdapter适配器其他模块通过RpcAdapter调用。Order模块订阅User模块的UserDtoService.getById在Order模块生成UserDtoServiceInOrderRpcAdapter.getById方法Order模块代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后调用。**必须注意**:变量命名必须是类名首字母小写,禁用其他变量名
- **修改建议:** 建议修改RPC方法不建议修改RPC方法签名、适配器内容
- **生成代码:** RPC在service层生成类文件和实现函数包含DTO自动生成的RPC如UserDtoService.getById、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。公开RPC才能被其他模块使用订阅后生成RpcAdapter适配器其他模块通过RpcAdapter调用。RpcAdapter的命名方式严格遵守以下格式${ClassName}In${ModuleName}RpcAdapter以下为示例Order模块订阅User模块的UserDtoService.getById在Order模块生成UserDtoServiceInOrderRpcAdapter.getById方法Order模块代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后调用。**必须注意**:变量命名必须是类名首字母小写,禁用其他变量名
- **修改建议** 修改RPC方法内容,**禁止** 修改方法签名,**禁止** RpcAdapter如果不满足需求则必须通过修改TOCO设计元素并重新生成代码
#### **2.14 应用程序接口 (API)**
- **定义与用途:** API定义对外HTTP接口
- **创建方式:** 通过TOCO创建需指定参数和返回值
- **存储特点:** TOCO只存储URI和方法签名不存储执行逻辑。了解实现需阅读对应代码
- **关键配置:** uri(/api/${moduleName}/xxx如/api/user/create全局唯一)、类名(以Controller结尾)、方法名(驼峰小写)、请求参数、返回值。分页查询参数为Qto时已包含from、size、scrollId属性无需额外参数
- **参数类型:** 只能为QTO、BTO、EO、Enum、基本类型可为单值或列表。对象类型优先用QTO、BTO。禁用DTO和自定义结构
- **返回值类型:** 脚手架自动包装返回值(code、message、data)。只能为VO、Enum、基本类型可为单值或列表。禁用DTO、QTO、BTO、自定义结构。对象类型优先用VO
- **参数类型:** 只能为QTO、BTO、EO、Enum、基本类型可为单值或列表。对象类型优先用QTO、BTO。禁用DTO、Map和自定义结构
- **返回值类型:** 脚手架自动包装返回值(code、message、data)。只能为VO、Enum、EO、基本类型可为单值或列表。禁用DTO、QTO、BTO、Map、自定义结构。对象类型优先用VO
- **json结构:** API用json表示示例
```json
{
@@ -1265,7 +1405,7 @@ class CreateUserBto { //对应实体user
- **代码产物和修改建议**
- **生成代码:** entrance层生成Controller及对应API方法
- **修改建议:** 建议修改API方法实现内容,禁止直接修改API方法签名、URI。修改API定义需通过修改API设计元素实现
- **修改建议** 修改API方法实现,不可修改API方法签名、URI。由于TOCO生成的框架中已经使用了HandlerMethodReturnValueHandler对返回值进行统一包装处理所以你**不能在Controller中使用@ResponseBody注解**,否则会导致包装失效!!!**禁止** 修改方法签名如果不满足需求则必须通过修改TOCO设计元素并重新生成代码
#### **2.15 流程服务FunctionFlow)**
- **定义与用途:** TOCO将复杂业务拆解为流程节点基于业务逻辑内聚性合并功能构建工作流式逻辑流程提升代码可维护性。内嵌流程引擎执行生成代码。
@@ -1321,7 +1461,7 @@ class CreateUserBto { //对应实体user
}
```
- 示例:用户注册流程
```json
```json-with-comments
{
"moduleName":"user", // 该流程所属的模块
"name":"user_register", // 定义功能
@@ -1398,62 +1538,195 @@ class CreateUserBto { //对应实体user
- **修改建议:** 不修改service函数和FlowConfig可修改FlowContext添加/修改出入参数修改FlowNode中具体业务逻辑。
#### **2.16 自定义查询**
- 读方案无法满足需求时,使用自定义查询
- 自定义查询使用复杂sql实现业务功能
- 读方案无法满足需求时,使用自定义查询。读方案能力边界示例:
- ✅ 可用读方案:
- 时间范围查询:`start_time >= #startTime AND end_time <= #endTime`
- 时间重叠查询:`(start_time <= #endTime AND end_time > #startTime)`
- 状态过滤:`status != 'CANCELLED' AND type in ['MEETING', 'CONFERENCE']`
- 组合条件:`(A AND B) OR (C AND D)`
- exists子查询`EXISTS(SELECT 1 FROM other_table WHERE ...)`
- 简单计数:`SELECT COUNT(*) WHERE ...`或`SELECT COUNT(*) > 0 WHERE ...`
- 一层或多层inner join和left join
- DISTINCT`SELECT DISTINCT xxx`,虽然读方案语法本身不支持,但是可以使用读方案查出一个对象列表,然后在内存中进行去重即可,无需使用自定义查询
- ❌ 必须自定义查询:
- GROUP BY聚合
- 数据库内置函数:`SELECT DATE_FORMAT(start_time, '%Y-%m') FROM meeting`
- 聚合函数:`SELECT AVG(duration), SUM(participant_count) FROM meeting`
- 窗口函数:`SELECT *, ROW_NUMBER() OVER(PARTITION BY room_id ORDER BY start_time) FROM meeting`
- right join和outer join
- 自定义查询使用读方案能力边界之外的sql实现业务功能
- 自定义查询无法被TOCO管理过度使用自定义查询会导致后续代码的维护困难所以在使用自定义查询之前必须仔细分析是否可以使用一个读方案或多个读方案串联实现并尽量使用读方案
- 数据访问层使用MyBatis、MyBatisPlus实现
- 自定义查询时框架不自动生成代码(需要手动编写全部代码)
- 各层代码位置严格遵守**3.2 项目结构与导航**必须有mapper层、service层、DO对象、DTO对象API返回数据必须有VO对象
#### **2.17 领域消息DomainMessage)**
- **定义与用途:** TOCO支持领域消息通过创建和订阅领域消息可以监听聚合对象的状态变化创建、删除、更新)TOCO会自动生成消息的发送逻辑
- **关键配置:** 名称(小写字母+下划线), 由于消息是全局可见所以TOCO限制了名称项目级别不能重复; **注意** 在监听状态变化的时候不要命名为某个字段的变更,比如 order_payed, 也不要按照业务语义命名例如order_payed_complete,因为实际上是order表的任意变更都会受到消息因此命名为order_changed或order_updated更准确的表达了消息的触发语义 同理对于create事件一般命名为 xxx_created对于delete时间一般命名为 xxx_deleted
- **与聚合的关系:** 每个聚合下可以定义多个领域消息,每次可以监听聚合下面其中一个实体的状态变更
- **如何创建/生成:** 创建领域消息,需要先确定聚合,然后确定需要监听的实体以及监听的变更类型
- **最佳实践** 如果只是字段区别,应该修改现有的领域消息,增加相应的字段即可,而不是新创建一个领域消息;
- **领域消息的定义表达**
- 以Json表达Json Schema 定义如下:
```json
{
"type": "object", "required": ["name","description", "bo", "entity", "action","fields"],
"properties": {
"name": {"type": "string", "description": "消息名称,单词之间使用下划线分割总称不超过32个字符,一个模块的领域消息名称不能重复"},
"description": {"type": "string", "description": "消息描述不超过128个字符"},
"bo": {"type": "string", "description": "聚合名称"},
"entity": {"type": "string", "description": "监听的实体名称,必须归属于聚合"},
"fields": {"type": "array", "description": "指定消息里返回的entity的字段对应的字段名称","items": {"type": "string", "description": "字段名称"}},
"action": {"type": "string", "description": "监听的变更类型可选值create、update、delete"},
"delayInSeconds": {"type": "integer", "description": "延迟时间,单位为秒, 如果希望消息延迟消费,则设置该字段, 例如: 订单下单后5分钟未支付触发一个消息则可以监听订单的创建状态并且设置延迟时间5分钟这样消息会在5分钟后被消费"},
"uuid": {"type": "string", "description": "消息的uuid在更新领域消息时传入创建的时候不传"}
}
}
```
- **代码产物和修改建议**
- **发送逻辑** 会在相应聚合的实体中通过hibernate的监听器中实现消息的发生逻辑**不能修改**
- **生成产物:**
- **消息载体:** 在manager层生成一个Java类封装消息体
- **命名规则:** 类名为${domainMessageName}Mo
- **职责:** 消息内容载体
- **类路径:** ```**.manager.mo```
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${DomainMessage在TOCO中的uuid}|DMO|DEFINITION
- **例子**
用户聚合包含了user实体, 用户实体有 user_id user_name 字段那么创建一个领域消息名称为user_created描述为用户创建成功聚合名称为user实体名称为user监听的变更类型为create返回的字段为user_iduser_name。则会生一个类<code> UserCreatedMo {Long userId;String userName;}</code>特别的对于update的监听生成消息里会包含字段的前值命名以old结尾, 例如:<code>UserUpdatedMo {String userName ; String userNameOld;}</code>
#### **2.18 普通消息 CommonMessage)**
- **定义与用途:** TOCO支持普通消息普通消息可以自由定义消息内容。定义消息后TOCO可以生成消息体以及消息的发送模板只需调用这个发送函数即可发送消息; 消息队列的创建配置等工作TOCO自动完成
- **关键配置:** 名称(小写字母+下划线), 由于消息是全局可见所以TOCO限制了名称项目级别不能重复;
- **普通消息的定义表达**
- 以Json表达Json Schema 定义如下:
```json
{
"type":"object",
"properties": {
"name":{ "type": "string", "description": "消息名称,单词之间使用下划线分割总称不超过32个字符,一个模块的领域消息名称不能重复"},
"description": { "type": "string","description": "消息描述不超过128个字符"},
"uuid":{ "type": "string", "description": "唯一标识符,创建时为空,更新时必填"},
"moduleName":{ "type": "string", "description": "所属模块,创建时必填,更新时可空"},
"delay": {"type": "boolean","description": "是否是延迟消息,如果是延时消息,在生成的的发送消息函数里添加延迟时间参数"},
"transactional": {"type": "boolean","description": "是否是事务消息,如果是事务消息,消息的发送和外层事务保持一致"},
"fieldList":{
"type":"array", "description": "消息属性列表",
"items":{
"type": "object",
"properties":{
"name": { "type": "string","description": "字段名称英文下划线分割不超过32字符"},
"uuid":{ "type": "string","description": "字段类型为Enum或Eo时必填对应的Enum或Eo标识符" } ,
"type":{ "type": "string","description": "字段类型String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal,List" },
"innerUuid":{"type": "string", "description": "当innerType为Eo或Enum时对应的标识符"},
"innerType": { "type": "string", "description": "List元素类型String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal"}
},
"required":[ "name","type"]
}
}
},
"required":["name","description"]
}
```
**代码产物和修改建议**
- **消息载体类:** 在manager层生成一个Java类封装消息体
- **命名规则:** 类名为${commonMessageName}Mo
- **职责:** 消息内容载体
- **类路径:** ```**.manager.mo```
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${CommonMessage在TOCO中的uuid}|MO|DEFINITION
- **发送类** 在service层生成一个Java类封装消息发送逻辑
- **命名规则:** 类型名为 ${commonMessageName}MoMessageSender
- **职责** 封装发送函数,作为消息发送的入口
- **类路径:** ```**.service.mq.producer```
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${CommonMessage在TOCO中的uuid}|MO|PRODUCER
- **例子**
- 在用户注册成功后需要给用户发一封邮件可以定义个普通消息名称为user_registered, 包含了字段: long user_id, String nick_name 生成代码后,会生成 类:<code> UserRegisteredMo {Long userId;String nickName;}</code> 和一个发送类 <code>UserRegisteredMoMessageSender {void send(UserRegisteredMo message);}</code>
#### **2.19 订阅消息 (SubscribeMessage)**
- **定义与用途:** TOCO支持订阅消息普通消息和领域消息, 订阅消息后生成代码会生成消息消费的模板代码, 后续只需在对应的模板代码里填写业务逻辑,而无需关注如何订阅消息的代码逻辑 **注意**在TOCO中消息订阅按照模块独立订阅一个消息可以被多个模块订阅同一个消息在一个模块中只能订阅一次
- **订阅消息的定义表达**
- 以Json表达Json Schema 定义如下: 通过msgId或者msgName指定订阅的消息
```json
{
"type": "object", "required": ["moduleName"],
"msgId": {"type": "string", "description": "消息id"},
"msgName": {"type": "string", "description": "消息名称"},
"moduleName": {"type": "string", "description": "模块名称,指定消息被订阅到哪个模块"}
}
```
- **代码产物和修改建议**
- **Consumer类**
- **命名规则:** 在指定模块的的service层生成一个Java类封装了消息的消费类入口
- **命名规则:** 类名为${messageName}Consumer
- **职责:** 消息的消费类入口
- **类路径:** ```**.service.mq.consumer```
- **MsgHandler函数**
- **命名规则:** 在Consumer类中生成一个函数封装了消息的消费逻辑的函数入口
- **命名规则:** 函数名为handleMessage
- **职责:** 消息的消费函数入口,该函数以对应的消息作为参数
- **唯一标识符位置:** 其对应的标识符在函数@AutoGenerated中指定, uuid规则: ${模块id}_${DomainMessage在TOCO中的uuid}
- **修改建议**
- handleMessage函数中实现业务逻辑 如果消费成功返回ture; 处理失败返回false 消息会被多次投递
- 如果消息要实现幂等消费则需要在handleMessage函数上添加@Transactional注解同时在Consumer类中覆盖父函数 @Override boolean useDBIdempotentWithTransactional(){return true;}, 开启幂等机制; 对于监听实体状态变化的事件,因为实体的任何字段变更都会触发事件,因此在消费消息的时候有些情况需要根据具体的字段值进行过滤, 例如:监听订单支付状态, 创建的消息叫做 order_changed_mo,包含了status字段那么仅当status_older!=payed && status=payed 为true的时候表示是订单支付完成的消息。
### **3 代码生成说明**
**3.1 技术栈**
Java、SpringBoot、MyBatis-plus(读取)、Hibernate(写入)
**3.2 项目结构**
多模块SpringBoot项目子模块在`/modules`目录下。子模块路径必须从`modules`开始,如:`modules/module1/src/main/java...`
- 多模块SpringBoot项目子模块在`/modules`目录下。子模块路径必须从`modules`开始,如:`modules/module1/src/main/java...`
- 根目录的entrance默认已经依赖了所有子模块的entrance模块无需额外引入pom配置。
```
项目根目录
── main_module
│ ├── common/ # 项目级公共模块
|──main_module
│ ├── pom.xml # 项目级pom配置
│ ├── common # 项目级公共模块
│ │ ├──config/ # 中间件配置
│ │ ├──constants/ # 项目常量
│ │ ├──enums/ # 项目枚举
│ │ ├──redis/ # Redis配置
│ │ ├──response/ # 返回结果封装
│ │ └── utils/ # 项目工具类
└── entrance/ # 项目入口
│ │ └──utils/ # 项目工具类存放全项目复用的、或多模块复用的工具类如OSSUtils、UserTokenUtils等
└───├── entrance/ # 项目入口引用了项目中的所有模块通常适合放一些全局的Filter、Aspect等如AuthFilter、LogAspect等
│ ├──pom.xml # 项目级pom配置
│ └──AppApplication.java # 启动类
└── modules/ # 子模块列表
└── modules # 子模块列表
└── module1/ # 子模块1
├── pom.xml # 子模块1的pom配置
├── common/
│ ├──pom.xml # common的pom配置
│ ├──constants/ # 模块常量
│ ├──utils/ #模块工具类
└── enums/ # 模块枚举
├── entrance/web/src/main/java/com/{项目名}/{模块名}/entrance/web/
│ └──enums/ # 枚举
├── entrance/web/src/main/java/com/{project_name}/{module_name}/entrance/web/
│ ├──pom.xml # entrance的pom配置
│ ├──controller/ # API定义
│ ├──converter/ # DTO转VO
│ ├──vo/ # VO结构
│ └── query/ # 读方案转VO
│ ├── assembler/ # VO数据填充
│ ├── collector/ # ID数据展开完整对象
│ └── executor/ # 调用Service实现,返回VO
├── manager/src/main/java/com/{项目名}/{模块名}/manager/
│ ├── collector/ # 读方案返回的id数据展开完整对象
│ └── executor/ # 调用Service的度方案实现同时调用collector和assembler返回最终的VO
├── manager/src/main/java/com/{project_name}/{module_name}/manager/
│ ├── bo/ # 聚合对象定义
│ │ └── base/ # 聚合对象基类
│ │ └── base/ # 聚合对象基类
│ ├── dto/ # DTO定义
│ ├── converter/ # 复杂DTO组装
│ ├── facade/ # 跨模块RPC适配器
│ ├── mo/ # 消息定义 (普通消息和领域消息)
│ ├── converter/ # 复杂Dto非BaseDto)组装
│ ├── facade/ # 调用其他模块的RPC适配器包含RpcAdapter如UserDtoServiceInMeetingRpcAdapter表示从meeting模块调用user模块中方法
│ └── impl/ # DTO查询实现
├── persist/src/main/java/com/{项目名}/{模块名}/persist/
├── persist/src/main/java/com/{project_name}/{module_name}/persist/
│ ├── eo/ # 值对象定义
│ ├── dos/ # 数据库表映射
│ ├── qto/ # 读方案数据库查询
│ └── mapper/ # MyBatis Mapper
└── service/src/main/java/com/{项目名}/{模块名}/service/
└── service/src/main/java/com/{project_name}/{module_name}/service/ # BOService包含某个聚合下所有写方案生成的方法、 DtoService包含DTO生成的预定义方法
├── mq/producer/ #普通消息的生成者
├── mq/consumer/ #消息的消费者
├── bto/ # 写方案入参定义
├── converter/ # BaseDto扩展
├── query/ # 查询方案Service入口
├── converter/ # 对返回的BaseDto按需进行二次扩展
├── query/ # 查询方案的service入口调用persist层的查询实现
└── base/ # BOService基类
模块依赖层级entrance -> service -> manager -> persist各层都依赖common
@@ -1471,17 +1744,85 @@ Java、SpringBoot、MyBatis-plus(读取)、Hibernate(写入)
- `uuid`属性:唯一标识,包含`|`字符表示特殊格式拼装
**3.5 分层规则**
Service层方法不能返回VO不能调用Controller层方法(如VoQueryExecutor、VoConverter等)
TOCO严格遵照分层规则必须严格遵守否则会产生严重后果
- VO以及相关的类属于entrance层
- Service禁止引用任何entrance层的类或代码
- Service层方法禁止返回VO禁止调用任何entrance层方法(如VoQueryExecutor、VoConverter等)
### 4. TOCO 最佳实践
#### 4.1 接口参数类型和返回值选择:
设计TOCO接口(API、RPC)时先判断主要功能是读库还是写库分析对应的读写方案及QTO、BTO。读场景**优先**使用QTO作参数写场景**优先**使用BTO作参数。QTO、BTO不满足时可增加基本类型、Enum、EO参数。必须遵循a.DTO、VO不能作参数b.QTO、BTO不能作返回值。
**重要说明:**
- 本节描述参数选择的**优先级原则**,非绝对限制
- API参数绝对限制见2.14章节
- "优先使用QTO/BTO"指在满足规范前提下,根据场景选择最合适的参数类型
- TOCO不支持Map、Object类型
- 注意每个API所属的站点和页面通常API参数一般不会包含当前用户ID、当前用户所属部门ID等属于当前用户的信息。如果接口需要这些信息则默认必须查找系统中是否存在对应的通用组件如用户认证、权限的控制等如果不存在则必须进行通用组件搭建
- RPC的接口参数中如果需要当前用户的信息则如果有需要通常会将当前用户的信息作为参数传入。
#### 4.2 并发数据保护最佳实践:
处理先读后更新场景时为避免并发导致数据脏写应充分利用BoService校验功能。例如账户扣钱场景避免并发扣除导致余额不足传统raw sql用 `update account set balance = balance - amount where user_id = xxx and balance > amount` 的where条件保护在TOCO中应该1、写方案中使用incr字段。2、在boService中添加保护代码 `if(userBo.getBalance() >= 0)` 或通过userBo业务不变性聚合校验添加 `balance>=0` 校验。
#### 4.3 聚合校验识别与实现指南
##### 4.3.1 业务不变性规则的明确判断标准
**必须放在聚合校验中的场景(四个核心判断标准):**
**标准1聚合内实体间的约束关系**
- 父子实体之间的数据一致性约束
- 示例:订单总金额 = 所有订单项金额之和
- 示例:会议时间范围必须包含所有议程时间
- 示例:主实体状态变更影响子实体状态的规则
**标准2聚合内集合类型字段的内部约束**
- 列表元素之间的互斥、重叠、顺序等约束
- 示例:同一会议的议程时间不能重叠
- 示例:同一用户的多个手机号不能重复
- 示例:优先级序号必须连续且唯一
**标准3聚合根实体自身的完整性约束**
- 单个实体内多个字段之间的逻辑关系
- 示例:开始时间 < 结束时间
- 示例折扣价 原价
- 示例最小值 最大值
**标准4领域概念的强制性规则**
- 体现核心业务概念的必然约束
- 示例会议必须至少有一个议程
- 示例订单必须至少有一个订单项
- 示例员工必须属于某个部门
**不应放在聚合校验中的场景:**
**场景1需要外部数据查询的校验**
- 需要查询数据库其他表的数据
- 需要调用其他服务的RPC
- 示例会议室占用校验需要查询其他会议
- 示例用户名唯一性校验需要查询用户表
- **实现位置**Controller或Service层
**场景2用例特定的前置条件校验**
- 仅在特定API或业务流程中需要的校验
- 非领域模型核心规则
- 示例必填参数校验
- 示例参数格式校验
- **实现位置**Controller层
**场景3需要重型操作的校验**
- 需要复杂计算或外部服务调用
- 会严重影响性能的校验
- **实现位置**Service层
##### 4.3.2 实现注意事项
- 聚合校验方法会在写方案执行数据库操作前自动调用
- 禁止在业务代码中显式调用validateAggregate()或valid()方法
- 如果校验逻辑需要查询数据库或调用RPC不要放在聚合校验中
- 聚合校验应该是纯内存操作执行速度快
-----------------------------------------------------------------------------
</TOCO知识库>