更新 knowledge.md

This commit is contained in:
ycl
2025-09-07 14:30:26 +08:00
parent b8255ca3a0
commit 636cb8a226

View File

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