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 并发数据保护最佳实践: