diff --git a/knowledge.md b/knowledge.md
index db1d755..15f8f53 100644
--- a/knowledge.md
+++ b/knowledge.md
@@ -717,78 +717,78 @@ meeting_with_room_and_agenda_wo示例:
- 禁止使用filter语法
- 禁止使用has语法
- 语法定义:使用 lezer 定义了如下语法
-
- @top Program { expression? }
- @skip { spaces | newline | LineComment }
- @precedence {
- member,
- and @left,
- or @left
- }
- kw { @specialize[@name={term}] }
- boolean { @specialize[@name=Boolean] }
- @skip {} {
- String[isolate] {
+
+ @top Program { expression? }
+ @skip { spaces | newline | LineComment }
+ @precedence {
+ member,
+ and @left,
+ or @left
+ }
+ kw { @specialize[@name={term}] }
+ boolean { @specialize[@name=Boolean] }
+ @skip {} {
+ String[isolate] {
'"' (stringContentDouble | Escape)* ('"' | "\n") |
"'" (stringContentSingle | Escape)* ("'" | "\n")
- }
- }
- commaSep {
- content ("," content)*
- }
- List {
- "[" commaSep ~destructure "]"
- }
- Value { String | Number | List | boolean }
- Field { identifier ~arrow }
- Member { Field !member "." (Member | Field) }
- Input { "#" identifier ~arrow }
- expression {
- TupleExpression | BinaryExpression | NotExpression | ParenthesizedExpression | ListExpression
- }
- TupleExpression { TwoTupleExpression | ThreeTupleExpression }
- TwoTupleExpression { (Field | Member) TwoOperator }
- ThreeTupleExpression { (Field | Member) ThreeOperator (Input | Value) }
- ListExpression { (Field | Member) ListOperator ParenthesizedExpression }
- BinaryExpression {
- expression !and (kw<'AND'> | kw<'and'>) expression |
- expression !or (kw<'OR'> | kw<'or'>) expression
- }
- NotExpression { (kw<'NOT'> | kw<'not'>) ParenthesizedExpression }
- ParenthesizedExpression { "(" expression ")" }
- @tokens {
- spaces[@export] { $[\u0009 \u000b\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+ }
- newline[@export] { $[\r\n\u2028\u2029] }
- identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
- word { identifierChar (identifierChar | @digit)* }
- identifier { word }
- hex { @digit | $[a-fA-F] }
- stringContentSingle { ![\\\n']+ }
- stringContentDouble { ![\\\n"]+ }
- @precedence { spaces, newline, identifier }
- Escape {
+ }
+ }
+ commaSep {
+ content ("," content)*
+ }
+ List {
+ "[" commaSep ~destructure "]"
+ }
+ Value { String | Number | List | boolean }
+ Field { identifier ~arrow }
+ Member { Field !member "." (Member | Field) }
+ Input { "#" identifier ~arrow }
+ expression {
+ TupleExpression | BinaryExpression | NotExpression | ParenthesizedExpression | ListExpression
+ }
+ TupleExpression { TwoTupleExpression | ThreeTupleExpression }
+ TwoTupleExpression { (Field | Member) TwoOperator }
+ ThreeTupleExpression { (Field | Member) ThreeOperator (Input | Value) }
+ ListExpression { (Field | Member) ListOperator ParenthesizedExpression }
+ BinaryExpression {
+ expression !and (kw<'AND'> | kw<'and'>) expression |
+ expression !or (kw<'OR'> | kw<'or'>) expression
+ }
+ NotExpression { (kw<'NOT'> | kw<'not'>) ParenthesizedExpression }
+ ParenthesizedExpression { "(" expression ")" }
+ @tokens {
+ spaces[@export] { $[\u0009 \u000b\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+ }
+ newline[@export] { $[\r\n\u2028\u2029] }
+ identifierChar { @asciiLetter | $[_$\u{a1}-\u{10ffff}] }
+ word { identifierChar (identifierChar | @digit)* }
+ identifier { word }
+ hex { @digit | $[a-fA-F] }
+ stringContentSingle { ![\\\n']+ }
+ stringContentDouble { ![\\\n"]+ }
+ @precedence { spaces, newline, identifier }
+ Escape {
"\\" ("x" hex hex | "u" ("{" hex+ "}" | hex hex hex hex) | ![xu])
- }
- Number {
+ }
+ Number {
(@digit ("_" | @digit)* ("." ("_" | @digit)*)? | "." @digit ("_" | @digit)*)
- (("e" | "E") ("+" | "-")? ("_" | @digit)+)? |
+ (("e" | "E") ("+" | "-")? ("_" | @digit)+)? |
@digit ("_" | @digit)* "n" |
"0x" (hex | "_")+ "n"? |
"0b" $[01_]+ "n"? |
"0o" $[0-7_]+ "n"?
- }
+ }
- @precedence { Number "." }
- ThreeOperator { "in" | "notIn" | "!=" | "==" | ">" | ">=" | "<" | "<=" | "isNullOrNot" | "like" | "has" }
- TwoOperator { "isNull" | "isNotNull" }
- ListOperator { "contains" | "filter" }
- LineComment[isolate] { "//" ![\n]* }
- "(" ")"
- "[" "]"
- "."
+ @precedence { Number "." }
+ ThreeOperator { "in" | "notIn" | "!=" | "==" | ">" | ">=" | "<" | "<=" | "isNullOrNot" | "like" | "has" }
+ TwoOperator { "isNull" | "isNotNull" }
+ ListOperator { "contains" | "filter" }
+ LineComment[isolate] { "//" ![\n]* }
+ "(" ")"
+ "[" "]"
+ "."
}
@detectDelim
-
+
- **读方案设计元素的表达**
- 以json格式表达,json schema 定义如下
```json
@@ -835,73 +835,73 @@ meeting_with_room_and_agenda_wo示例:
- 上下文
public class meeting_room {
- storey storey; // 楼层对象
- String name; // 名称
- room_type_enum room_type; // 会议室类型
- List equipment; // 设备列表
- Long id; // 主键
- Long seat_number; // 座位数
- String description; // 说明
- Boolean enable_indicator; // 是否启用
- String input_code; // 输入码
- Long storey_id; // 楼层id
- Long contact_staff_id; // 联系员工ID
- List meeting; // 会议列表,可添加过滤条件
- class storey { // 楼层
- location location; // 位置对象
- String name; // 楼层名
- Long id; // 主键
- Long sort_number; // 排序号
- Long location_id; // 位置id
- }
- class meeting {
- Long id; // 主键
- String title; // 标题
- Date start_time; // 开始时间
- Date end_time; // 结束时间
- Long room_id; // 会议室id
- String description; // 描述
- Long create_user_id; // 创建人id
- }
- class location { // 位置,如楼栋
- String name; // 名称
- Long id; // 主键
- String description; // 描述
- Long sort_number; // 排序号
- }
- enum room_type_enum {
- SMALL, //小会议室
- MEDIUM, //中会议室
- LARGE
- }
- enum equipment_enum {
- TV, //电视
- WHITEBOARD, //白板
- PROJECTOR, //投影仪
- VIDEO
- }
- }
-
+ storey storey; // 楼层对象
+ String name; // 名称
+ room_type_enum room_type; // 会议室类型
+ List equipment; // 设备列表
+ Long id; // 主键
+ Long seat_number; // 座位数
+ String description; // 说明
+ Boolean enable_indicator; // 是否启用
+ String input_code; // 输入码
+ Long storey_id; // 楼层id
+ Long contact_staff_id; // 联系员工ID
+ List meeting; // 会议列表,可添加过滤条件
+ class storey { // 楼层
+ location location; // 位置对象
+ String name; // 楼层名
+ Long id; // 主键
+ Long sort_number; // 排序号
+ Long location_id; // 位置id
+ }
+ class meeting {
+ Long id; // 主键
+ String title; // 标题
+ Date start_time; // 开始时间
+ Date end_time; // 结束时间
+ Long room_id; // 会议室id
+ String description; // 描述
+ Long create_user_id; // 创建人id
+ }
+ class location { // 位置,如楼栋
+ String name; // 名称
+ Long id; // 主键
+ String description; // 描述
+ Long sort_number; // 排序号
+ }
+ enum room_type_enum {
+ SMALL, //小会议室
+ MEDIUM, //中会议室
+ LARGE
+ }
+ enum equipment_enum {
+ TV, //电视
+ WHITEBOARD, //白板
+ PROJECTOR, //投影仪
+ VIDEO
+ }
+ }
+
- 用户需求
获取一段时间未使用的会议室(包含当前会议已选择的会议室),根据会议标题过滤会议信息,分页返回。
- 读方案定义
{
- "name": "get_unused_meeting_room_list",
- "description": "获取未使用的会议室(包含当前会议已选择的会议室)",
- "woId":"759bedd4-4540-4de6-b65d-d44912fb0991",
- "dtoOrVoId":"e50c02c3-f336-4dc6-88e1-24ffe3dea1e2",
- "generateCountApi": true,
- "supportPaginate": true,
- "supportUnPage": false,
- "supportWaterfall": false,
- "query": "enable_indicator == true AND ( id == #idIs OR meeting isNull OR NOT ( meeting contains ( start_time <= #meetingEndTime AND end_time >= #meetingStartTime ) ) )",
- "filters": [
- {
- "fieldPath": "meeting",
- "filter": "title like #meetingTitleLike"
- }
- ]
+ "name": "get_unused_meeting_room_list",
+ "description": "获取未使用的会议室(包含当前会议已选择的会议室)",
+ "woId":"759bedd4-4540-4de6-b65d-d44912fb0991",
+ "dtoOrVoId":"e50c02c3-f336-4dc6-88e1-24ffe3dea1e2",
+ "generateCountApi": true,
+ "supportPaginate": true,
+ "supportUnPage": false,
+ "supportWaterfall": false,
+ "query": "enable_indicator == true AND ( id == #idIs OR meeting isNull OR NOT ( meeting contains ( start_time <= #meetingEndTime AND end_time >= #meetingStartTime ) ) )",
+ "filters": [
+ {
+ "fieldPath": "meeting",
+ "filter": "title like #meetingTitleLike"
+ }
+ ]
}
- **代码产物和修改建议**
@@ -912,11 +912,11 @@ meeting_with_room_and_agenda_wo示例:
* **职责:** 读方案的查询参数结构,可作为API参数,直接透传给RPC调用
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${ReadPlan的uuid}|QTO|DEFINITION
* **内部结构:** QTO包含分页相关字段:
-
- private String scrollId; //瀑布流游标
- private Integer size; //每页记录数
- private Integer from; //开始记录位置
-
+
+ private String scrollId; //瀑布流游标
+ private Integer size; //每页记录数
+ private Integer from; //开始记录位置
+
- **Dao**
- **生成产物:** Dao层生成Java类
- **命名规则:** 类名以Dao结尾(${ReadPlanName}Dao)
@@ -926,44 +926,44 @@ meeting_with_room_and_agenda_wo示例:
- **QueryExecutor**
- **生成条件** 如果一个读方案返回的是VO结构,则会生成QueryExecutor
- **生成产物** 返回**VO**的查询方案,在entrance层生成Java类,包含独立函数:
- - 分页查询: **Paged函数,返回VSQueryResult
- - 不分页全量: query**函数,返回List
- - 瀑布流: **Waterfall函数,返回VSQueryResult
- - 查询数量: **Count函数,返回Integer
+ - 分页查询: **Paged函数,返回VSQueryResult
+ - 不分页全量: query**函数,返回List
+ - 瀑布流: **Waterfall函数,返回VSQueryResult
+ - 查询数量: **Count函数,返回Integer
- **命名规则:** 类名以QueryExecutor结尾(${VoName}QueryExecutor)
- **类路径:** `**.entrance.web.query.executor`包下
- **职责:** 提供VO查询入口,将QtoService返回的id数据转化为目标**VO**
- **QueryService**
- - **生成条件** 如果一个读方案返回的是DTO结构,则会生成QueryService
- - **生成产物** 返回**DTO**的查询方案,在service层生成Java类,包含独立函数:
- - 分页查询: **Paged函数,返回VSQueryResult
- - 不分页全量: query**函数,返回List
- - 瀑布流: **Waterfall函数,返回VSQueryResult
- - 查询数量: **Count函数,返回Integer
- - **命名规则:** 类名以QueryService结尾(${DtoName}QueryService)
- - **类路径:** `**.service.index.entity`包下
- - **职责:** 提供DTO查询入口,将QtoService返回的id数据转化为目标**DTO**,或返回符合条件的记录数量
+ - **生成条件** 如果一个读方案返回的是DTO结构,则会生成QueryService
+ - **生成产物** 返回**DTO**的查询方案,在service层生成Java类,包含独立函数:
+ - 分页查询: **Paged函数,返回VSQueryResult
+ - 不分页全量: query**函数,返回List
+ - 瀑布流: **Waterfall函数,返回VSQueryResult
+ - 查询数量: **Count函数,返回Integer
+ - **命名规则:** 类名以QueryService结尾(${DtoName}QueryService)
+ - **类路径:** `**.service.index.entity`包下
+ - **职责:** 提供DTO查询入口,将QtoService返回的id数据转化为目标**DTO**,或返回符合条件的记录数量
- **VSQueryResult说明:**
- VSQueryResult是固定类,全路径为com.vs.es.query.VSQueryResult,存在于jar中,直接使用,禁止创建新的VSQueryResult类!
- VSQueryResult代码结构:
-
- package com.vs.es.query;
+
+ package com.vs.es.query;
- import lombok.Data;
+ import lombok.Data;
- import java.util.List;
+ import java.util.List;
- @Data
- public class VSQueryResult { //T为DTO或VO类型
- private int count;
- private List result;
+ @Data
+ public class VSQueryResult { //T为DTO或VO类型
+ private int count;
+ private List result;
private int from; //页码分页起始条数
private int size; //每页记录数量
private String scrollId; //瀑布流查询游标
private boolean hasMore; //瀑布流查询是否有下一页
- }
-
+ }
+
- **例子:**
* 根据用户名称查询用户列表返回UserDTO,生成UserNameQto、UserNameQtoService、UserNameQtoDao、UserNameQueryService; UserNameQueryService调用UserNameQtoService,UserNameQtoService调用UserNameQtoDao
- **修改建议:**
@@ -978,12 +978,12 @@ meeting_with_room_and_agenda_wo示例:
#### **2.11 写方案 (WritePlan,单聚合操作)**
- **定义与用途:** 写方案是数据库写操作的唯一方式,每个写方案只能变更一个聚合的数据。写方案可以一次操作聚合内的多张表。例如:location(父对象/聚合根)和storey(子对象)是1:N关系,可创建写方案同时更新location信息和操作storey列表(新增/删除/修改);也可创建写方案单独更新单个storey对象。
- **关键配置:** 名称(小写+下划线,不以write_plan结尾,全局唯一),操作的聚合,聚合内的实体和字段,每个实体的操作类型:
- - CREATE:创建单个实体
- - UPDATE:更新单个实体
- - DELETE:删除单个实体
- - CREATE_ON_DUPLICATE_UPDATE:创建或更新单个实体
- - FULL_MERGE:批量更新列表数据,执行CREATE_ON_DUPLICATE_UPDATE操作,删除不在传入列表中的旧数据
- - PARTIAL_MERGE:批量更新列表数据,执行CREATE_ON_DUPLICATE_UPDATE操作
+ - CREATE:创建单个实体
+ - UPDATE:更新单个实体
+ - DELETE:删除单个实体
+ - CREATE_ON_DUPLICATE_UPDATE:创建或更新单个实体
+ - FULL_MERGE:批量更新列表数据,执行CREATE_ON_DUPLICATE_UPDATE操作,删除不在传入列表中的旧数据
+ - PARTIAL_MERGE:批量更新列表数据,执行CREATE_ON_DUPLICATE_UPDATE操作
- **与RPC关系:** 每个写方案自动生成一个RPC方法,参数为对应BTO,返回值为聚合根实体主键值,只实现当前聚合的数据库操作。
- **生成RPC使用:** 写方案RPC属于所属模块,其他模块需先订阅该RPC,用adapter调用;当前模块直接调用对应Service。
- **与聚合关系:** 每个聚合可定义多个写方案,但每个写方案只能操作一个聚合内的表。
@@ -997,13 +997,13 @@ meeting_with_room_and_agenda_wo示例:
- 选定变更对象的字段(每个对象至少选一个字段,无法确定时默认选全部,字段必须明确指定名称)
- 为每个对象设置操作类型
- 列表属性可批量操作:
- - 批量更新列表(FULL_MERGE/PARTIAL_MERGE):需包含父对象
- - 批量删除列表:需包含父对象,父对象设为UPDATE
- - 批量创建列表:需包含父对象,父对象设为UPDATE或CREATE
- - 示例:meeting对象有List agendaList,批量创建agenda需同时设置meeting和meeting_agenda对象,meeting_agenda设为CREATE;只设置meeting_agenda的CREATE则一次只能创建一条数据
+ - 批量更新列表(FULL_MERGE/PARTIAL_MERGE):需包含父对象
+ - 批量删除列表:需包含父对象,父对象设为UPDATE
+ - 批量创建列表:需包含父对象,父对象设为UPDATE或CREATE
+ - 示例:meeting对象有List agendaList,批量创建agenda需同时设置meeting和meeting_agenda对象,meeting_agenda设为CREATE;只设置meeting_agenda的CREATE则一次只能创建一条数据
- FULL_MERGE和PARTIAL_MERGE只用于列表属性,单值属性用CREATE_ON_DUPLICATE_UPDATE
- UPDATE/DELETE/CREATE_ON_DUPLICATE_UPDATE/FULL_MERGE/PARTIAL_MERGE操作必须指定一个唯一键(包括主键),用于确定数据记录,主键字段也要包含在fields中
- - 唯一键:实体中的唯一索引,由一个或多个字段组成
+ - 唯一键:实体中的唯一索引,由一个或多个字段组成
- 选定对象层级不能跳跃,发生不连续时去掉不连续的叶子对象
- 父对象和子对象操作类型限制:
- 父对象DELETE:子对象不能选择任何操作
@@ -1012,8 +1012,8 @@ meeting_with_room_and_agenda_wo示例:
- 父对象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操作的时候,如果主键不又入参指定,不勾选主键字段,主键字段的值由框架分配,如果勾选了主键,但是不传递参数值,会造成运行时异常
+ - 按需选择字段
+ - 当CREATE操作的时候,如果主键不又入参指定,不勾选主键字段,主键字段的值由框架分配,如果勾选了主键,但是不传递参数值,会造成运行时异常
- **写方案设计元素表达:**
```json
{
@@ -1411,10 +1411,10 @@ class CreateUserBto { //对应实体user
- **定义与用途:** TOCO支持领域消息,通过创建和订阅领域消息,可以监听聚合对象的状态变化(创建、删除、更新),TOCO会自动生成消息的发送逻辑
- **关键配置:** 名称(小写字母+下划线), 由于消息是全局可见,所以TOCO限制了名称项目级别不能重复; **注意** 在监听状态变化的时候不要命名为某个字段的变更,比如 order_payed, 也不要按照业务语义命名,例如order_payed_complete,因为实际上是order表的任意变更都会受到消息,因此命名为order_changed或order_updated更准确的表达了消息的触发语义; 同理对于create事件,一般命名为 xxx_created;对于delete时间,一般命名为 xxx_deleted
- **与聚合的关系:** 每个聚合下可以定义多个领域消息,每次可以监听聚合下面其中一个实体的状态变更
-- **如何创建/生成:** 创建领域消息,需要先去顶聚合,然后确定需要监听的实体以及监听的变更类型
+- **如何创建/生成:** 创建领域消息,需要先确定聚合,然后确定需要监听的实体以及监听的变更类型
- **最佳实践** 如果只是字段区别,应该修改现有的领域消息,增加相应的字段即可,而不是新创建一个领域消息;
- **领域消息的定义表达**
- - 以Json表达,Json Schema 定义如下:
+ - 以Json表达,Json Schema 定义如下:
```json
{
"type": "object", "required": ["name","description", "bo", "entity", "action","fields"],
@@ -1431,20 +1431,66 @@ class CreateUserBto { //对应实体user
}
```
- **代码产物和修改建议**
- - **发送逻辑** 会在相应聚合的实体中通过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_id,user_name。则会生一个类: UserCreatedMo {Long userId;String userName;};特别的,对于update的监听,生成消息里会包含字段的前值,命名以old结尾, 例如:UserUpdatedMo {String userName ; String userNameOld;}
-
-#### **2.18 订阅消息 (SubscribeMessage)**
-- **定义与用途:** TOCO支持订阅消息, 订阅消息后生成代码会生成消息消费的模板代码, 后续只需在对应的模板代码里填写业务逻辑,而无需关注如何订阅消息的代码逻辑 **注意**在TOCO中,消息订阅按照模块独立订阅:一个消息可以被多个模块订阅,同一个消息在一个模块中只能订阅一次
+ - **发送逻辑** 会在相应聚合的实体中通过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_id,user_name。则会生一个类: UserCreatedMo {Long userId;String userName;};特别的,对于update的监听,生成消息里会包含字段的前值,命名以old结尾, 例如:UserUpdatedMo {String userName ; String userNameOld;}
+#### **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, 生成代码后,会生成 类: UserRegisteredMo {Long userId;String nickName;} 和一个发送类 UserRegisteredMoMessageSender {void send(UserRegisteredMo message);}
+
+#### **2.19 订阅消息 (SubscribeMessage)**
+- **定义与用途:** TOCO支持订阅消息(普通消息和领域消息), 订阅消息后生成代码会生成消息消费的模板代码, 后续只需在对应的模板代码里填写业务逻辑,而无需关注如何订阅消息的代码逻辑 **注意**在TOCO中,消息订阅按照模块独立订阅:一个消息可以被多个模块订阅,同一个消息在一个模块中只能订阅一次
- **订阅消息的定义表达**
- - 以Json表达,Json Schema 定义如下: 通过msgId或者msgName指定订阅的消息
+ - 以Json表达,Json Schema 定义如下: 通过msgId或者msgName指定订阅的消息
```json
{
"type": "object", "required": ["moduleName"],
@@ -1455,19 +1501,19 @@ class CreateUserBto { //对应实体user
```
- **代码产物和修改建议**
- **Consumer类**
- - **命名规则:** 在指定模块的的service层生成一个Java类,封装了消息的消费类入口
- - **命名规则:** 类名为${messageName}Consumer
- - **职责:** 消息的消费类入口
- - **类路径:** ```**.service.mq.consumer```
+ - **命名规则:** 在指定模块的的service层生成一个Java类,封装了消息的消费类入口
+ - **命名规则:** 类名为${messageName}Consumer
+ - **职责:** 消息的消费类入口
+ - **类路径:** ```**.service.mq.consumer```
- **MsgHandler函数**
- - **命名规则:** 在Consumer类中生成一个函数,封装了消息的消费逻辑的函数入口
- - **命名规则:** 函数名为handleMessage
- - **职责:** 消息的消费函数入口,该函数以对应的消息作为参数
- - **唯一标识符位置:** 其对应的标识符在函数@AutoGenerated中指定, uuid规则: ${模块id}_${DomainMessage在TOCO中的uuid}
+ - **命名规则:** 在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的时候表示是订单支付完成的消息。
-
+ - handleMessage函数中实现业务逻辑, 如果消费成功,返回ture; 处理失败返回false, 消息会被多次投递
+ - 如果消息要实现幂等消费,则需要在handleMessage函数上添加@Transactional注解,同时在Consumer类中覆盖父函数 @Override boolean useDBIdempotentWithTransactional(){return true;}, 开启幂等机制; 对于监听实体状态变化的事件,因为实体的任何字段变更都会触发事件,因此在消费消息的时候有些情况需要根据具体的字段值进行过滤, 例如:监听订单支付状态, 创建的消息叫做 order_changed_mo,包含了status字段,那么仅当status_older!=payed && status=payed 为true的时候表示是订单支付完成的消息。
+
### **3 代码生成说明**
@@ -1552,7 +1598,7 @@ Service层方法不能返回VO,不能调用Controller层方法(如VoQueryExecu
**重要说明:**
- 本节描述参数选择的**优先级原则**,非绝对限制
-- API参数绝对限制见2.14章节
+- API参数绝对限制见2.14章节
- "优先使用QTO/BTO"指在满足规范前提下,根据场景选择最合适的参数类型
#### 4.2 并发数据保护最佳实践: