From acdc73999af5806092e0e4222f6ca7618d019ee9 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Mon, 1 Dec 2025 22:25:28 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat(databus):=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=E4=B8=80+=E4=BA=8C-=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E5=B1=82=E4=B8=8E=E6=95=B0=E6=8D=AE=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 阶段一:数据契约层(任务 1-16) - 新增 DatabusDeptData, DatabusAdminUserData, DatabusPostData 数据对象 - 新增 CursorPageReqDTO, CursorPageResult 游标分页 DTO - 新增 DatabusDeptProviderApi, DatabusUserProviderApi, DatabusPostProviderApi Feign 接口 - 修改 system-api pom.xml 添加 databus-api 依赖 阶段二:数据提供者实现(任务 17-38) - 新增 DatabusDeptProviderApiImpl, DatabusUserProviderApiImpl, DatabusPostProviderApiImpl Feign 接口实现 - 实现游标分页查询(基于 cursorTime + cursorId 复合游标) - 新增 DatabusDeptChangeMessage, DatabusUserChangeMessage, DatabusPostChangeMessage MQ 消息类 - 新增 DatabusChangeProducer 消息生产者(支持部门、用户、岗位三实体) - 修改 DeptServiceImpl, AdminUserServiceImpl, PostServiceImpl 添加事件发布 技术要点: - 游标分页:cursorTime + cursorId 复合游标解决雪花ID乱序问题 - 事件发布:create/update/delete 操作后异步发送 MQ 消息 - 数据聚合:用户数据包含部门和岗位简要信息 Ref: docs/databus/implementation-checklist.md 任务 1-38 --- .../api/data/DatabusAdminUserData.java | 132 +++++++++ .../databus/api/data/DatabusDeptData.java | 90 ++++++ .../databus/api/data/DatabusPostData.java | 67 +++++ .../databus/api/dto/CursorPageReqDTO.java | 48 ++++ .../databus/api/dto/CursorPageResult.java | 81 ++++++ .../api/provider/DatabusDeptProviderApi.java | 71 +++++ .../api/provider/DatabusPostProviderApi.java | 71 +++++ .../api/provider/DatabusUserProviderApi.java | 71 +++++ zt-module-system/zt-module-system-api/pom.xml | 7 + .../api/mq/DatabusDeptChangeMessage.java | 160 +++++++++++ .../api/mq/DatabusPostChangeMessage.java | 109 +++++++ .../api/mq/DatabusUserChangeMessage.java | 154 ++++++++++ .../databus/DatabusDeptProviderApiImpl.java | 218 ++++++++++++++ .../databus/DatabusPostProviderApiImpl.java | 148 ++++++++++ .../databus/DatabusUserProviderApiImpl.java | 267 ++++++++++++++++++ .../databus/DatabusChangeProducer.java | 193 +++++++++++++ .../system/service/dept/DeptServiceImpl.java | 20 ++ .../system/service/dept/PostServiceImpl.java | 17 ++ .../service/user/AdminUserServiceImpl.java | 15 + 19 files changed, 1939 insertions(+) create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusAdminUserData.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusDeptData.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusPostData.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageReqDTO.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageResult.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusDeptProviderApi.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusPostProviderApi.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserProviderApi.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusDeptChangeMessage.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusPostChangeMessage.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusUserChangeMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusPostProviderApiImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusAdminUserData.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusAdminUserData.java new file mode 100644 index 00000000..067207c4 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusAdminUserData.java @@ -0,0 +1,132 @@ +package com.zt.plat.module.databus.api.data; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 用户数据对象(DataBus) + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "用户数据") +public class DatabusAdminUserData implements Serializable { + + private static final long serialVersionUID = 1L; + + // ========== 基本信息 ========== + + @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") + private Long id; + + @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zhangsan") + private String username; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String nickname; + + @Schema(description = "用户手机号", example = "13800138000") + private String mobile; + + @Schema(description = "用户邮箱", example = "zhangsan@example.com") + private String email; + + @Schema(description = "用户性别:0-未知 1-男 2-女", example = "1") + private Integer sex; + + @Schema(description = "用户头像URL", example = "https://example.com/avatar.jpg") + private String avatar; + + @Schema(description = "状态:0-启用 1-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + + @Schema(description = "备注", example = "测试用户") + private String remark; + + @Schema(description = "用户来源类型", example = "1") + private Integer userSource; + + // ========== 组织信息 ========== + + @Schema(description = "所属部门ID列表", example = "[100, 101]") + private List deptIds; + + @Schema(description = "所属部门信息列表") + private List depts; + + // ========== 岗位信息 ========== + + @Schema(description = "岗位ID列表", example = "[1, 2]") + private List postIds; + + @Schema(description = "岗位信息列表") + private List posts; + + // ========== 多租户 ========== + + @Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long tenantId; + + // ========== 时间信息 ========== + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 部门简要信息 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "部门简要信息") + public static class DeptSimpleInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "部门ID", example = "100") + private Long deptId; + + @Schema(description = "部门编码", example = "DEPT_001") + private String deptCode; + + @Schema(description = "部门名称", example = "技术部") + private String deptName; + } + + /** + * 岗位简要信息 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "岗位简要信息") + public static class PostSimpleInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "岗位ID", example = "1") + private Long postId; + + @Schema(description = "岗位编码", example = "CEO") + private String postCode; + + @Schema(description = "岗位名称", example = "首席执行官") + private String postName; + } + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusDeptData.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusDeptData.java new file mode 100644 index 00000000..c12abeaa --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusDeptData.java @@ -0,0 +1,90 @@ +package com.zt.plat.module.databus.api.data; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 部门数据对象(DataBus) + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "部门数据") +public class DatabusDeptData implements Serializable { + + private static final long serialVersionUID = 1L; + + // ========== 基本信息 ========== + + @Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Long id; + + @Schema(description = "部门编码", example = "DEPT_001") + private String code; + + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部") + private String name; + + @Schema(description = "部门简称", example = "技术") + private String shortName; + + @Schema(description = "父部门ID(顶级为0)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Long parentId; + + @Schema(description = "排序号", example = "1") + private Integer sort; + + @Schema(description = "状态:0-启用 1-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + + // ========== 部门类型 ========== + + @Schema(description = "部门类型:28-公司 26-部门", example = "26") + private Integer deptType; + + @Schema(description = "是否集团", example = "false") + private Boolean isGroup; + + @Schema(description = "是否公司", example = "false") + private Boolean isCompany; + + @Schema(description = "部门来源类型", example = "1") + private Integer deptSource; + + // ========== 联系信息 ========== + + @Schema(description = "负责人ID", example = "1001") + private Long leaderUserId; + + @Schema(description = "负责人姓名", example = "张三") + private String leaderUserName; + + @Schema(description = "联系电话", example = "010-12345678") + private String phone; + + @Schema(description = "邮箱", example = "tech@example.com") + private String email; + + // ========== 多租户 ========== + + @Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long tenantId; + + // ========== 时间信息 ========== + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusPostData.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusPostData.java new file mode 100644 index 00000000..de4361fb --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/data/DatabusPostData.java @@ -0,0 +1,67 @@ +package com.zt.plat.module.databus.api.data; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 岗位数据对象(DataBus) + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusPostData implements Serializable { + + /** + * 岗位ID + */ + private Long id; + + /** + * 岗位编码 + */ + private String code; + + /** + * 岗位名称 + */ + private String name; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageReqDTO.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageReqDTO.java new file mode 100644 index 00000000..8182e677 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageReqDTO.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.databus.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 游标分页请求 DTO + *

+ * 用于 Databus 全量同步时的断点续传 + * 使用 create_time + id 复合游标解决雪花ID非连续问题 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "游标分页请求") +public class CursorPageReqDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "游标时间(上一批最后一条数据的创建时间)", example = "2024-01-01T00:00:00") + private LocalDateTime cursorTime; + + @Schema(description = "游标ID(上一批最后一条数据的ID,用于同一时间戳内去重)", example = "1234567890") + private Long cursorId; + + @Schema(description = "批量大小", example = "100") + private Integer batchSize; + + @Schema(description = "租户ID(可选,用于多租户过滤)", example = "1") + private Long tenantId; + + /** + * 是否为首次查询(无游标) + */ + public boolean isFirstPage() { + return cursorTime == null && cursorId == null; + } + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageResult.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageResult.java new file mode 100644 index 00000000..e3bc4a36 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/CursorPageResult.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.databus.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 游标分页结果 DTO + *

+ * 用于 Databus 全量同步时的断点续传响应 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "游标分页结果") +public class CursorPageResult implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "数据列表") + private List list; + + @Schema(description = "下一页游标时间(最后一条数据的创建时间)") + private LocalDateTime nextCursorTime; + + @Schema(description = "下一页游标ID(最后一条数据的ID)") + private Long nextCursorId; + + @Schema(description = "本次返回的数据量") + private Integer count; + + @Schema(description = "是否还有更多数据") + private Boolean hasMore; + + @Schema(description = "总数据量(可选,首次查询时返回)") + private Long total; + + /** + * 构建首页结果 + */ + public static CursorPageResult of(List list, LocalDateTime nextCursorTime, + Long nextCursorId, boolean hasMore, Long total) { + return CursorPageResult.builder() + .list(list) + .nextCursorTime(nextCursorTime) + .nextCursorId(nextCursorId) + .count(list != null ? list.size() : 0) + .hasMore(hasMore) + .total(total) + .build(); + } + + /** + * 构建后续页结果 + */ + public static CursorPageResult of(List list, LocalDateTime nextCursorTime, + Long nextCursorId, boolean hasMore) { + return of(list, nextCursorTime, nextCursorId, hasMore, null); + } + + /** + * 构建空结果 + */ + public static CursorPageResult empty() { + return CursorPageResult.builder() + .list(List.of()) + .count(0) + .hasMore(false) + .build(); + } + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusDeptProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusDeptProviderApi.java new file mode 100644 index 00000000..a9cafe03 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusDeptProviderApi.java @@ -0,0 +1,71 @@ +package com.zt.plat.module.databus.api.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Databus 部门数据提供者 API + *

+ * 供 Databus 调用,获取部门数据用于全量/增量同步 + * + * @author ZT + */ +@FeignClient(name = "${databus.provider.dept.service:system-server}") +@Tag(name = "RPC 服务 - Databus 部门数据提供者") +public interface DatabusDeptProviderApi { + + String PREFIX = "/rpc/databus/dept"; + + /** + * 游标分页查询部门数据(用于全量同步) + * + * @param reqDTO 游标分页请求 + * @return 部门数据分页结果 + */ + @PostMapping(PREFIX + "/page-by-cursor") + @Operation(summary = "游标分页查询部门数据") + CommonResult> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO); + + /** + * 根据ID查询部门详情(用于增量同步) + * + * @param id 部门ID + * @return 部门数据 + */ + @GetMapping(PREFIX + "/get") + @Operation(summary = "查询部门详情") + @Parameter(name = "id", description = "部门ID", required = true, example = "100") + CommonResult getById(@RequestParam("id") Long id); + + /** + * 批量查询部门详情(用于增量同步批量获取) + * + * @param ids 部门ID列表 + * @return 部门数据列表 + */ + @GetMapping(PREFIX + "/list") + @Operation(summary = "批量查询部门详情") + @Parameter(name = "ids", description = "部门ID列表", required = true, example = "100,101,102") + CommonResult> getListByIds(@RequestParam("ids") List ids); + + /** + * 统计部门总数(用于全量同步进度计算) + * + * @param tenantId 租户ID(可选) + * @return 部门总数 + */ + @GetMapping(PREFIX + "/count") + @Operation(summary = "统计部门总数") + @Parameter(name = "tenantId", description = "租户ID", example = "1") + CommonResult count(@RequestParam(value = "tenantId", required = false) Long tenantId); + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusPostProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusPostProviderApi.java new file mode 100644 index 00000000..632c312b --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusPostProviderApi.java @@ -0,0 +1,71 @@ +package com.zt.plat.module.databus.api.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Databus 岗位数据提供者 API + *

+ * 供 Databus 调用,获取岗位数据用于全量/增量同步 + * + * @author ZT + */ +@FeignClient(name = "${databus.provider.post.service:system-server}") +@Tag(name = "RPC 服务 - Databus 岗位数据提供者") +public interface DatabusPostProviderApi { + + String PREFIX = "/rpc/databus/post"; + + /** + * 游标分页查询岗位数据(用于全量同步) + * + * @param reqDTO 游标分页请求 + * @return 岗位数据分页结果 + */ + @PostMapping(PREFIX + "/page-by-cursor") + @Operation(summary = "游标分页查询岗位数据") + CommonResult> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO); + + /** + * 根据ID查询岗位详情(用于增量同步) + * + * @param id 岗位ID + * @return 岗位数据 + */ + @GetMapping(PREFIX + "/get") + @Operation(summary = "查询岗位详情") + @Parameter(name = "id", description = "岗位ID", required = true, example = "1") + CommonResult getById(@RequestParam("id") Long id); + + /** + * 批量查询岗位详情(用于增量同步批量获取) + * + * @param ids 岗位ID列表 + * @return 岗位数据列表 + */ + @GetMapping(PREFIX + "/list") + @Operation(summary = "批量查询岗位详情") + @Parameter(name = "ids", description = "岗位ID列表", required = true, example = "1,2,3") + CommonResult> getListByIds(@RequestParam("ids") List ids); + + /** + * 统计岗位总数(用于全量同步进度计算) + * + * @param tenantId 租户ID(可选) + * @return 岗位总数 + */ + @GetMapping(PREFIX + "/count") + @Operation(summary = "统计岗位总数") + @Parameter(name = "tenantId", description = "租户ID", example = "1") + CommonResult count(@RequestParam(value = "tenantId", required = false) Long tenantId); + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserProviderApi.java new file mode 100644 index 00000000..96061c43 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserProviderApi.java @@ -0,0 +1,71 @@ +package com.zt.plat.module.databus.api.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Databus 用户数据提供者 API + *

+ * 供 Databus 调用,获取用户数据用于全量/增量同步 + * + * @author ZT + */ +@FeignClient(name = "${databus.provider.user.service:system-server}") +@Tag(name = "RPC 服务 - Databus 用户数据提供者") +public interface DatabusUserProviderApi { + + String PREFIX = "/rpc/databus/user"; + + /** + * 游标分页查询用户数据(用于全量同步) + * + * @param reqDTO 游标分页请求 + * @return 用户数据分页结果 + */ + @PostMapping(PREFIX + "/page-by-cursor") + @Operation(summary = "游标分页查询用户数据") + CommonResult> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO); + + /** + * 根据ID查询用户详情(用于增量同步) + * + * @param id 用户ID + * @return 用户数据 + */ + @GetMapping(PREFIX + "/get") + @Operation(summary = "查询用户详情") + @Parameter(name = "id", description = "用户ID", required = true, example = "1001") + CommonResult getById(@RequestParam("id") Long id); + + /** + * 批量查询用户详情(用于增量同步批量获取) + * + * @param ids 用户ID列表 + * @return 用户数据列表 + */ + @GetMapping(PREFIX + "/list") + @Operation(summary = "批量查询用户详情") + @Parameter(name = "ids", description = "用户ID列表", required = true, example = "1001,1002,1003") + CommonResult> getListByIds(@RequestParam("ids") List ids); + + /** + * 统计用户总数(用于全量同步进度计算) + * + * @param tenantId 租户ID(可选) + * @return 用户总数 + */ + @GetMapping(PREFIX + "/count") + @Operation(summary = "统计用户总数") + @Parameter(name = "tenantId", description = "租户ID", example = "1") + CommonResult count(@RequestParam(value = "tenantId", required = false) Long tenantId); + +} diff --git a/zt-module-system/zt-module-system-api/pom.xml b/zt-module-system/zt-module-system-api/pom.xml index f8862e90..0e3e130f 100644 --- a/zt-module-system/zt-module-system-api/pom.xml +++ b/zt-module-system/zt-module-system-api/pom.xml @@ -22,6 +22,13 @@ zt-common + + + com.zt.plat + zt-module-databus-api + ${revision} + + org.springdoc diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusDeptChangeMessage.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusDeptChangeMessage.java new file mode 100644 index 00000000..5bc3bce3 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusDeptChangeMessage.java @@ -0,0 +1,160 @@ +package com.zt.plat.module.system.api.mq; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Databus 部门变更消息 + *

+ * 用于跨服务传递部门变更通知 + * + * @author ZT + */ +@Data +@Accessors(chain = true) +public class DatabusDeptChangeMessage implements Serializable { + + /** + * 消息 Topic + */ + public static final String TOPIC = "databus-change-system-dept"; + + /** + * 事件动作:create-创建 update-更新 delete-删除 + */ + private String action; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 部门编码 + */ + private String deptCode; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 部门简称 + */ + private String shortName; + + /** + * 上级部门ID + */ + private Long parentId; + + /** + * 排序 + */ + private Integer sort; + + /** + * 负责人用户ID + */ + private Long leaderUserId; + + /** + * 联系电话 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 是否公司 + */ + private Boolean isCompany; + + /** + * 是否集团 + */ + private Boolean isGroup; + + /** + * 部门来源类型 + */ + private Integer deptSource; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + // ==================== 静态工厂方法 ==================== + + public static DatabusDeptChangeMessage create(Long deptId, String deptCode, String deptName, String shortName, + Long parentId, Integer sort, Long leaderUserId, String phone, + String email, Integer status, Boolean isCompany, Boolean isGroup, + Integer deptSource, Long tenantId, LocalDateTime createTime) { + return new DatabusDeptChangeMessage() + .setAction("create") + .setDeptId(deptId) + .setDeptCode(deptCode) + .setDeptName(deptName) + .setShortName(shortName) + .setParentId(parentId) + .setSort(sort) + .setLeaderUserId(leaderUserId) + .setPhone(phone) + .setEmail(email) + .setStatus(status) + .setIsCompany(isCompany) + .setIsGroup(isGroup) + .setDeptSource(deptSource) + .setTenantId(tenantId) + .setEventTime(createTime != null ? createTime : LocalDateTime.now()); + } + + public static DatabusDeptChangeMessage update(Long deptId, String deptCode, String deptName, String shortName, + Long parentId, Integer sort, Long leaderUserId, String phone, + String email, Integer status, Boolean isCompany, Boolean isGroup, + Integer deptSource, Long tenantId, LocalDateTime updateTime) { + return new DatabusDeptChangeMessage() + .setAction("update") + .setDeptId(deptId) + .setDeptCode(deptCode) + .setDeptName(deptName) + .setShortName(shortName) + .setParentId(parentId) + .setSort(sort) + .setLeaderUserId(leaderUserId) + .setPhone(phone) + .setEmail(email) + .setStatus(status) + .setIsCompany(isCompany) + .setIsGroup(isGroup) + .setDeptSource(deptSource) + .setTenantId(tenantId) + .setEventTime(updateTime != null ? updateTime : LocalDateTime.now()); + } + + public static DatabusDeptChangeMessage delete(Long deptId, Long tenantId) { + return new DatabusDeptChangeMessage() + .setAction("delete") + .setDeptId(deptId) + .setTenantId(tenantId) + .setEventTime(LocalDateTime.now()); + } +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusPostChangeMessage.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusPostChangeMessage.java new file mode 100644 index 00000000..1d5803ed --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusPostChangeMessage.java @@ -0,0 +1,109 @@ +package com.zt.plat.module.system.api.mq; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Databus 岗位变更消息 + *

+ * 用于跨服务传递岗位变更通知 + * + * @author ZT + */ +@Data +@Accessors(chain = true) +public class DatabusPostChangeMessage implements Serializable { + + /** + * 消息 Topic + */ + public static final String TOPIC = "databus-change-system-post"; + + /** + * 事件动作:create-创建 update-更新 delete-删除 + */ + private String action; + + /** + * 岗位ID + */ + private Long postId; + + /** + * 岗位编码 + */ + private String postCode; + + /** + * 岗位名称 + */ + private String postName; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 租户ID(PostDO 不支持多租户,固定为 null) + */ + private Long tenantId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + // ==================== 静态工厂方法 ==================== + + public static DatabusPostChangeMessage create(Long postId, String postCode, String postName, + Integer sort, Integer status, String remark, + LocalDateTime createTime) { + return new DatabusPostChangeMessage() + .setAction("create") + .setPostId(postId) + .setPostCode(postCode) + .setPostName(postName) + .setSort(sort) + .setStatus(status) + .setRemark(remark) + .setTenantId(null) // PostDO 不支持多租户 + .setEventTime(createTime != null ? createTime : LocalDateTime.now()); + } + + public static DatabusPostChangeMessage update(Long postId, String postCode, String postName, + Integer sort, Integer status, String remark, + LocalDateTime updateTime) { + return new DatabusPostChangeMessage() + .setAction("update") + .setPostId(postId) + .setPostCode(postCode) + .setPostName(postName) + .setSort(sort) + .setStatus(status) + .setRemark(remark) + .setTenantId(null) // PostDO 不支持多租户 + .setEventTime(updateTime != null ? updateTime : LocalDateTime.now()); + } + + public static DatabusPostChangeMessage delete(Long postId) { + return new DatabusPostChangeMessage() + .setAction("delete") + .setPostId(postId) + .setTenantId(null) // PostDO 不支持多租户 + .setEventTime(LocalDateTime.now()); + } +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusUserChangeMessage.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusUserChangeMessage.java new file mode 100644 index 00000000..9209b6f8 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/mq/DatabusUserChangeMessage.java @@ -0,0 +1,154 @@ +package com.zt.plat.module.system.api.mq; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Set; + +/** + * Databus 用户变更消息 + *

+ * 用于跨服务传递用户变更通知 + * + * @author ZT + */ +@Data +@Accessors(chain = true) +public class DatabusUserChangeMessage implements Serializable { + + /** + * 消息 Topic + */ + public static final String TOPIC = "databus-change-system-user"; + + /** + * 事件动作:create-创建 update-更新 delete-删除 + */ + private String action; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 备注 + */ + private String remark; + + /** + * 部门ID集合 + */ + private Set deptIds; + + /** + * 岗位ID集合 + */ + private Set postIds; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String mobile; + + /** + * 用户性别(0未知 1男 2女) + */ + private Integer sex; + + /** + * 头像地址 + */ + private String avatar; + + /** + * 状态(0正常 1停用) + */ + private Integer status; + + /** + * 用户来源类型 + */ + private Integer userSource; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + // ==================== 静态工厂方法 ==================== + + public static DatabusUserChangeMessage create(Long userId, String username, String nickname, String remark, + Set deptIds, Set postIds, String email, String mobile, + Integer sex, String avatar, Integer status, Integer userSource, + Long tenantId, LocalDateTime createTime) { + return new DatabusUserChangeMessage() + .setAction("create") + .setUserId(userId) + .setUsername(username) + .setNickname(nickname) + .setRemark(remark) + .setDeptIds(deptIds) + .setPostIds(postIds) + .setEmail(email) + .setMobile(mobile) + .setSex(sex) + .setAvatar(avatar) + .setStatus(status) + .setUserSource(userSource) + .setTenantId(tenantId) + .setEventTime(createTime != null ? createTime : LocalDateTime.now()); + } + + public static DatabusUserChangeMessage update(Long userId, String username, String nickname, String remark, + Set deptIds, Set postIds, String email, String mobile, + Integer sex, String avatar, Integer status, Integer userSource, + Long tenantId, LocalDateTime updateTime) { + return new DatabusUserChangeMessage() + .setAction("update") + .setUserId(userId) + .setUsername(username) + .setNickname(nickname) + .setRemark(remark) + .setDeptIds(deptIds) + .setPostIds(postIds) + .setEmail(email) + .setMobile(mobile) + .setSex(sex) + .setAvatar(avatar) + .setStatus(status) + .setUserSource(userSource) + .setTenantId(tenantId) + .setEventTime(updateTime != null ? updateTime : LocalDateTime.now()); + } + + public static DatabusUserChangeMessage delete(Long userId, Long tenantId) { + return new DatabusUserChangeMessage() + .setAction("delete") + .setUserId(userId) + .setTenantId(tenantId) + .setEventTime(LocalDateTime.now()); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java new file mode 100644 index 00000000..909845dc --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java @@ -0,0 +1,218 @@ +package com.zt.plat.module.system.api.databus; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; +import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; +import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * Databus 部门数据提供者 API 实现 + * + * @author ZT + */ +@Slf4j +@RestController +@Validated +public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi { + + @Resource + private DeptMapper deptMapper; + + @Resource + private AdminUserMapper userMapper; + + @Override + public CommonResult> getPageByCursor(CursorPageReqDTO reqDTO) { + // 构建游标查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) + if (!reqDTO.isFirstPage()) { + queryWrapper.and(w -> w + .gt(DeptDO::getCreateTime, reqDTO.getCursorTime()) + .or(o -> o + .eq(DeptDO::getCreateTime, reqDTO.getCursorTime()) + .gt(DeptDO::getId, reqDTO.getCursorId()) + ) + ); + } + + // 租户过滤(如果指定) + if (reqDTO.getTenantId() != null) { + queryWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId()); + } + + // 按 create_time, id 升序排列,确保顺序稳定 + queryWrapper.orderByAsc(DeptDO::getCreateTime) + .orderByAsc(DeptDO::getId); + + // 多查一条判断是否有更多数据 + int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100; + queryWrapper.last("LIMIT " + (limit + 1)); + + List deptList = deptMapper.selectList(queryWrapper); + + // 判断是否有更多 + boolean hasMore = deptList.size() > limit; + if (hasMore) { + deptList = deptList.subList(0, limit); + } + + if (CollUtil.isEmpty(deptList)) { + return success(CursorPageResult.empty()); + } + + // 收集负责人用户ID + Set leaderUserIds = deptList.stream() + .map(DeptDO::getLeaderUserId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 批量查询负责人用户名 + Map userNameMap = new HashMap<>(); + if (CollUtil.isNotEmpty(leaderUserIds)) { + List users = userMapper.selectBatchIds(leaderUserIds); + userNameMap = users.stream() + .collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1)); + } + + // 转换为同步数据 + Map finalUserNameMap = userNameMap; + List dataList = deptList.stream() + .map(dept -> convertToDatabusDeptData(dept, finalUserNameMap)) + .collect(Collectors.toList()); + + // 获取最后一条数据的游标 + DeptDO lastDept = deptList.get(deptList.size() - 1); + + // 首次查询时返回总数 + Long total = null; + if (reqDTO.isFirstPage()) { + LambdaQueryWrapper countWrapper = new LambdaQueryWrapper<>(); + if (reqDTO.getTenantId() != null) { + countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId()); + } + total = deptMapper.selectCount(countWrapper); + } + + return success(CursorPageResult.of( + dataList, + lastDept.getCreateTime(), + lastDept.getId(), + hasMore, + total + )); + } + + @Override + public CommonResult getById(Long id) { + DeptDO dept = deptMapper.selectById(id); + if (dept == null) { + return success(null); + } + + // 查询负责人用户名 + Map userNameMap = new HashMap<>(); + if (dept.getLeaderUserId() != null) { + AdminUserDO user = userMapper.selectById(dept.getLeaderUserId()); + if (user != null) { + userNameMap.put(user.getId(), user.getNickname()); + } + } + + return success(convertToDatabusDeptData(dept, userNameMap)); + } + + @Override + public CommonResult> getListByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return success(Collections.emptyList()); + } + + List deptList = deptMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(deptList)) { + return success(Collections.emptyList()); + } + + // 收集负责人用户ID + Set leaderUserIds = deptList.stream() + .map(DeptDO::getLeaderUserId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 批量查询负责人用户名 + Map userNameMap = new HashMap<>(); + if (CollUtil.isNotEmpty(leaderUserIds)) { + List users = userMapper.selectBatchIds(leaderUserIds); + userNameMap = users.stream() + .collect(Collectors.toMap(AdminUserDO::getId, AdminUserDO::getNickname, (v1, v2) -> v1)); + } + + Map finalUserNameMap = userNameMap; + List dataList = deptList.stream() + .map(dept -> convertToDatabusDeptData(dept, finalUserNameMap)) + .collect(Collectors.toList()); + + return success(dataList); + } + + @Override + public CommonResult count(Long tenantId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (tenantId != null) { + queryWrapper.eq(DeptDO::getTenantId, tenantId); + } + return success(deptMapper.selectCount(queryWrapper)); + } + + /** + * 将 DeptDO 转换为 DatabusDeptData + */ + private DatabusDeptData convertToDatabusDeptData(DeptDO dept, Map userNameMap) { + // 根据 isCompany 反推 deptType + Integer deptType = null; + if (Boolean.TRUE.equals(dept.getIsCompany())) { + deptType = 28; // 公司 + } else if (Boolean.FALSE.equals(dept.getIsCompany())) { + deptType = 26; // 部门 + } + + return DatabusDeptData.builder() + .id(dept.getId()) + .code(dept.getCode()) + .name(dept.getName()) + .shortName(dept.getShortName()) + .parentId(dept.getParentId()) + .sort(dept.getSort()) + .status(dept.getStatus()) + .deptType(deptType) + .isGroup(dept.getIsGroup()) + .isCompany(dept.getIsCompany()) + .deptSource(dept.getDeptSource()) + .leaderUserId(dept.getLeaderUserId()) + .leaderUserName(dept.getLeaderUserId() != null ? userNameMap.get(dept.getLeaderUserId()) : null) + .phone(dept.getPhone()) + .email(dept.getEmail()) + .tenantId(dept.getTenantId()) + .createTime(dept.getCreateTime()) + .updateTime(dept.getUpdateTime()) + .build(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusPostProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusPostProviderApiImpl.java new file mode 100644 index 00000000..5e31b2f9 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusPostProviderApiImpl.java @@ -0,0 +1,148 @@ +package com.zt.plat.module.system.api.databus; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.provider.DatabusPostProviderApi; +import com.zt.plat.module.system.dal.dataobject.dept.PostDO; +import com.zt.plat.module.system.dal.mysql.dept.PostMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * Databus 岗位数据提供者 API 实现 + * + * @author ZT + */ +@Slf4j +@RestController +@Validated +public class DatabusPostProviderApiImpl implements DatabusPostProviderApi { + + @Resource + private PostMapper postMapper; + + @Override + public CommonResult> getPageByCursor(CursorPageReqDTO reqDTO) { + // 构建游标查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) + if (!reqDTO.isFirstPage()) { + queryWrapper.and(w -> w + .gt(PostDO::getCreateTime, reqDTO.getCursorTime()) + .or(o -> o + .eq(PostDO::getCreateTime, reqDTO.getCursorTime()) + .gt(PostDO::getId, reqDTO.getCursorId()) + ) + ); + } + + // PostDO 不支持多租户,忽略租户过滤 + + // 按 create_time, id 升序排列,确保顺序稳定 + queryWrapper.orderByAsc(PostDO::getCreateTime) + .orderByAsc(PostDO::getId); + + // 多查一条判断是否有更多数据 + int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100; + queryWrapper.last("LIMIT " + (limit + 1)); + + List postList = postMapper.selectList(queryWrapper); + + // 判断是否有更多 + boolean hasMore = postList.size() > limit; + if (hasMore) { + postList = postList.subList(0, limit); + } + + if (CollUtil.isEmpty(postList)) { + return success(CursorPageResult.empty()); + } + + // 转换为同步数据 + List dataList = postList.stream() + .map(this::convertToDatabusPostData) + .collect(Collectors.toList()); + + // 获取最后一条数据的游标 + PostDO lastPost = postList.get(postList.size() - 1); + + // 首次查询时返回总数 + Long total = null; + if (reqDTO.isFirstPage()) { + total = postMapper.selectCount(new LambdaQueryWrapper<>()); + } + + return success(CursorPageResult.of( + dataList, + lastPost.getCreateTime(), + lastPost.getId(), + hasMore, + total + )); + } + + @Override + public CommonResult getById(Long id) { + PostDO post = postMapper.selectById(id); + if (post == null) { + return success(null); + } + + return success(convertToDatabusPostData(post)); + } + + @Override + public CommonResult> getListByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return success(Collections.emptyList()); + } + + List postList = postMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(postList)) { + return success(Collections.emptyList()); + } + + List dataList = postList.stream() + .map(this::convertToDatabusPostData) + .collect(Collectors.toList()); + + return success(dataList); + } + + @Override + public CommonResult count(Long tenantId) { + // PostDO 不支持多租户,忽略租户参数 + return success(postMapper.selectCount(new LambdaQueryWrapper<>())); + } + + /** + * 将 PostDO 转换为 DatabusPostData + */ + private DatabusPostData convertToDatabusPostData(PostDO post) { + return DatabusPostData.builder() + .id(post.getId()) + .code(post.getCode()) + .name(post.getName()) + .sort(post.getSort()) + .status(post.getStatus()) + .remark(post.getRemark()) + .tenantId(null) // PostDO 不支持多租户 + .createTime(post.getCreateTime()) + .updateTime(post.getUpdateTime()) + .build(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java new file mode 100644 index 00000000..286b9984 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java @@ -0,0 +1,267 @@ +package com.zt.plat.module.system.api.databus; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData.DeptSimpleInfo; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData.PostSimpleInfo; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.provider.DatabusUserProviderApi; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.dept.PostDO; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; +import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO; +import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; +import com.zt.plat.module.system.dal.mysql.dept.PostMapper; +import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper; +import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * Databus 用户数据提供者 API 实现 + * + * @author ZT + */ +@Slf4j +@RestController +@Validated +public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { + + @Resource + private AdminUserMapper userMapper; + + @Resource + private UserDeptMapper userDeptMapper; + + @Resource + private DeptMapper deptMapper; + + @Resource + private PostMapper postMapper; + + @Override + public CommonResult> getPageByCursor(CursorPageReqDTO reqDTO) { + // 构建游标查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + + // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) + if (!reqDTO.isFirstPage()) { + queryWrapper.and(w -> w + .gt(AdminUserDO::getCreateTime, reqDTO.getCursorTime()) + .or(o -> o + .eq(AdminUserDO::getCreateTime, reqDTO.getCursorTime()) + .gt(AdminUserDO::getId, reqDTO.getCursorId()) + ) + ); + } + + // 租户过滤(如果指定) + if (reqDTO.getTenantId() != null) { + queryWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId()); + } + + // 按 create_time, id 升序排列,确保顺序稳定 + queryWrapper.orderByAsc(AdminUserDO::getCreateTime) + .orderByAsc(AdminUserDO::getId); + + // 多查一条判断是否有更多数据 + int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100; + queryWrapper.last("LIMIT " + (limit + 1)); + + List userList = userMapper.selectList(queryWrapper); + + // 判断是否有更多 + boolean hasMore = userList.size() > limit; + if (hasMore) { + userList = userList.subList(0, limit); + } + + if (CollUtil.isEmpty(userList)) { + return success(CursorPageResult.empty()); + } + + // 批量聚合用户关联数据 + List dataList = aggregateUserData(userList); + + // 获取最后一条数据的游标 + AdminUserDO lastUser = userList.get(userList.size() - 1); + + // 首次查询时返回总数 + Long total = null; + if (reqDTO.isFirstPage()) { + LambdaQueryWrapper countWrapper = new LambdaQueryWrapper<>(); + if (reqDTO.getTenantId() != null) { + countWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId()); + } + total = userMapper.selectCount(countWrapper); + } + + return success(CursorPageResult.of( + dataList, + lastUser.getCreateTime(), + lastUser.getId(), + hasMore, + total + )); + } + + @Override + public CommonResult getById(Long id) { + AdminUserDO user = userMapper.selectById(id); + if (user == null) { + return success(null); + } + + List dataList = aggregateUserData(Collections.singletonList(user)); + return success(dataList.isEmpty() ? null : dataList.get(0)); + } + + @Override + public CommonResult> getListByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return success(Collections.emptyList()); + } + + List userList = userMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(userList)) { + return success(Collections.emptyList()); + } + + return success(aggregateUserData(userList)); + } + + @Override + public CommonResult count(Long tenantId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (tenantId != null) { + queryWrapper.eq(AdminUserDO::getTenantId, tenantId); + } + return success(userMapper.selectCount(queryWrapper)); + } + + /** + * 批量聚合用户关联数据(部门、岗位) + */ + private List aggregateUserData(List userList) { + if (CollUtil.isEmpty(userList)) { + return Collections.emptyList(); + } + + // 收集所有用户ID + List userIds = userList.stream() + .map(AdminUserDO::getId) + .collect(Collectors.toList()); + + // 批量查询用户-部门关系 + List userDeptList = userDeptMapper.selectValidListByUserIds(userIds); + Map> userDeptIdsMap = userDeptList.stream() + .collect(Collectors.groupingBy( + UserDeptDO::getUserId, + Collectors.mapping(UserDeptDO::getDeptId, Collectors.toList()) + )); + + // 收集所有部门ID + Set allDeptIds = userDeptList.stream() + .map(UserDeptDO::getDeptId) + .collect(Collectors.toSet()); + + // 批量查询部门信息 + Map deptMap = new HashMap<>(); + if (CollUtil.isNotEmpty(allDeptIds)) { + List deptList = deptMapper.selectBatchIds(allDeptIds); + deptMap = deptList.stream() + .collect(Collectors.toMap(DeptDO::getId, d -> d, (v1, v2) -> v1)); + } + + // 收集所有岗位ID + Set allPostIds = userList.stream() + .map(AdminUserDO::getPostIds) + .filter(Objects::nonNull) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + + // 批量查询岗位信息 + Map postMap = new HashMap<>(); + if (CollUtil.isNotEmpty(allPostIds)) { + List postList = postMapper.selectBatchIds(allPostIds); + postMap = postList.stream() + .collect(Collectors.toMap(PostDO::getId, p -> p, (v1, v2) -> v1)); + } + + // 转换为同步数据 + Map finalDeptMap = deptMap; + Map finalPostMap = postMap; + + return userList.stream() + .map(user -> convertToDatabusAdminUserData(user, userDeptIdsMap, finalDeptMap, finalPostMap)) + .collect(Collectors.toList()); + } + + /** + * 将 AdminUserDO 转换为 DatabusAdminUserData + */ + private DatabusAdminUserData convertToDatabusAdminUserData(AdminUserDO user, + Map> userDeptIdsMap, + Map deptMap, + Map postMap) { + // 获取用户的部门ID列表 + List deptIds = userDeptIdsMap.getOrDefault(user.getId(), Collections.emptyList()); + + // 构建部门简要信息列表 + List depts = deptIds.stream() + .map(deptMap::get) + .filter(Objects::nonNull) + .map(dept -> DeptSimpleInfo.builder() + .deptId(dept.getId()) + .deptCode(dept.getCode()) + .deptName(dept.getName()) + .build()) + .collect(Collectors.toList()); + + // 获取用户的岗位ID列表 + Set postIds = user.getPostIds(); + List postIdList = postIds != null ? new ArrayList<>(postIds) : Collections.emptyList(); + + // 构建岗位简要信息列表 + List posts = postIdList.stream() + .map(postMap::get) + .filter(Objects::nonNull) + .map(post -> PostSimpleInfo.builder() + .postId(post.getId()) + .postCode(post.getCode()) + .postName(post.getName()) + .build()) + .collect(Collectors.toList()); + + return DatabusAdminUserData.builder() + .id(user.getId()) + .username(user.getUsername()) + .nickname(user.getNickname()) + .mobile(user.getMobile()) + .email(user.getEmail()) + .sex(user.getSex()) + .avatar(user.getAvatar()) + .status(user.getStatus()) + .remark(user.getRemark()) + .userSource(user.getUserSource()) + .deptIds(deptIds) + .depts(depts) + .postIds(postIdList) + .posts(posts) + .tenantId(user.getTenantId()) + .createTime(user.getCreateTime()) + .updateTime(user.getUpdateTime()) + .build(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java new file mode 100644 index 00000000..76c5b624 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java @@ -0,0 +1,193 @@ +package com.zt.plat.module.system.mq.producer.databus; + +import com.zt.plat.module.system.api.mq.DatabusDeptChangeMessage; +import com.zt.plat.module.system.api.mq.DatabusPostChangeMessage; +import com.zt.plat.module.system.api.mq.DatabusUserChangeMessage; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.dept.PostDO; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * Databus 数据变更消息生产者 + *

+ * 用于发送部门、用户、岗位变更消息到 MQ,供 databus-server 消费 + * + * @author ZT + */ +@Slf4j +@Component +public class DatabusChangeProducer { + + @Resource + private RocketMQTemplate rocketMQTemplate; + + // ==================== 部门变更消息 ==================== + + /** + * 发送部门创建消息 + */ + public void sendDeptCreatedMessage(DeptDO dept) { + DatabusDeptChangeMessage message = DatabusDeptChangeMessage.create( + dept.getId(), dept.getCode(), dept.getName(), dept.getShortName(), + dept.getParentId(), dept.getSort(), dept.getLeaderUserId(), dept.getPhone(), + dept.getEmail(), dept.getStatus(), dept.getIsCompany(), dept.getIsGroup(), + dept.getDeptSource(), dept.getTenantId(), dept.getCreateTime()); + sendDeptChangeMessage(message); + } + + /** + * 发送部门更新消息 + */ + public void sendDeptUpdatedMessage(DeptDO dept) { + DatabusDeptChangeMessage message = DatabusDeptChangeMessage.update( + dept.getId(), dept.getCode(), dept.getName(), dept.getShortName(), + dept.getParentId(), dept.getSort(), dept.getLeaderUserId(), dept.getPhone(), + dept.getEmail(), dept.getStatus(), dept.getIsCompany(), dept.getIsGroup(), + dept.getDeptSource(), dept.getTenantId(), dept.getUpdateTime()); + sendDeptChangeMessage(message); + } + + /** + * 发送部门删除消息 + */ + public void sendDeptDeletedMessage(Long deptId, Long tenantId) { + DatabusDeptChangeMessage message = DatabusDeptChangeMessage.delete(deptId, tenantId); + sendDeptChangeMessage(message); + } + + private void sendDeptChangeMessage(DatabusDeptChangeMessage message) { + try { + rocketMQTemplate.asyncSend(DatabusDeptChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { + @Override + public void onSuccess(org.apache.rocketmq.client.producer.SendResult sendResult) { + log.info("[Databus] 部门变更消息发送成功, action={}, deptId={}, msgId={}", + message.getAction(), message.getDeptId(), sendResult.getMsgId()); + } + + @Override + public void onException(Throwable e) { + log.error("[Databus] 部门变更消息发送失败, action={}, deptId={}", + message.getAction(), message.getDeptId(), e); + } + }); + } catch (Exception e) { + log.error("[Databus] 部门变更消息发送异常, action={}, deptId={}", + message.getAction(), message.getDeptId(), e); + } + } + + // ==================== 用户变更消息 ==================== + + /** + * 发送用户创建消息(完整数据) + */ + public void sendUserCreatedMessage(AdminUserDO user) { + DatabusUserChangeMessage message = DatabusUserChangeMessage.create( + user.getId(), user.getUsername(), user.getNickname(), + user.getRemark(), user.getDeptIds(), user.getPostIds(), + user.getEmail(), user.getMobile(), user.getSex(), user.getAvatar(), + user.getStatus(), user.getUserSource(), + user.getTenantId(), user.getCreateTime()); + sendUserChangeMessage(message); + } + + /** + * 发送用户更新消息(完整数据) + */ + public void sendUserUpdatedMessage(AdminUserDO user) { + DatabusUserChangeMessage message = DatabusUserChangeMessage.update( + user.getId(), user.getUsername(), user.getNickname(), + user.getRemark(), user.getDeptIds(), user.getPostIds(), + user.getEmail(), user.getMobile(), user.getSex(), user.getAvatar(), + user.getStatus(), user.getUserSource(), + user.getTenantId(), user.getUpdateTime()); + sendUserChangeMessage(message); + } + + /** + * 发送用户删除消息 + */ + public void sendUserDeletedMessage(Long userId, Long tenantId) { + DatabusUserChangeMessage message = DatabusUserChangeMessage.delete(userId, tenantId); + sendUserChangeMessage(message); + } + + private void sendUserChangeMessage(DatabusUserChangeMessage message) { + try { + rocketMQTemplate.asyncSend(DatabusUserChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { + @Override + public void onSuccess(org.apache.rocketmq.client.producer.SendResult sendResult) { + log.info("[Databus] 用户变更消息发送成功, action={}, userId={}, msgId={}", + message.getAction(), message.getUserId(), sendResult.getMsgId()); + } + + @Override + public void onException(Throwable e) { + log.error("[Databus] 用户变更消息发送失败, action={}, userId={}", + message.getAction(), message.getUserId(), e); + } + }); + } catch (Exception e) { + log.error("[Databus] 用户变更消息发送异常, action={}, userId={}", + message.getAction(), message.getUserId(), e); + } + } + + // ==================== 岗位变更消息 ==================== + + /** + * 发送岗位创建消息 + */ + public void sendPostCreatedMessage(PostDO post) { + DatabusPostChangeMessage message = DatabusPostChangeMessage.create( + post.getId(), post.getCode(), post.getName(), + post.getSort(), post.getStatus(), post.getRemark(), + post.getCreateTime()); + sendPostChangeMessage(message); + } + + /** + * 发送岗位更新消息 + */ + public void sendPostUpdatedMessage(PostDO post) { + DatabusPostChangeMessage message = DatabusPostChangeMessage.update( + post.getId(), post.getCode(), post.getName(), + post.getSort(), post.getStatus(), post.getRemark(), + post.getUpdateTime()); + sendPostChangeMessage(message); + } + + /** + * 发送岗位删除消息 + */ + public void sendPostDeletedMessage(Long postId) { + DatabusPostChangeMessage message = DatabusPostChangeMessage.delete(postId); + sendPostChangeMessage(message); + } + + private void sendPostChangeMessage(DatabusPostChangeMessage message) { + try { + rocketMQTemplate.asyncSend(DatabusPostChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { + @Override + public void onSuccess(org.apache.rocketmq.client.producer.SendResult sendResult) { + log.info("[Databus] 岗位变更消息发送成功, action={}, postId={}, msgId={}", + message.getAction(), message.getPostId(), sendResult.getMsgId()); + } + + @Override + public void onException(Throwable e) { + log.error("[Databus] 岗位变更消息发送失败, action={}, postId={}", + message.getAction(), message.getPostId(), e); + } + }); + } catch (Exception e) { + log.error("[Databus] 岗位变更消息发送异常, action={}, postId={}", + message.getAction(), message.getPostId(), e); + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index 0d1da94f..3cf54bee 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -50,6 +50,8 @@ public class DeptServiceImpl implements DeptService { private DeptMapper deptMapper; @Resource private UserDeptMapper userDeptMapper; + @Resource + private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; private static final String ROOT_CODE_PREFIX = "ZT"; private static final int CODE_SEGMENT_LENGTH = 3; @@ -99,6 +101,10 @@ public class DeptServiceImpl implements DeptService { dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); } deptMapper.insert(dept); + + // 发布部门创建事件 + databusChangeProducer.sendDeptCreatedMessage(dept); + return dept.getId(); } @@ -153,6 +159,12 @@ public class DeptServiceImpl implements DeptService { DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class); deptMapper.updateById(updateObj); + // 发布部门更新事件(重新查询获取完整数据) + DeptDO updatedDept = deptMapper.selectById(updateObj.getId()); + if (updatedDept != null) { + databusChangeProducer.sendDeptUpdatedMessage(updatedDept); + } + if (parentChanged) { refreshChildCodesRecursively(updateObj.getId(), updateReqVO.getCode()); } @@ -168,8 +180,16 @@ public class DeptServiceImpl implements DeptService { if (deptMapper.selectCountByParentId(id) > 0) { throw exception(DEPT_EXITS_CHILDREN); } + + // 删除前先查询部门信息(用于发布事件) + DeptDO dept = deptMapper.selectById(id); + Long tenantId = (dept != null) ? dept.getTenantId() : null; + // 删除部门 deptMapper.deleteById(id); + + // 发布部门删除事件 + databusChangeProducer.sendDeptDeletedMessage(id, tenantId); } @VisibleForTesting diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java index 4d551fce..ff0aa7b4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java @@ -34,6 +34,9 @@ public class PostServiceImpl implements PostService { @Resource private PostMapper postMapper; + @Resource + private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; + @Override public Long createPost(PostSaveReqVO createReqVO) { // 校验正确性 @@ -42,6 +45,10 @@ public class PostServiceImpl implements PostService { // 插入岗位 PostDO post = BeanUtils.toBean(createReqVO, PostDO.class); postMapper.insert(post); + + // 发布岗位创建事件 + databusChangeProducer.sendPostCreatedMessage(post); + return post.getId(); } @@ -53,14 +60,24 @@ public class PostServiceImpl implements PostService { // 更新岗位 PostDO updateObj = BeanUtils.toBean(updateReqVO, PostDO.class); postMapper.updateById(updateObj); + + // 发布岗位更新事件(重新查询获取完整数据) + PostDO updatedPost = postMapper.selectById(updateObj.getId()); + if (updatedPost != null) { + databusChangeProducer.sendPostUpdatedMessage(updatedPost); + } } @Override public void deletePost(Long id) { // 校验是否存在 validatePostExists(id); + // 删除部门 postMapper.deleteById(id); + + // 发布岗位删除事件(PostDO 不支持多租户,tenantId 为 null) + databusChangeProducer.sendPostDeletedMessage(id); } private void validatePostForCreateOrUpdate(Long id, String name, String code) { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java index db5a2176..91bf4b7d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java @@ -93,6 +93,9 @@ public class AdminUserServiceImpl implements AdminUserService { @Resource private ConfigApi configApi; + + @Resource + private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; @Resource private UserDeptService userDeptService; @@ -141,6 +144,9 @@ public class AdminUserServiceImpl implements AdminUserService { postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId))); } + // 2.4 发布用户创建事件 + databusChangeProducer.sendUserCreatedMessage(user); + // 3. 记录操作日志上下文 LogRecordContext.putVariable("user", user); return user.getId(); @@ -232,6 +238,12 @@ public class AdminUserServiceImpl implements AdminUserService { // 2.3 更新岗位 updateUserPost(updateReqVO, updateObj); + // 2.4 发布用户更新事件(重新查询获取完整数据) + AdminUserDO updatedUser = userMapper.selectById(updateObj.getId()); + if (updatedUser != null) { + databusChangeProducer.sendUserUpdatedMessage(updatedUser); + } + // 3. 记录操作日志上下文 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); LogRecordContext.putVariable("user", oldUser); @@ -321,6 +333,9 @@ public class AdminUserServiceImpl implements AdminUserService { // 2.2 删除用户岗位 userPostMapper.deleteByUserId(id); + // 2.3 发布用户删除事件 + databusChangeProducer.sendUserDeletedMessage(id, user.getTenantId()); + // 3. 记录操作日志上下文 LogRecordContext.putVariable("user", user); } From bd90ec9d70006a3b26127691e28b12efe3663fe5 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Mon, 1 Dec 2025 23:44:32 +0800 Subject: [PATCH 02/10] =?UTF-8?q?feat(databus):=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=E5=9B=9B-DataBus=20Server=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充缺失的 API 类(DatabusMessage、DatabusBatchMessage、DatabusEventType) - 新增变更消息消费者(3个:部门、用户、岗位) - 新增数据提供者(3个:部门、用户、岗位) - 确认分发器服务(核心定向推送逻辑) - 确认全量同步与消息推送组件 - 确认管理后台 API(5个 Controller) - 确认 Service ��(4个核心服务) - 确认 DAL 层(7个 DO + Mapper) - 添加 databus-server starter 依赖到 pom.xml - 编译验证通过 Ref: docs/databus/implementation-checklist.md 任务 39-70 --- .../pom.xml | 71 ++++ .../DatabusSyncClientAutoConfiguration.java | 23 ++ .../config/DatabusSyncClientProperties.java | 113 ++++++ .../controller/SyncMessageController.java | 43 +++ .../core/idempotent/IdempotentStore.java | 19 + .../core/idempotent/RedisIdempotentStore.java | 30 ++ .../listener/DatabusConsumerRegistry.java | 148 ++++++++ .../client/core/message/SyncMessage.java | 54 +++ .../core/processor/MessageProcessor.java | 236 +++++++++++++ .../client/handler/BatchSyncEventHandler.java | 51 +++ .../client/handler/SyncEventHandler.java | 57 +++ .../client/handler/post/PostSyncService.java | 108 ++++++ .../handler/post/SystemPostFullHandler.java | 71 ++++ .../main/resources/META-INF/spring.factories | 2 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../pom.xml | 73 ++++ .../config/DatabusMessagingConfiguration.java | 27 ++ .../DatabusServerAutoConfiguration.java | 24 ++ .../config/DatabusServerProperties.java | 60 ++++ .../DatabusSyncServerAutoConfiguration.java | 45 +++ .../config/DatabusSyncServerProperties.java | 61 ++++ .../admin/DatabusSyncClientController.java | 92 +++++ .../DatabusSyncDeadLetterController.java | 76 ++++ .../admin/DatabusSyncEventController.java | 92 +++++ .../admin/DatabusSyncFullTaskController.java | 102 ++++++ .../admin/DatabusSyncPushLogController.java | 69 ++++ .../DatabusSyncSubscriptionController.java | 100 ++++++ .../vo/client/DatabusSyncClientPageReqVO.java | 27 ++ .../vo/client/DatabusSyncClientRespVO.java | 63 ++++ .../vo/client/DatabusSyncClientSaveReqVO.java | 55 +++ .../DatabusSyncDeadLetterPageReqVO.java | 43 +++ .../DatabusSyncDeadLetterRespVO.java | 63 ++++ .../vo/event/DatabusSyncEventPageReqVO.java | 24 ++ .../vo/event/DatabusSyncEventRespVO.java | 54 +++ .../vo/event/DatabusSyncEventSaveReqVO.java | 45 +++ .../DatabusSyncFullTaskCreateReqVO.java | 18 + .../DatabusSyncFullTaskPageReqVO.java | 30 ++ .../fulltask/DatabusSyncFullTaskRespVO.java | 88 +++++ .../pushlog/DatabusSyncPushLogPageReqVO.java | 46 +++ .../vo/pushlog/DatabusSyncPushLogRespVO.java | 72 ++++ .../DatabusSyncSubscriptionPageReqVO.java | 24 ++ .../DatabusSyncSubscriptionRespVO.java | 78 +++++ .../DatabusSyncSubscriptionSaveReqVO.java | 39 +++ .../convert/DatabusSyncClientConvert.java | 25 ++ .../convert/DatabusSyncDeadLetterConvert.java | 22 ++ .../convert/DatabusSyncEventConvert.java | 25 ++ .../convert/DatabusSyncFullTaskConvert.java | 22 ++ .../convert/DatabusSyncPushLogConvert.java | 22 ++ .../DatabusSyncSubscriptionConvert.java | 25 ++ .../server/core/event/DatabusEvent.java | 71 ++++ .../server/core/message/BatchSyncMessage.java | 121 +++++++ .../server/core/message/SyncMessage.java | 54 +++ .../server/core/provider/DataProvider.java | 120 +++++++ .../core/provider/DataProviderRegistry.java | 62 ++++ .../core/publisher/DatabusEventPublisher.java | 28 ++ .../publisher/DatabusEventPublisherImpl.java | 60 ++++ .../server/core/pusher/MessagePusher.java | 50 +++ .../server/core/pusher/MessagePusherImpl.java | 103 ++++++ .../core/sync/DatabusFullSyncService.java | 51 +++ .../sync/DatabusIncrementalSyncService.java | 27 ++ .../DatabusIncrementalSyncServiceImpl.java | 246 +++++++++++++ .../server/core/sync/DatabusSyncService.java | 24 ++ .../core/sync/DatabusSyncServiceImpl.java | 237 +++++++++++++ .../dal/dataobject/DatabusSyncClientDO.java | 84 +++++ .../dataobject/DatabusSyncDeadLetterDO.java | 106 ++++++ .../dal/dataobject/DatabusSyncEventDO.java | 76 ++++ .../dataobject/DatabusSyncEventRecordDO.java | 87 +++++ .../dal/dataobject/DatabusSyncFullTaskDO.java | 126 +++++++ .../dal/dataobject/DatabusSyncLogDO.java | 126 +++++++ .../dataobject/DatabusSyncSubscriptionDO.java | 91 +++++ .../dal/mapper/DatabusSyncClientMapper.java | 38 ++ .../mapper/DatabusSyncDeadLetterMapper.java | 29 ++ .../dal/mapper/DatabusSyncEventMapper.java | 37 ++ .../mapper/DatabusSyncEventRecordMapper.java | 15 + .../dal/mapper/DatabusSyncFullTaskMapper.java | 62 ++++ .../dal/mapper/DatabusSyncLogMapper.java | 30 ++ .../mapper/DatabusSyncSubscriptionMapper.java | 41 +++ .../server/enums/DataProviderTypeEnum.java | 36 ++ .../server/enums/DeadLetterStatusEnum.java | 51 +++ .../server/enums/ErrorCodeConstants.java | 30 ++ .../server/enums/FullTaskStatusEnum.java | 75 ++++ .../databus/server/enums/SyncModeEnum.java | 46 +++ .../databus/server/enums/SyncStatusEnum.java | 56 +++ .../databus/server/enums/TimelinessEnum.java | 51 +++ .../server/enums/TransportTypeEnum.java | 46 +++ .../producer/DatabusMessageProducer.java | 195 +++++++++++ .../service/DatabusSyncClientService.java | 70 ++++ .../service/DatabusSyncDeadLetterService.java | 54 +++ .../service/DatabusSyncEventService.java | 70 ++++ .../server/service/DatabusSyncLogService.java | 37 ++ .../DatabusSyncSubscriptionService.java | 75 ++++ .../impl/DatabusFullSyncServiceImpl.java | 328 ++++++++++++++++++ .../impl/DatabusSyncClientServiceImpl.java | 115 ++++++ .../DatabusSyncDeadLetterServiceImpl.java | 99 ++++++ .../impl/DatabusSyncEventServiceImpl.java | 115 ++++++ .../impl/DatabusSyncLogServiceImpl.java | 50 +++ .../DatabusSyncSubscriptionServiceImpl.java | 129 +++++++ .../main/resources/META-INF/spring.factories | 2 + ...ot.autoconfigure.AutoConfiguration.imports | 2 + .../api/message/DatabusBatchMessage.java | 106 ++++++ .../databus/api/message/DatabusMessage.java | 73 ++++ .../databus/enums/DatabusEventType.java | 306 ++++++++++++++++ .../zt-module-databus-server/pom.xml | 7 + .../consumer/DatabusDeptChangeConsumer.java | 90 +++++ .../consumer/DatabusPostChangeConsumer.java | 73 ++++ .../consumer/DatabusUserChangeConsumer.java | 79 +++++ .../provider/DeptDataFeignProvider.java | 85 +++++ .../provider/PostDataFeignProvider.java | 81 +++++ .../provider/UserDataFeignProvider.java | 81 +++++ 109 files changed, 7673 insertions(+) create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/pom.xml create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientAutoConfiguration.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientProperties.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/controller/SyncMessageController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/IdempotentStore.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/RedisIdempotentStore.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/listener/DatabusConsumerRegistry.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/message/SyncMessage.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/processor/MessageProcessor.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/SyncEventHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring.factories create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/pom.xml create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusMessagingConfiguration.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerAutoConfiguration.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerProperties.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerAutoConfiguration.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerProperties.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncClientController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncDeadLetterController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncFullTaskController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncPushLogController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientSaveReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventSaveReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskCreateReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionPageReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionSaveReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncClientConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncDeadLetterConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncEventConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncFullTaskConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncPushLogConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncSubscriptionConvert.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/event/DatabusEvent.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/BatchSyncMessage.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/SyncMessage.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProvider.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProviderRegistry.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisher.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusher.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusherImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusFullSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncClientDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncDeadLetterDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventRecordDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncFullTaskDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncLogDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncSubscriptionDO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncClientMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncDeadLetterMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventRecordMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncFullTaskMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncLogMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DataProviderTypeEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DeadLetterStatusEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/ErrorCodeConstants.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/FullTaskStatusEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncModeEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncStatusEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TimelinessEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TransportTypeEnum.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/producer/DatabusMessageProducer.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncDeadLetterService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncLogService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring.factories create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusBatchMessage.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusMessage.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusPostChangeConsumer.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusUserChangeConsumer.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java diff --git a/zt-framework/zt-spring-boot-starter-databus-client/pom.xml b/zt-framework/zt-spring-boot-starter-databus-client/pom.xml new file mode 100644 index 00000000..08e8dcc8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/pom.xml @@ -0,0 +1,71 @@ + + + + com.zt.plat + zt-framework + ${revision} + + 4.0.0 + zt-spring-boot-starter-databus-client + jar + + ${project.artifactId} + DataBus 客户端组件,负责接收数据变更并同步 + https://github.com/YunaiV/ruoyi-vue-pro + + + + com.zt.plat + zt-common + + + + + com.zt.plat + zt-module-databus-api + ${revision} + + + + + com.zt.plat + zt-module-system-api + ${revision} + true + + + + cn.hutool + hutool-all + + + + + com.zt.plat + zt-spring-boot-starter-mq + + + + + com.zt.plat + zt-spring-boot-starter-redis + + + + + org.springframework.boot + spring-boot-starter-web + true + + + + + com.zt.plat + zt-spring-boot-starter-test + test + + + + diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientAutoConfiguration.java new file mode 100644 index 00000000..afa72c43 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.zt.plat.framework.databus.client.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +/** + * Databus 同步客户端自动配置 + * + * @author ZT + */ +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(DatabusSyncClientProperties.class) +@ComponentScan(basePackages = "com.zt.plat.framework.databus.client") +public class DatabusSyncClientAutoConfiguration { + + public DatabusSyncClientAutoConfiguration() { + log.info("[Databus] 数据同步客户端模块已加载"); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientProperties.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientProperties.java new file mode 100644 index 00000000..f79478ef --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/config/DatabusSyncClientProperties.java @@ -0,0 +1,113 @@ +package com.zt.plat.framework.databus.client.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Databus 数据同步客户端配置属性 + * + * @author ZT + */ +@Data +@ConfigurationProperties(prefix = "zt.databus.sync.client") +public class DatabusSyncClientProperties { + + /** + * 是否启用 + */ + private Boolean enabled = true; + /** + * RocketMQ NameServer 地址 + */ + private String nameServer; + + /** + * 客户端编码(必填,用于订阅专属Topic) + * Topic格式: databus-sync-{eventType}-{clientCode} + */ + private String clientCode; + + /** + * MQ配置 + */ + private Mq mq = new Mq(); + + /** + * HTTP配置 + */ + private Http http = new Http(); + + /** + * 幂等配置 + */ + private Idempotent idempotent = new Idempotent(); + + @Data + public static class Mq { + /** + * 是否启用MQ消费 + */ + private Boolean enabled = true; + /** + * RocketMQ NameServer 地址 + */ + private String nameServer; + + /** + * Topic基础名称 + */ + private String topicBase = "databus-sync"; + + /** + * 消费者组前缀,完整格式: {consumerGroupPrefix}-{eventType} + * 默认: databus-client-{clientCode} + */ + private String consumerGroupPrefix; + + /** + * 消费线程数 + */ + private Integer consumeThreadMin = 1; + + /** + * 消费线程数 + */ + private Integer consumeThreadMax = 5; + + /** + * 最大重试次数 + */ + private Integer maxReconsumeTimes = 3; + } + + @Data + public static class Http { + /** + * 是否启用HTTP推送接收 + */ + private Boolean enabled = false; + + /** + * 接收端点路径 + */ + private String endpoint = "/databus/sync/receive"; + } + + @Data + public static class Idempotent { + /** + * 是否启用幂等检查 + */ + private Boolean enabled = true; + /** + * RocketMQ NameServer 地址 + */ + private String nameServer; + + /** + * 幂等记录过期时间(秒) + */ + private Integer expireSeconds = 86400; // 24小时 + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/controller/SyncMessageController.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/controller/SyncMessageController.java new file mode 100644 index 00000000..1d5ace2b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/controller/SyncMessageController.java @@ -0,0 +1,43 @@ +package com.zt.plat.framework.databus.client.core.controller; + +import com.zt.plat.framework.databus.client.core.processor.MessageProcessor; +import com.zt.plat.module.databus.enums.DatabusEventType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +/** + * 同步消息HTTP接收控制器 + * + * @author ZT + */ +@Slf4j +@RestController +@RequestMapping("${zt.databus.sync.client.http.endpoint:/databus/sync/receive}") +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "zt.databus.sync.client.http", name = "enabled", havingValue = "true") +public class SyncMessageController { + + private final MessageProcessor messageProcessor; + + /** + * 接收同步消息 + * + * @param eventType 事件类型 + * @param message 消息体 + */ + @PostMapping("/{eventType}") + public void receive(@PathVariable("eventType") String eventType, @RequestBody String message) { + log.debug("[Databus Client] 接收到HTTP消息, eventType={}", eventType); + + DatabusEventType type = DatabusEventType.valueOf(eventType); + if (type == null) { + log.warn("[Databus Client] 未知的事件类型: {}", eventType); + return; + } + + messageProcessor.process(message, type); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/IdempotentStore.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/IdempotentStore.java new file mode 100644 index 00000000..14f99600 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/IdempotentStore.java @@ -0,0 +1,19 @@ +package com.zt.plat.framework.databus.client.core.idempotent; + +/** + * 幂等存储接口 + * + * @author ZT + */ +public interface IdempotentStore { + + /** + * 检查并记录消息是否已处理 + * + * @param syncId 同步ID + * @param expireSeconds 过期时间(秒) + * @return true-未处理(可以处理), false-已处理(应该跳过) + */ + boolean checkAndMark(String syncId, int expireSeconds); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/RedisIdempotentStore.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/RedisIdempotentStore.java new file mode 100644 index 00000000..edae4ce7 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/idempotent/RedisIdempotentStore.java @@ -0,0 +1,30 @@ +package com.zt.plat.framework.databus.client.core.idempotent; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * 基于 Redis 的幂等存储实现 + * + * @author ZT + */ +@Component +@RequiredArgsConstructor +public class RedisIdempotentStore implements IdempotentStore { + + private static final String KEY_PREFIX = "databus:sync:idempotent:"; + + private final StringRedisTemplate stringRedisTemplate; + + @Override + public boolean checkAndMark(String syncId, int expireSeconds) { + String key = KEY_PREFIX + syncId; + Boolean success = stringRedisTemplate.opsForValue() + .setIfAbsent(key, "1", expireSeconds, TimeUnit.SECONDS); + return Boolean.TRUE.equals(success); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/listener/DatabusConsumerRegistry.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/listener/DatabusConsumerRegistry.java new file mode 100644 index 00000000..bf1ba313 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/listener/DatabusConsumerRegistry.java @@ -0,0 +1,148 @@ +package com.zt.plat.framework.databus.client.core.listener; + +import com.zt.plat.framework.databus.client.config.DatabusSyncClientProperties; +import com.zt.plat.framework.databus.client.core.processor.MessageProcessor; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.message.MessageExt; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Databus Consumer 注册器 + *

+ * 根据 DatabusEventType 枚举自动为所有事件类型创建消费者 + * Topic格式: {topicBase}-{module}-{entity}-{action}-{clientCode} + * 例如: databus-sync-system-user-create-company-a + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client.mq", name = "enabled", havingValue = "true", matchIfMissing = true) +public class DatabusConsumerRegistry { + + private final MessageProcessor messageProcessor; + private final DatabusSyncClientProperties properties; + + + /** + * 管理的消费者列表 + */ + private final List consumers = new ArrayList<>(); + + public DatabusConsumerRegistry(MessageProcessor messageProcessor, DatabusSyncClientProperties properties) { + this.messageProcessor = messageProcessor; + this.properties = properties; + } + + @PostConstruct + public void init() { + if (!Boolean.TRUE.equals(properties.getEnabled())) { + log.info("[Databus Client] 客户端未启用,跳过初始化"); + return; + } + + String nameServer = properties.getMq().getNameServer(); + if (nameServer == null || nameServer.isEmpty()) { + log.warn("[Databus Client] RocketMQ nameServer未配置,跳过MQ消费者初始化"); + return; + } + + String clientCode = properties.getClientCode(); + if (clientCode == null || clientCode.isEmpty()) { + log.error("[Databus Client] clientCode未配置,无法订阅Topic"); + return; + } + + String topicBase = properties.getMq().getTopicBase(); + String consumerGroupPrefix = properties.getMq().getConsumerGroupPrefix(); + if (consumerGroupPrefix == null || consumerGroupPrefix.isEmpty()) { + consumerGroupPrefix = "databus-client-" + clientCode; + } + + // 为每个事件类型创建独立的消费者 + for (DatabusEventType eventType : DatabusEventType.values()) { + try { + createConsumer(eventType, topicBase, clientCode, consumerGroupPrefix, nameServer); + } catch (Exception e) { + log.error("[Databus Client] 创建消费者失败, eventType={}", eventType.name(), e); + } + } + + log.info("[Databus Client] 消费者注册完成,共创建 {} 个消费者", consumers.size()); + } + + /** + * 为指定事件类型创建消费者 + */ + private void createConsumer(DatabusEventType eventType, String topicBase, String clientCode, String consumerGroupPrefix, String nameServer) throws MQClientException { + // Topic: databus-sync-system-user-create-company-a + String topic = eventType.getTopic(topicBase, clientCode); + // ConsumerGroup: databus-client-company-a-system-user-create + String consumerGroup = String.format("%s-%s", consumerGroupPrefix, eventType.getTopicSuffix()); + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup); + consumer.setNamesrvAddr(nameServer); + consumer.setConsumeThreadMin(properties.getMq().getConsumeThreadMin()); + consumer.setConsumeThreadMax(properties.getMq().getConsumeThreadMax()); + consumer.setMaxReconsumeTimes(properties.getMq().getMaxReconsumeTimes()); + + // 订阅Topic + consumer.subscribe(topic, "*"); + + // 设置消息监听器 + consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + for (MessageExt msg : msgs) { + try { + String messageBody = new String(msg.getBody(), StandardCharsets.UTF_8); + log.debug("[Databus Client] 收到消息, topic={}, msgId={}, eventType={}", + msg.getTopic(), msg.getMsgId(), eventType.name()); + + messageProcessor.process(messageBody, eventType); + + } catch (Exception e) { + log.error("[Databus Client] 消息处理失败, topic={}, msgId={}, reconsumeTimes={}", + msg.getTopic(), msg.getMsgId(), msg.getReconsumeTimes(), e); + + if (msg.getReconsumeTimes() >= properties.getMq().getMaxReconsumeTimes()) { + log.error("[Databus Client] 重试次数已达上限,放弃处理, msgId={}", msg.getMsgId()); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + + consumer.start(); + consumers.add(consumer); + + log.info("[Databus Client] 消费者启动成功, topic={}, consumerGroup={}, event={}", + topic, consumerGroup, eventType.getName()); + } + + @PreDestroy + public void destroy() { + for (DefaultMQPushConsumer consumer : consumers) { + try { + consumer.shutdown(); + } catch (Exception e) { + log.error("[Databus Client] 关闭消费者失败", e); + } + } + consumers.clear(); + log.info("[Databus Client] 所有消费者已关闭"); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/message/SyncMessage.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/message/SyncMessage.java new file mode 100644 index 00000000..00d00ce0 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/message/SyncMessage.java @@ -0,0 +1,54 @@ +package com.zt.plat.framework.databus.client.core.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 数据同步消息(服务端推送格式) + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SyncMessage { + + /** + * 同步ID(唯一标识) + */ + private String syncId; + + /** + * 事件记录ID + */ + private Long eventRecordId; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 事件动作 + */ + private String eventAction; + + /** + * 业务数据快照(JSON字符串) + */ + private String dataSnapshot; + + /** + * 数据版本 + */ + private Integer dataVersion; + + /** + * 时间戳 + */ + private Long timestamp; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/processor/MessageProcessor.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/processor/MessageProcessor.java new file mode 100644 index 00000000..8f67e409 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/processor/MessageProcessor.java @@ -0,0 +1,236 @@ +package com.zt.plat.framework.databus.client.core.processor; + +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.databus.client.config.DatabusSyncClientProperties; +import com.zt.plat.framework.databus.client.core.idempotent.IdempotentStore; +import com.zt.plat.framework.databus.client.core.message.SyncMessage; +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 消息处理器 + *

+ * 负责消息的幂等检查和路由到具体的Handler + * + * @author ZT + */ +@Slf4j +@Component +@SuppressWarnings({"rawtypes", "unchecked"}) +public class MessageProcessor { + + private final IdempotentStore idempotentStore; + private final DatabusSyncClientProperties properties; + + /** + * 增量同步处理器列表 + */ + private List handlers = new ArrayList<>(); + + /** + * 全量同步处理器列表 + */ + private List batchHandlers = new ArrayList<>(); + + private Map handlerMap; + private Map batchHandlerMap; + + public MessageProcessor(IdempotentStore idempotentStore, DatabusSyncClientProperties properties) { + this.idempotentStore = idempotentStore; + this.properties = properties; + } + + @Autowired(required = false) + public void setHandlers(List handlers) { + if (handlers != null) { + this.handlers = handlers; + } + } + + @Autowired(required = false) + public void setBatchHandlers(List batchHandlers) { + if (batchHandlers != null) { + this.batchHandlers = batchHandlers; + } + } + + /** + * 处理同步消息 + * + * @param messageJson 消息JSON字符串 + * @param eventType 事件类型枚举 + */ + public void process(String messageJson, DatabusEventType eventType) { + try { + if (eventType.isFullSync()) { + processBatchMessage(messageJson, eventType); + } else { + processIncrementalMessage(messageJson, eventType); + } + } catch (Exception e) { + log.error("[Databus Client] 消息处理失败, eventType={}, message={}", eventType.name(), messageJson, e); + throw new RuntimeException("消息处理失败", e); + } + } + + /** + * 处理增量同步消息 + */ + private void processIncrementalMessage(String messageJson, DatabusEventType eventType) { + // 先解析为服务端推送的 SyncMessage 格式 + SyncMessage syncMessage = JSONUtil.toBean(messageJson, SyncMessage.class); + + // 幂等检查 + if (properties.getIdempotent().getEnabled()) { + boolean canProcess = idempotentStore.checkAndMark( + syncMessage.getSyncId(), + properties.getIdempotent().getExpireSeconds() + ); + if (!canProcess) { + log.info("[Databus Client] 消息已处理,跳过, syncId={}", syncMessage.getSyncId()); + return; + } + } + + // 路由到对应的Handler + SyncEventHandler handler = getHandler(eventType); + if (handler == null) { + log.warn("[Databus Client] 未找到事件处理器, eventType={}", eventType.name()); + return; + } + + // 获取 Handler 期望的数据类型,并转换消息 + Class dataType = handler.getDataType(); + DatabusMessage message = convertToDatabusMessage(syncMessage, eventType, dataType); + handler.handle(message); + log.info("[Databus Client] 增量消息处理成功, syncId={}, eventType={}", + syncMessage.getSyncId(), eventType.name()); + } + + /** + * 将 SyncMessage 转换为 DatabusMessage + * + * @param syncMessage 服务端推送的同步消息 + * @param eventType 事件类型 + * @param dataType Handler 期望的数据类型 + * @return DatabusMessage + */ + private DatabusMessage convertToDatabusMessage(SyncMessage syncMessage, DatabusEventType eventType, Class dataType) { + DatabusMessage message = new DatabusMessage(); + message.setMessageId(syncMessage.getSyncId()); + message.setEventType(eventType); + + // 从 dataSnapshot JSON 字符串解析业务数据对象 + if (syncMessage.getDataSnapshot() != null && !syncMessage.getDataSnapshot().isEmpty()) { + // 使用 Handler 提供的类型进行反序列化 + Object data = JSONUtil.toBean(syncMessage.getDataSnapshot(), dataType); + message.setData(data); + } + + // 设置时间戳 + if (syncMessage.getTimestamp() != null) { + message.setTimestamp(java.time.LocalDateTime.ofInstant( + java.time.Instant.ofEpochMilli(syncMessage.getTimestamp()), + java.time.ZoneId.systemDefault() + )); + } + + return message; + } + + /** + * 处理全量同步批量消息 + */ + private void processBatchMessage(String messageJson, DatabusEventType eventType) { + DatabusBatchMessage message = JSONUtil.toBean(messageJson, DatabusBatchMessage.class); + + // 幂等检查 + if (properties.getIdempotent().getEnabled()) { + boolean canProcess = idempotentStore.checkAndMark( + message.getMessageId(), + properties.getIdempotent().getExpireSeconds() + ); + if (!canProcess) { + log.info("[Databus Client] 批量消息已处理,跳过, messageId={}", message.getMessageId()); + return; + } + } + + // 路由到对应的BatchHandler + BatchSyncEventHandler handler = getBatchHandler(eventType); + if (handler == null) { + log.warn("[Databus Client] 未找到批量事件处理器, eventType={}", eventType.name()); + return; + } + + // 全量同步开始回调(第一批) + if (message.getBatchNo() != null && message.getBatchNo() == 1) { + handler.onFullSyncStart(message); + log.info("[Databus Client] 全量同步开始, taskId={}, eventType={}, totalCount={}", + message.getTaskId(), eventType.name(), message.getTotalCount()); + } + + // 执行批量处理 + handler.handleBatch(message); + log.info("[Databus Client] 批量消息处理成功, messageId={}, eventType={}, batchNo={}/{}, count={}", + message.getMessageId(), eventType.name(), + message.getBatchNo(), message.getTotalBatch(), message.getCount()); + + // 全量同步完成回调(最后一批) + if (Boolean.TRUE.equals(message.getIsLastBatch())) { + handler.onFullSyncComplete(message); + log.info("[Databus Client] 全量同步完成, taskId={}, eventType={}, totalCount={}", + message.getTaskId(), eventType.name(), message.getTotalCount()); + } + } + + /** + * 获取增量同步事件处理器 + */ + private SyncEventHandler getHandler(DatabusEventType eventType) { + if (handlerMap == null) { + if (handlers == null || handlers.isEmpty()) { + handlerMap = Collections.emptyMap(); + } else { + handlerMap = handlers.stream() + .collect(Collectors.toMap( + SyncEventHandler::getSupportedEventType, + Function.identity() + )); + } + } + return handlerMap.get(eventType); + } + + /** + * 获取全量同步批量事件处理器 + */ + private BatchSyncEventHandler getBatchHandler(DatabusEventType eventType) { + if (batchHandlerMap == null) { + if (batchHandlers == null || batchHandlers.isEmpty()) { + batchHandlerMap = Collections.emptyMap(); + } else { + batchHandlerMap = batchHandlers.stream() + .collect(Collectors.toMap( + BatchSyncEventHandler::getSupportedEventType, + Function.identity() + )); + } + } + return batchHandlerMap.get(eventType); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java new file mode 100644 index 00000000..4ab5a573 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java @@ -0,0 +1,51 @@ +package com.zt.plat.framework.databus.client.handler; + +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; + +/** + * 批量同步事件处理器接口 + *

+ * 业务系统需要实现此接口来处理全量同步的批量数据 + * + * @author ZT + */ +public interface BatchSyncEventHandler { + + /** + * 获取支持的事件类型 + * + * @return 事件类型枚举 + */ + DatabusEventType getSupportedEventType(); + + /** + * 处理批量同步消息 + * + * @param message 批量同步消息 + */ + void handleBatch(DatabusBatchMessage message); + + /** + * 全量同步开始回调(可选实现) + *

+ * 当收到第一批数据(batchNo=1)时调用 + * + * @param message 批量同步消息 + */ + default void onFullSyncStart(DatabusBatchMessage message) { + // 默认空实现,子类可覆盖 + } + + /** + * 全量同步完成回调(可选实现) + *

+ * 当收到最后一批数据(isLastBatch=true)时调用 + * + * @param message 批量同步消息 + */ + default void onFullSyncComplete(DatabusBatchMessage message) { + // 默认空实现,子类可覆盖 + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/SyncEventHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/SyncEventHandler.java new file mode 100644 index 00000000..f89906d0 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/SyncEventHandler.java @@ -0,0 +1,57 @@ +package com.zt.plat.framework.databus.client.handler; + +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * 同步事件处理器接口 + *

+ * 业务系统需要实现此接口来处理增量数据同步 + * + * @author ZT + */ +public interface SyncEventHandler { + + /** + * 获取支持的事件类型 + * + * @return 事件类型枚举 + */ + DatabusEventType getSupportedEventType(); + + /** + * 处理同步消息 + * + * @param message 同步消息 + */ + void handle(DatabusMessage message); + + /** + * 获取数据类型 + *

+ * 默认通过反射获取泛型类型参数,子类可以覆盖此方法提供具体类型 + * + * @return 数据类型的 Class 对象 + */ + @SuppressWarnings("unchecked") + default Class getDataType() { + Type[] genericInterfaces = this.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + if (parameterizedType.getRawType().equals(SyncEventHandler.class)) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length > 0 && typeArguments[0] instanceof Class) { + return (Class) typeArguments[0]; + } + } + } + } + // 如果无法获取泛型类型,返回 Object.class + return (Class) Object.class; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java new file mode 100644 index 00000000..f42330a9 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java @@ -0,0 +1,108 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.module.databus.api.data.PostData; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.dept.dto.PostSaveReqDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +/** + * 岗位同步业务逻辑 + *

+ * 被各个 PostHandler 共享使用 + * + * @author ZT + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.PostApi") +public class PostSyncService { + + @Autowired(required = false) + private PostApi postApi; + + /** + * 创建岗位 + */ + public void create(PostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过创建岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + postApi.createPost(dto).checkError(); + log.info("[PostSync] 创建岗位成功, postId={}, postName={}", dto.getId(), dto.getName()); + } + + /** + * 更新岗位 + */ + public void update(PostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过更新岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + postApi.updatePost(dto).checkError(); + log.info("[PostSync] 更新岗位成功, postId={}, postName={}", dto.getId(), dto.getName()); + } + + /** + * 删除岗位 + */ + public void delete(PostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过删除岗位操作, postId={}", data.getId()); + return; + } + Long postId = data.getId(); + if (postId != null) { + postApi.deletePost(postId).checkError(); + log.info("[PostSync] 删除岗位成功, postId={}", postId); + } + } + + /** + * 全量同步单条数据(存在则更新,不存在则创建) + */ + public void fullSync(PostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过全量同步岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + try { + if (dto.getId() != null) { + var existing = postApi.getPost(dto.getId()); + if (existing.isSuccess() && existing.getData() != null) { + postApi.updatePost(dto).checkError(); + } else { + postApi.createPost(dto).checkError(); + } + } else { + postApi.createPost(dto).checkError(); + } + } catch (Exception e) { + postApi.createPost(dto).checkError(); + } + } + + /** + * 构建岗位DTO + */ + private PostSaveReqDTO buildPostDTO(PostData data) { + PostSaveReqDTO dto = new PostSaveReqDTO(); + dto.setId(data.getId()); + dto.setCode(data.getCode()); + dto.setName(data.getName()); + dto.setSort(data.getSort()); + dto.setStatus(data.getStatus()); + dto.setRemark(data.getRemark()); + return dto; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java new file mode 100644 index 00000000..50727a74 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java @@ -0,0 +1,71 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.module.databus.api.data.PostData; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 岗位全量同步事件处理器 + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(PostSyncService.class) +public class SystemPostFullHandler implements BatchSyncEventHandler { + + @Resource + private PostSyncService postSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_POST_FULL; + } + + @Override + public void onFullSyncStart(DatabusBatchMessage message) { + log.info("[PostSync] 全量同步开始, taskId={}, totalCount={}, totalBatch={}", + message.getTaskId(), message.getTotalCount(), message.getTotalBatch()); + } + + @Override + public void handleBatch(DatabusBatchMessage message) { + log.info("[PostSync] 处理批次, batchNo={}/{}, count={}", + message.getBatchNo(), message.getTotalBatch(), message.getCount()); + + if (message.getDataList() == null || message.getDataList().isEmpty()) { + log.warn("[PostSync] 数据列表为空, batchNo={}", message.getBatchNo()); + return; + } + + int successCount = 0; + int failCount = 0; + + for (PostData data : message.getDataList()) { + try { + postSyncService.fullSync(data); + successCount++; + } catch (Exception e) { + failCount++; + log.error("[PostSync] 处理数据项失败, postId={}", data.getId(), e); + } + } + + log.info("[PostSync] 批次处理完成, batchNo={}, success={}, fail={}", + message.getBatchNo(), successCount, failCount); + } + + @Override + public void onFullSyncComplete(DatabusBatchMessage message) { + log.info("[PostSync] 全量同步完成, taskId={}, totalCount={}", + message.getTaskId(), message.getTotalCount()); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring.factories b/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..9b570f2a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.zt.plat.framework.databus.client.config.DatabusSyncClientAutoConfiguration diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..50f5c6d4 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.zt.plat.framework.databus.client.config.DatabusSyncClientAutoConfiguration diff --git a/zt-framework/zt-spring-boot-starter-databus-server/pom.xml b/zt-framework/zt-spring-boot-starter-databus-server/pom.xml new file mode 100644 index 00000000..59150712 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/pom.xml @@ -0,0 +1,73 @@ + + + + com.zt.plat + zt-framework + ${revision} + + 4.0.0 + zt-spring-boot-starter-databus-server + jar + + ${project.artifactId} + DataBus 服务端组件,负责发送数据变更消息到各客户端 + + + + com.zt.plat + zt-common + + + + + com.zt.plat + zt-module-databus-api + ${revision} + + + + cn.hutool + hutool-all + + + + + com.zt.plat + zt-spring-boot-starter-mq + + + + + com.zt.plat + zt-spring-boot-starter-mybatis + + + + + com.zt.plat + zt-spring-boot-starter-web + + + + + com.zt.plat + zt-spring-boot-starter-security + + + + + com.zt.plat + zt-spring-boot-starter-biz-tenant + + + + + com.zt.plat + zt-spring-boot-starter-test + test + + + + diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusMessagingConfiguration.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusMessagingConfiguration.java new file mode 100644 index 00000000..48e7d0cb --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusMessagingConfiguration.java @@ -0,0 +1,27 @@ +package com.zt.plat.framework.databus.server.config; + +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusherImpl; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Databus 消息配置类 + * + * @author ZT + */ +@Configuration +public class DatabusMessagingConfiguration { + + @Bean + @ConditionalOnMissingBean + public MessagePusher messagePusher(@Autowired(required = false) RocketMQTemplate rocketMQTemplate) { + MessagePusherImpl pusher = new MessagePusherImpl(); + pusher.setRocketMQTemplate(rocketMQTemplate); + return pusher; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerAutoConfiguration.java new file mode 100644 index 00000000..7444745e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerAutoConfiguration.java @@ -0,0 +1,24 @@ +package com.zt.plat.framework.databus.server.config; + +import com.zt.plat.framework.databus.server.producer.DatabusMessageProducer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Databus 服务端自动配置 + * + * @author ZT + */ +@Configuration +@EnableConfigurationProperties(DatabusServerProperties.class) +@ConditionalOnProperty(prefix = "zt.databus.sync.server", name = "enabled", havingValue = "true") +public class DatabusServerAutoConfiguration { + + @Bean + public DatabusMessageProducer databusMessageProducer(DatabusServerProperties properties) { + return new DatabusMessageProducer(properties); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerProperties.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerProperties.java new file mode 100644 index 00000000..22836f48 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusServerProperties.java @@ -0,0 +1,60 @@ +package com.zt.plat.framework.databus.server.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * Databus 服务端配置属性 + * + * @author ZT + */ +@Data +@ConfigurationProperties(prefix = "zt.databus.sync.server") +public class DatabusServerProperties { + + /** + * 是否启用 + */ + private boolean enabled = false; + + /** + * MQ 配置 + */ + private MqConfig mq = new MqConfig(); + + /** + * 订阅的客户端列表 + */ + private List clients; + + @Data + public static class MqConfig { + /** + * 是否启用 MQ 发送 + */ + private boolean enabled = true; + + /** + * RocketMQ NameServer 地址 + */ + private String nameServer; + + /** + * Topic 基础名称 + */ + private String topicBase = "databus-sync"; + + /** + * 生产者组名称 + */ + private String producerGroup = "databus-server-producer"; + /** + * 发送超时时间(毫秒) + */ + private int sendMsgTimeout = 10000; // 默认 10 秒 + + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerAutoConfiguration.java new file mode 100644 index 00000000..1c929812 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerAutoConfiguration.java @@ -0,0 +1,45 @@ +package com.zt.plat.framework.databus.server.config; + +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusherImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +/** + * Databus 同步服务端自动配置 + * + * @author ZT + */ +@Slf4j +@AutoConfiguration +@EnableAsync +@EnableConfigurationProperties(DatabusSyncServerProperties.class) +@ComponentScan(basePackages = "com.zt.plat.framework.databus.server") +@MapperScan("com.zt.plat.framework.databus.server.dal.mapper") +public class DatabusSyncServerAutoConfiguration { + + public DatabusSyncServerAutoConfiguration() { + log.info("[Databus] 数据同步服务端模块已加载"); + } + + /** + * 注册消息推送器 Bean + */ + @Bean + @ConditionalOnBean(RocketMQTemplate.class) + public MessagePusher messagePusher(RocketMQTemplate rocketMQTemplate) { + MessagePusherImpl pusher = new MessagePusherImpl(); + pusher.setRocketMQTemplate(rocketMQTemplate); + log.info("[Databus] MessagePusher Bean 已注册"); + return pusher; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerProperties.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerProperties.java new file mode 100644 index 00000000..f1cba915 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/config/DatabusSyncServerProperties.java @@ -0,0 +1,61 @@ +package com.zt.plat.framework.databus.server.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Databus 数据同步服务端配置属性 + * + * @author ZT + */ +@Data +@ConfigurationProperties(prefix = "zt.databus.sync.server") +public class DatabusSyncServerProperties { + + /** + * 是否启用 + */ + private Boolean enabled = true; + + /** + * 重试配置 + */ + private Retry retry = new Retry(); + + /** + * 批量推送配置 + */ + private Batch batch = new Batch(); + + @Data + public static class Retry { + /** + * 最大重试次数 + */ + private Integer maxAttempts = 5; + + /** + * 初始重试延迟(秒) + */ + private Integer initialDelay = 1; + + /** + * 重试延迟倍数 + */ + private Integer multiplier = 2; + } + + @Data + public static class Batch { + /** + * 默认批量大小 + */ + private Integer defaultSize = 500; + + /** + * 批量推送间隔(秒) + */ + private Integer interval = 5; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncClientController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncClientController.java new file mode 100644 index 00000000..e758be19 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncClientController.java @@ -0,0 +1,92 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncClientConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO; +import com.zt.plat.framework.databus.server.service.DatabusSyncClientService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据同步客户端") +@RestController +@RequestMapping("/databus/sync/client") +@Validated +public class DatabusSyncClientController { + + @Resource + private DatabusSyncClientService clientService; + + @PostMapping("/create") + @Operation(summary = "创建数据同步客户端") + @PreAuthorize("@ss.hasPermission('databus:sync:client:create')") + public CommonResult createClient(@Valid @RequestBody DatabusSyncClientSaveReqVO createReqVO) { + return success(clientService.createClient(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新数据同步客户端") + @PreAuthorize("@ss.hasPermission('databus:sync:client:update')") + public CommonResult updateClient(@Valid @RequestBody DatabusSyncClientSaveReqVO updateReqVO) { + clientService.updateClient(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除数据同步客户端") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:client:delete')") + public CommonResult deleteClient(@RequestParam("id") Long id) { + clientService.deleteClient(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得数据同步客户端") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:client:query')") + public CommonResult getClient(@RequestParam("id") Long id) { + DatabusSyncClientDO client = clientService.getClient(id); + return success(DatabusSyncClientConvert.INSTANCE.convert(client)); + } + + @GetMapping("/page") + @Operation(summary = "获得数据同步客户端分页") + @PreAuthorize("@ss.hasPermission('databus:sync:client:query')") + public CommonResult> getClientPage(@Valid DatabusSyncClientPageReqVO pageReqVO) { + PageResult pageResult = clientService.getClientPage(pageReqVO); + return success(DatabusSyncClientConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list") + @Operation(summary = "获得数据同步客户端列表") + @PreAuthorize("@ss.hasPermission('databus:sync:client:query')") + public CommonResult> getClientList() { + List list = clientService.getClientList(); + return success(DatabusSyncClientConvert.INSTANCE.convertList(list)); + } + + @PutMapping("/update-status") + @Operation(summary = "修改客户端启用状态") + @PreAuthorize("@ss.hasPermission('databus:sync:client:update')") + public CommonResult updateClientStatus( + @RequestParam("id") Long id, + @RequestParam("enabled") Integer enabled) { + clientService.updateClientStatus(id, enabled); + return success(true); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncDeadLetterController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncDeadLetterController.java new file mode 100644 index 00000000..7a89b128 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncDeadLetterController.java @@ -0,0 +1,76 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterRespVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncDeadLetterConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; +import com.zt.plat.framework.databus.server.service.DatabusSyncDeadLetterService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据同步死信队列") +@RestController +@RequestMapping("/databus/sync/dead-letter") +@Validated +public class DatabusSyncDeadLetterController { + + @Resource + private DatabusSyncDeadLetterService deadLetterService; + + @GetMapping("/get") + @Operation(summary = "获得数据同步死信队列") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:query')") + public CommonResult getDeadLetter(@RequestParam("id") Long id) { + DatabusSyncDeadLetterDO deadLetter = deadLetterService.getDeadLetter(id); + return success(DatabusSyncDeadLetterConvert.INSTANCE.convert(deadLetter)); + } + + @GetMapping("/page") + @Operation(summary = "获得数据同步死信队列分页") + @PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:query')") + public CommonResult> getDeadLetterPage(@Valid DatabusSyncDeadLetterPageReqVO pageReqVO) { + PageResult pageResult = deadLetterService.getDeadLetterPage(pageReqVO); + return success(DatabusSyncDeadLetterConvert.INSTANCE.convertPage(pageResult)); + } + + @PostMapping("/reprocess") + @Operation(summary = "重新投递死信消息") + @Parameter(name = "id", description = "死信ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:reprocess')") + public CommonResult reprocessDeadLetter(@RequestParam("id") Long id) { + deadLetterService.reprocessDeadLetter(id); + return success(true); + } + + @PostMapping("/batch-reprocess") + @Operation(summary = "批量重新投递死信消息") + @PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:reprocess')") + public CommonResult batchReprocessDeadLetter(@RequestBody List ids) { + deadLetterService.batchReprocessDeadLetter(ids); + return success(true); + } + + @PutMapping("/mark-handled") + @Operation(summary = "标记为已处理") + @PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:update')") + public CommonResult markHandled( + @RequestParam("id") Long id, + @RequestParam(value = "remark", required = false) String remark) { + deadLetterService.markHandled(id, remark); + return success(true); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java new file mode 100644 index 00000000..c74227d2 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java @@ -0,0 +1,92 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncEventConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; +import com.zt.plat.framework.databus.server.service.DatabusSyncEventService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据同步事件") +@RestController +@RequestMapping("/databus/sync/event") +@Validated +public class DatabusSyncEventController { + + @Resource + private DatabusSyncEventService eventService; + + @PostMapping("/create") + @Operation(summary = "创建数据同步事件") + @PreAuthorize("@ss.hasPermission('databus:sync:event:create')") + public CommonResult createEvent(@Valid @RequestBody DatabusSyncEventSaveReqVO createReqVO) { + return success(eventService.createEvent(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新数据同步事件") + @PreAuthorize("@ss.hasPermission('databus:sync:event:update')") + public CommonResult updateEvent(@Valid @RequestBody DatabusSyncEventSaveReqVO updateReqVO) { + eventService.updateEvent(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除数据同步事件") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:event:delete')") + public CommonResult deleteEvent(@RequestParam("id") Long id) { + eventService.deleteEvent(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得数据同步事件") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:event:query')") + public CommonResult getEvent(@RequestParam("id") Long id) { + DatabusSyncEventDO event = eventService.getEvent(id); + return success(DatabusSyncEventConvert.INSTANCE.convert(event)); + } + + @GetMapping("/page") + @Operation(summary = "获得数据同步事件分页") + @PreAuthorize("@ss.hasPermission('databus:sync:event:query')") + public CommonResult> getEventPage(@Valid DatabusSyncEventPageReqVO pageReqVO) { + PageResult pageResult = eventService.getEventPage(pageReqVO); + return success(DatabusSyncEventConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list") + @Operation(summary = "获得数据同步事件列表") + @PreAuthorize("@ss.hasPermission('databus:sync:event:query')") + public CommonResult> getEventList() { + List list = eventService.getEventList(); + return success(DatabusSyncEventConvert.INSTANCE.convertList(list)); + } + + @PutMapping("/update-status") + @Operation(summary = "修改事件启用状态") + @PreAuthorize("@ss.hasPermission('databus:sync:event:update')") + public CommonResult updateEventStatus( + @RequestParam("id") Long id, + @RequestParam("enabled") Integer enabled) { + eventService.updateEventStatus(id, enabled); + return success(true); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncFullTaskController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncFullTaskController.java new file mode 100644 index 00000000..cca415c9 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncFullTaskController.java @@ -0,0 +1,102 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskCreateReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskRespVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncFullTaskConvert; +import com.zt.plat.framework.databus.server.core.sync.DatabusFullSyncService; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncFullTaskMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * 管理后台 - 数据全量同步任务 + * + * @author ZT + */ +@Tag(name = "管理后台 - 全量同步任务") +@RestController +@RequestMapping("/databus/sync/full-task") +@Validated +public class DatabusSyncFullTaskController { + + @Resource + private DatabusFullSyncService fullSyncService; + + @Resource + private DatabusSyncFullTaskMapper fullTaskMapper; + + @PostMapping("/create") + @Operation(summary = "创建全量同步任务") + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:create')") + public CommonResult createFullTask(@Valid @RequestBody DatabusSyncFullTaskCreateReqVO createReqVO) { + Long taskId = fullSyncService.createFullSyncTask(createReqVO.getSubscriptionId(), createReqVO.getRemark()); + return success(taskId); + } + + @PostMapping("/execute") + @Operation(summary = "执行全量同步任务") + @Parameter(name = "id", description = "任务ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:execute')") + public CommonResult executeFullTask(@RequestParam("id") Long id) { + fullSyncService.executeFullSyncTask(id); + return success(true); + } + + @PostMapping("/create-and-execute") + @Operation(summary = "创建并执行全量同步任务") + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:create')") + public CommonResult createAndExecuteFullTask(@Valid @RequestBody DatabusSyncFullTaskCreateReqVO createReqVO) { + Long taskId = fullSyncService.createFullSyncTask(createReqVO.getSubscriptionId(), createReqVO.getRemark()); + // 异步执行任务 + fullSyncService.executeFullSyncTask(taskId); + return success(taskId); + } + + @PostMapping("/cancel") + @Operation(summary = "取消全量同步任务") + @Parameter(name = "id", description = "任务ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:cancel')") + public CommonResult cancelFullTask(@RequestParam("id") Long id) { + fullSyncService.cancelFullSyncTask(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得全量同步任务详情") + @Parameter(name = "id", description = "任务ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')") + public CommonResult getFullTask(@RequestParam("id") Long id) { + DatabusSyncFullTaskDO task = fullSyncService.getTaskById(id); + return success(DatabusSyncFullTaskConvert.INSTANCE.convert(task)); + } + + @GetMapping("/get-by-task-no") + @Operation(summary = "根据任务编号获得全量同步任务详情") + @Parameter(name = "taskNo", description = "任务编号", required = true, example = "abc123") + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')") + public CommonResult getFullTaskByTaskNo(@RequestParam("taskNo") String taskNo) { + DatabusSyncFullTaskDO task = fullSyncService.getTaskByTaskNo(taskNo); + return success(DatabusSyncFullTaskConvert.INSTANCE.convert(task)); + } + + @GetMapping("/page") + @Operation(summary = "获得全量同步任务分页") + @PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')") + public CommonResult> getFullTaskPage(@Valid DatabusSyncFullTaskPageReqVO pageReqVO) { + PageResult pageResult = fullTaskMapper.selectPage(pageReqVO); + return success(DatabusSyncFullTaskConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncPushLogController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncPushLogController.java new file mode 100644 index 00000000..52b10746 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncPushLogController.java @@ -0,0 +1,69 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogRespVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncPushLogConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper; +import com.zt.plat.framework.databus.server.service.DatabusSyncLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据同步推送日志") +@RestController +@RequestMapping("/databus/sync/push-log") +@Validated +public class DatabusSyncPushLogController { + + @Resource + private DatabusSyncLogService pushLogService; + + @Resource + private DatabusSyncEventRecordMapper eventRecordMapper; + + @GetMapping("/get") + @Operation(summary = "获得数据同步推送日志") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:push-log:query')") + public CommonResult getPushLog(@RequestParam("id") Long id) { + DatabusSyncLogDO pushLog = pushLogService.getPushLog(id); + DatabusSyncPushLogRespVO respVO = DatabusSyncPushLogConvert.INSTANCE.convert(pushLog); + // 关联查询事件记录,获取消息内容 + if (pushLog != null && pushLog.getEventRecordId() != null) { + DatabusSyncEventRecordDO eventRecord = eventRecordMapper.selectById(pushLog.getEventRecordId()); + if (eventRecord != null) { + respVO.setDataSnapshot(eventRecord.getDataSnapshot()); + } + } + return success(respVO); + } + + @GetMapping("/page") + @Operation(summary = "获得数据同步推送日志分页") + @PreAuthorize("@ss.hasPermission('databus:sync:push-log:query')") + public CommonResult> getPushLogPage(@Valid DatabusSyncPushLogPageReqVO pageReqVO) { + PageResult pageResult = pushLogService.getPushLogPage(pageReqVO); + return success(DatabusSyncPushLogConvert.INSTANCE.convertPage(pageResult)); + } + + @PostMapping("/retry") + @Operation(summary = "重试推送") + @Parameter(name = "id", description = "日志ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:push-log:retry')") + public CommonResult retryPush(@RequestParam("id") Long id) { + pushLogService.retryPush(id); + return success(true); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java new file mode 100644 index 00000000..737f138f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java @@ -0,0 +1,100 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncSubscriptionConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; +import com.zt.plat.framework.databus.server.service.DatabusSyncSubscriptionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据同步订阅") +@RestController +@RequestMapping("/databus/sync/subscription") +@Validated +public class DatabusSyncSubscriptionController { + + @Resource + private DatabusSyncSubscriptionService subscriptionService; + + @PostMapping("/create") + @Operation(summary = "创建数据同步订阅") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:create')") + public CommonResult createSubscription(@Valid @RequestBody DatabusSyncSubscriptionSaveReqVO createReqVO) { + return success(subscriptionService.createSubscription(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新数据同步订阅") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')") + public CommonResult updateSubscription(@Valid @RequestBody DatabusSyncSubscriptionSaveReqVO updateReqVO) { + subscriptionService.updateSubscription(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除数据同步订阅") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:delete')") + public CommonResult deleteSubscription(@RequestParam("id") Long id) { + subscriptionService.deleteSubscription(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得数据同步订阅") + @Parameter(name = "id", description = "编号", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')") + public CommonResult getSubscription(@RequestParam("id") Long id) { + DatabusSyncSubscriptionDO subscription = subscriptionService.getSubscription(id); + return success(DatabusSyncSubscriptionConvert.INSTANCE.convert(subscription)); + } + + @GetMapping("/page") + @Operation(summary = "获得数据同步订阅分页") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')") + public CommonResult> getSubscriptionPage(@Valid DatabusSyncSubscriptionPageReqVO pageReqVO) { + PageResult pageResult = subscriptionService.getSubscriptionPage(pageReqVO); + return success(DatabusSyncSubscriptionConvert.INSTANCE.convertPage(pageResult)); + } + + @PutMapping("/update-status") + @Operation(summary = "修改订阅启用状态") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')") + public CommonResult updateSubscriptionStatus( + @RequestParam("id") Long id, + @RequestParam("enabled") Integer enabled) { + subscriptionService.updateSubscriptionStatus(id, enabled); + return success(true); + } + + @PutMapping("/reset-checkpoint") + @Operation(summary = "重置订阅断点") + @Parameter(name = "id", description = "订阅ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')") + public CommonResult resetCheckpoint(@RequestParam("id") Long id) { + subscriptionService.resetCheckpoint(id); + return success(true); + } + + @PostMapping("/trigger-sync") + @Operation(summary = "手动触发同步") + @Parameter(name = "id", description = "订阅ID", required = true) + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:trigger')") + public CommonResult triggerSync(@RequestParam("id") Long id) { + subscriptionService.triggerSync(id); + return success(true); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientPageReqVO.java new file mode 100644 index 00000000..896bf61e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientPageReqVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.client; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 数据同步客户端分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncClientPageReqVO extends PageParam { + + @Schema(description = "客户端编码", example = "company-a") + private String clientCode; + + @Schema(description = "客户端名称", example = "A分公司") + private String clientName; + + @Schema(description = "启用状态(0-禁用 1-启用)", example = "1") + private Integer enabled; + + @Schema(description = "通信方式(MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP)", example = "MQ_FIRST") + private String transportType; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientRespVO.java new file mode 100644 index 00000000..fcf35f7c --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientRespVO.java @@ -0,0 +1,63 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.client; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 数据同步客户端 Response VO") +@Data +public class DatabusSyncClientRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "客户端编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "company-a") + private String clientCode; + + @Schema(description = "客户端名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "A分公司") + private String clientName; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer enabled; + + @Schema(description = "通信方式(MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP)", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ_FIRST") + private String transportType; + + @Schema(description = "MQ是否启用(0-否 1-是)", example = "1") + private Integer mqEnabled; + + @Schema(description = "MQ NameServer地址", example = "localhost:9876") + private String mqNamesrvAddr; + + @Schema(description = "MQ Topic基础名称", example = "databus-sync") + private String mqTopicBase; + + @Schema(description = "HTTP是否启用(0-否 1-是)", example = "1") + private Integer httpEnabled; + + @Schema(description = "HTTP推送端点", example = "http://example.com/databus/sync/receive") + private String httpEndpoint; + + @Schema(description = "应用Key", example = "app-key-001") + private String appKey; + + @Schema(description = "应用Secret", example = "***") + private String appSecret; + + @Schema(description = "创建者", example = "admin") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新者", example = "admin") + private String updater; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientSaveReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientSaveReqVO.java new file mode 100644 index 00000000..475c9f7b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientSaveReqVO.java @@ -0,0 +1,55 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.client; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +@Schema(description = "管理后台 - 数据同步客户端创建/修改 Request VO") +@Data +public class DatabusSyncClientSaveReqVO { + + @Schema(description = "主键ID", example = "1") + private Long id; + + @Schema(description = "客户端编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "company-a") + @NotBlank(message = "客户端编码不能为空") + @Pattern(regexp = "^[a-z0-9-]+$", message = "客户端编码只能包含小写字母、数字和短横线") + private String clientCode; + + @Schema(description = "客户端名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "A分公司") + @NotBlank(message = "客户端名称不能为空") + private String clientName; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; + + @Schema(description = "通信方式(MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP)", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ_FIRST") + @NotBlank(message = "通信方式不能为空") + private String transportType; + + @Schema(description = "MQ是否启用(0-否 1-是)", example = "1") + private Integer mqEnabled; + + @Schema(description = "MQ NameServer地址", example = "localhost:9876") + private String mqNamesrvAddr; + + @Schema(description = "MQ Topic基础名称", example = "databus-sync") + private String mqTopicBase; + + @Schema(description = "HTTP是否启用(0-否 1-是)", example = "1") + private Integer httpEnabled; + + @Schema(description = "HTTP推送端点", example = "http://example.com/databus/sync/receive") + private String httpEndpoint; + + @Schema(description = "应用Key", example = "app-key-001") + private String appKey; + + @Schema(description = "应用Secret(加密存储)", example = "secret") + private String appSecret; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterPageReqVO.java new file mode 100644 index 00000000..d07889cc --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterPageReqVO.java @@ -0,0 +1,43 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.deadletter; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 数据同步死信队列分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncDeadLetterPageReqVO extends PageParam { + + @Schema(description = "同步ID", example = "sync-20231124-001") + private String syncId; + + @Schema(description = "客户端编码", example = "company-a") + private String clientCode; + + @Schema(description = "事件类型", example = "user-changed") + private String eventType; + + @Schema(description = "状态(PENDING-待处理 REDELIVERED-已重新投递 IGNORED-已忽略)", example = "PENDING") + private String status; + + @Schema(description = "是否已处理(0-否 1-是)", example = "0") + private Integer handled; + + @Schema(description = "创建时间开始", example = "2023-11-24 00:00:00") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTimeStart; + + @Schema(description = "创建时间结束", example = "2023-11-24 23:59:59") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTimeEnd; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterRespVO.java new file mode 100644 index 00000000..39f29bfb --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/deadletter/DatabusSyncDeadLetterRespVO.java @@ -0,0 +1,63 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.deadletter; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 数据同步死信队列 Response VO") +@Data +public class DatabusSyncDeadLetterRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "同步ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "sync-20231124-001") + private String syncId; + + @Schema(description = "同步日志ID", example = "10001") + private Long syncLogId; + + @Schema(description = "客户端编码", example = "company-a") + private String clientCode; + + @Schema(description = "事件类型", example = "user-changed") + private String eventType; + + @Schema(description = "消息体(JSON)", example = "{\"userId\":1001,\"userName\":\"张三\"}") + private String messageBody; + + @Schema(description = "状态(PENDING-待处理 REDELIVERED-已重新投递 IGNORED-已忽略)", example = "PENDING") + private String status; + + @Schema(description = "失败原因", example = "连接超时") + private String lastErrorMessage; + + @Schema(description = "失败时间") + private LocalDateTime lastErrorTime; + + @Schema(description = "重试次数", example = "5") + private Integer retryCount; + + @Schema(description = "是否已处理(0-否 1-是)", example = "0") + private Integer handled; + + @Schema(description = "处理时间") + private LocalDateTime handleTime; + + @Schema(description = "处理人", example = "admin") + private String handler; + + @Schema(description = "处理备注", example = "已通知开发人员修复") + private String handleRemark; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventPageReqVO.java new file mode 100644 index 00000000..708bb89a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventPageReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.event; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 数据同步事件分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncEventPageReqVO extends PageParam { + + @Schema(description = "事件类型编码", example = "user-changed") + private String eventType; + + @Schema(description = "事件名称", example = "用户信息变更") + private String eventName; + + @Schema(description = "启用状态(0-禁用 1-启用)", example = "1") + private Integer enabled; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventRespVO.java new file mode 100644 index 00000000..6dfd2d9e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventRespVO.java @@ -0,0 +1,54 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.event; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 数据同步事件 Response VO") +@Data +public class DatabusSyncEventRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "事件类型编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system-user-full") + private String eventType; + + @Schema(description = "事件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户信息变更") + private String eventName; + + @Schema(description = "事件描述", example = "用户基本信息变更事件") + private String eventDesc; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer enabled; + + @Schema(description = "是否支持全量同步(0-否 1-是)", example = "1") + private Integer supportFullSync; + + @Schema(description = "是否支持增量同步(0-否 1-是)", example = "1") + private Integer supportIncrementalSync; + + @Schema(description = "数据结构版本", example = "1") + private Integer dataVersion; + + @Schema(description = "数据提供者类型(全量同步使用)", example = "ORG") + private String dataProviderMethod; + + @Schema(description = "创建者", example = "admin") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新者", example = "admin") + private String updater; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventSaveReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventSaveReqVO.java new file mode 100644 index 00000000..578854d8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventSaveReqVO.java @@ -0,0 +1,45 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.event; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +@Schema(description = "管理后台 - 数据同步事件创建/修改 Request VO") +@Data +public class DatabusSyncEventSaveReqVO { + + @Schema(description = "主键ID", example = "1") + private Long id; + + @Schema(description = "事件类型编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system-user-full") + @NotBlank(message = "事件类型编码不能为空") + @Pattern(regexp = "^[a-z0-9-]+$", message = "事件类型编码只能包含小写字母、数字和短横线") + private String eventType; + + @Schema(description = "事件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户信息变更") + @NotBlank(message = "事件名称不能为空") + private String eventName; + + @Schema(description = "事件描述", example = "用户基本信息变更事件") + private String eventDesc; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; + + @Schema(description = "数据提供者类型(全量同步使用)", example = "ORG") + private String dataProviderMethod; + + @Schema(description = "是否支持全量同步(0-否 1-是)", example = "1") + private Integer supportFullSync; + + @Schema(description = "是否支持增量同步(0-否 1-是)", example = "1") + private Integer supportIncrementalSync; + + @Schema(description = "数据结构版本", example = "1") + private Integer dataVersion; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskCreateReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskCreateReqVO.java new file mode 100644 index 00000000..27d35719 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskCreateReqVO.java @@ -0,0 +1,18 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 全量同步任务创建 Request VO") +@Data +public class DatabusSyncFullTaskCreateReqVO { + + @Schema(description = "订阅关系ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "订阅关系ID不能为空") + private Long subscriptionId; + + @Schema(description = "备注", example = "手动触发全量同步") + private String remark; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskPageReqVO.java new file mode 100644 index 00000000..08ac6423 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskPageReqVO.java @@ -0,0 +1,30 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 全量同步任务分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncFullTaskPageReqVO extends PageParam { + + @Schema(description = "任务编号", example = "abc123") + private String taskNo; + + @Schema(description = "订阅关系ID", example = "1") + private Long subscriptionId; + + @Schema(description = "客户端编码", example = "client-01") + private String clientCode; + + @Schema(description = "事件类型", example = "ORG_SYNC") + private String eventType; + + @Schema(description = "任务状态(0-待执行 1-执行中 2-已完成 3-失败 4-已取消)", example = "2") + private Integer status; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskRespVO.java new file mode 100644 index 00000000..de43a6d7 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/fulltask/DatabusSyncFullTaskRespVO.java @@ -0,0 +1,88 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 全量同步任务 Response VO") +@Data +public class DatabusSyncFullTaskRespVO { + + @Schema(description = "主键ID", example = "1") + private Long id; + + @Schema(description = "任务编号", example = "abc123") + private String taskNo; + + @Schema(description = "订阅关系ID", example = "1") + private Long subscriptionId; + + @Schema(description = "客户端编码", example = "client-01") + private String clientCode; + + @Schema(description = "事件类型", example = "ORG_SYNC") + private String eventType; + + @Schema(description = "任务状态(0-待执行 1-执行中 2-已完成 3-失败 4-已取消)", example = "2") + private Integer status; + + @Schema(description = "总数据量", example = "1000") + private Long totalCount; + + @Schema(description = "已处理数据量", example = "500") + private Long processedCount; + + @Schema(description = "成功数据量", example = "480") + private Long successCount; + + @Schema(description = "失败数据量", example = "20") + private Long failCount; + + @Schema(description = "总批次数", example = "10") + private Integer totalBatch; + + @Schema(description = "当前批次号", example = "5") + private Integer currentBatch; + + @Schema(description = "批量大小", example = "100") + private Integer batchSize; + + @Schema(description = "任务开始时间") + private LocalDateTime startTime; + + @Schema(description = "任务结束时间") + private LocalDateTime endTime; + + @Schema(description = "最后一次错误信息", example = "网络超时") + private String lastErrorMessage; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "备注", example = "手动触发全量同步") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "进度百分比", example = "50.0") + private Double progressPercent; + + /** + * 计算进度百分比 + */ + public Double getProgressPercent() { + if (totalCount == null || totalCount == 0) { + return 0.0; + } + if (processedCount == null) { + return 0.0; + } + return Math.round(processedCount * 10000.0 / totalCount) / 100.0; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogPageReqVO.java new file mode 100644 index 00000000..eb481034 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogPageReqVO.java @@ -0,0 +1,46 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.pushlog; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 数据同步推送日志分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncPushLogPageReqVO extends PageParam { + + @Schema(description = "同步ID", example = "sync-20231124-001") + private String syncId; + + @Schema(description = "订阅关系ID", example = "1") + private Long subscriptionId; + + @Schema(description = "客户端编码", example = "company-a") + private String clientCode; + + @Schema(description = "事件类型", example = "user-changed") + private String eventType; + + @Schema(description = "状态(PENDING-待处理 SUCCESS-成功 FAILED-失败 RETRYING-重试中)", example = "SUCCESS") + private String status; + + @Schema(description = "传输方式(MQ/HTTP)", example = "MQ") + private String transportType; + + @Schema(description = "创建时间开始", example = "2023-11-24 00:00:00") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTimeStart; + + @Schema(description = "创建时间结束", example = "2023-11-24 23:59:59") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTimeEnd; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogRespVO.java new file mode 100644 index 00000000..8d1e7cdf --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/pushlog/DatabusSyncPushLogRespVO.java @@ -0,0 +1,72 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.pushlog; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 数据同步推送日志 Response VO") +@Data +public class DatabusSyncPushLogRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "同步ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "sync-20231124-001") + private String syncId; + + @Schema(description = "事件记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10001") + private Long eventRecordId; + + @Schema(description = "订阅关系ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long subscriptionId; + + @Schema(description = "客户端编码", example = "company-a") + private String clientCode; + + @Schema(description = "事件类型", example = "user-changed") + private String eventType; + + @Schema(description = "同步模式(FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL") + private String syncMode; + + @Schema(description = "传输方式(MQ/HTTP)", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ") + private String transportType; + + @Schema(description = "MQ Topic", example = "databus-sync-user-changed-company-a") + private String mqTopic; + + @Schema(description = "MQ消息ID", example = "C0A8012300002A9F0000000000000001") + private String mqMsgId; + + @Schema(description = "状态(PENDING-待处理 SUCCESS-成功 FAILED-失败 RETRYING-重试中)", requiredMode = Schema.RequiredMode.REQUIRED, example = "SUCCESS") + private String status; + + @Schema(description = "重试次数", example = "0") + private Integer retryCount; + + @Schema(description = "错误信息", example = "连接超时") + private String errorMessage; + + @Schema(description = "开始时间") + private LocalDateTime startTime; + + @Schema(description = "结束时间") + private LocalDateTime endTime; + + @Schema(description = "耗时(毫秒)", example = "150") + private Integer duration; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + + @Schema(description = "推送消息内容(JSON)", example = "{\"id\":1,\"name\":\"张三\"}") + private String dataSnapshot; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionPageReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionPageReqVO.java new file mode 100644 index 00000000..6a057825 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionPageReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.subscription; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 数据同步订阅分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DatabusSyncSubscriptionPageReqVO extends PageParam { + + @Schema(description = "客户端ID", example = "1") + private Long clientId; + + @Schema(description = "事件ID", example = "1") + private Long eventId; + + @Schema(description = "启用状态(0-禁用 1-启用)", example = "1") + private Integer enabled; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionRespVO.java new file mode 100644 index 00000000..50a8500b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionRespVO.java @@ -0,0 +1,78 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.subscription; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 数据同步订阅 Response VO") +@Data +public class DatabusSyncSubscriptionRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "租户ID", example = "1") + private Long tenantId; + + @Schema(description = "客户端ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long clientId; + + @Schema(description = "客户端编码(关联查询)", example = "company-a") + private String clientCode; + + @Schema(description = "客户端名称(关联查询)", example = "A分公司") + private String clientName; + + @Schema(description = "事件ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long eventId; + + @Schema(description = "事件类型(关联查询)", example = "user-changed") + private String eventType; + + @Schema(description = "事件名称(关联查询)", example = "用户信息变更") + private String eventName; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer enabled; + + @Schema(description = "同步模式(FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL") + private String syncMode; + + @Schema(description = "时效性(REALTIME-实时 NEAR_REALTIME-准实时 BATCH-批量)", example = "NEAR_REALTIME") + private String timeliness; + + @Schema(description = "时效值(秒)", example = "60") + private Integer timelinessValue; + + @Schema(description = "批量大小", example = "500") + private Integer batchSize; + + @Schema(description = "最后同步的事件记录ID", example = "10001") + private Long lastSyncEventId; + + @Schema(description = "最后同步时间") + private LocalDateTime lastSyncTime; + + @Schema(description = "总同步次数", example = "100") + private Long totalSyncCount; + + @Schema(description = "成功次数", example = "95") + private Long totalSuccessCount; + + @Schema(description = "失败次数", example = "5") + private Long totalFailCount; + + @Schema(description = "创建者", example = "admin") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新者", example = "admin") + private String updater; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionSaveReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionSaveReqVO.java new file mode 100644 index 00000000..83caeba4 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionSaveReqVO.java @@ -0,0 +1,39 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.subscription; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 数据同步订阅创建/修改 Request VO") +@Data +public class DatabusSyncSubscriptionSaveReqVO { + + @Schema(description = "主键ID", example = "1") + private Long id; + + @Schema(description = "客户端ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "客户端ID不能为空") + private Long clientId; + + @Schema(description = "事件ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "事件ID不能为空") + private Long eventId; + + @Schema(description = "启用状态(0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; + + @Schema(description = "同步模式(FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL") + private String syncMode; + + @Schema(description = "时效性(REALTIME-实时 NEAR_REALTIME-准实时 BATCH-批量)", example = "NEAR_REALTIME") + private String timeliness; + + @Schema(description = "时效值(秒)", example = "60") + private Integer timelinessValue; + + @Schema(description = "批量大小", example = "500") + private Integer batchSize; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncClientConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncClientConvert.java new file mode 100644 index 00000000..0ec678c2 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncClientConvert.java @@ -0,0 +1,25 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncClientConvert { + + DatabusSyncClientConvert INSTANCE = Mappers.getMapper(DatabusSyncClientConvert.class); + + DatabusSyncClientDO convert(DatabusSyncClientSaveReqVO bean); + + DatabusSyncClientRespVO convert(DatabusSyncClientDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncDeadLetterConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncDeadLetterConvert.java new file mode 100644 index 00000000..c8c87784 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncDeadLetterConvert.java @@ -0,0 +1,22 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterRespVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncDeadLetterConvert { + + DatabusSyncDeadLetterConvert INSTANCE = Mappers.getMapper(DatabusSyncDeadLetterConvert.class); + + DatabusSyncDeadLetterRespVO convert(DatabusSyncDeadLetterDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncEventConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncEventConvert.java new file mode 100644 index 00000000..24ea52cd --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncEventConvert.java @@ -0,0 +1,25 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncEventConvert { + + DatabusSyncEventConvert INSTANCE = Mappers.getMapper(DatabusSyncEventConvert.class); + + DatabusSyncEventDO convert(DatabusSyncEventSaveReqVO bean); + + DatabusSyncEventRespVO convert(DatabusSyncEventDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncFullTaskConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncFullTaskConvert.java new file mode 100644 index 00000000..931aab0b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncFullTaskConvert.java @@ -0,0 +1,22 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskRespVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncFullTaskConvert { + + DatabusSyncFullTaskConvert INSTANCE = Mappers.getMapper(DatabusSyncFullTaskConvert.class); + + DatabusSyncFullTaskRespVO convert(DatabusSyncFullTaskDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncPushLogConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncPushLogConvert.java new file mode 100644 index 00000000..7a1ddc30 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncPushLogConvert.java @@ -0,0 +1,22 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogRespVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncPushLogConvert { + + DatabusSyncPushLogConvert INSTANCE = Mappers.getMapper(DatabusSyncPushLogConvert.class); + + DatabusSyncPushLogRespVO convert(DatabusSyncLogDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncSubscriptionConvert.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncSubscriptionConvert.java new file mode 100644 index 00000000..fe9f4b61 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/convert/DatabusSyncSubscriptionConvert.java @@ -0,0 +1,25 @@ +package com.zt.plat.framework.databus.server.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionRespVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DatabusSyncSubscriptionConvert { + + DatabusSyncSubscriptionConvert INSTANCE = Mappers.getMapper(DatabusSyncSubscriptionConvert.class); + + DatabusSyncSubscriptionDO convert(DatabusSyncSubscriptionSaveReqVO bean); + + DatabusSyncSubscriptionRespVO convert(DatabusSyncSubscriptionDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/event/DatabusEvent.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/event/DatabusEvent.java new file mode 100644 index 00000000..5f209157 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/event/DatabusEvent.java @@ -0,0 +1,71 @@ +package com.zt.plat.framework.databus.server.core.event; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * Databus 事件对象 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusEvent { + + /** + * 事件类型编码 + */ + private String eventType; + + /** + * 事件动作(create-创建 update-更新 delete-删除) + */ + private String eventAction; + + /** + * 完整业务数据快照(JSON格式) + */ + private String dataSnapshot; + + /** + * 数据版本号 + */ + private Integer dataVersion; + + /** + * 来源服务名 + */ + private String sourceService; + + /** + * 来源MQ Topic + */ + private String sourceTopic; + + /** + * 来源MQ消息ID + */ + private String sourceMsgId; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 操作人 + */ + private String operator; + + /** + * 事件发生时间 + */ + private LocalDateTime eventTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/BatchSyncMessage.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/BatchSyncMessage.java new file mode 100644 index 00000000..6b4abd37 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/BatchSyncMessage.java @@ -0,0 +1,121 @@ +package com.zt.plat.framework.databus.server.core.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 批量数据同步消息 + *

+ * 用于全量同步和批量增量同步,一条消息包含多条数据 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BatchSyncMessage { + + /** + * 消息ID(唯一标识) + */ + private String messageId; + + /** + * 请求ID(用于追踪) + */ + private String requestId; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 同步模式(1-全量 2-增量) + */ + private Integer syncMode; + + /** + * 数据结构版本 + */ + private Integer dataVersion; + + /** + * 消息时间戳 + */ + private Long timestamp; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 数据列表 + */ + private List dataList; + + /** + * 本批次数据条数 + */ + private Integer count; + + // ========== 全量同步相关字段 ========== + + /** + * 全量任务ID + */ + private Long fullTaskId; + + /** + * 当前批次号 + */ + private Integer batchNo; + + /** + * 总批次数 + */ + private Integer totalBatch; + + /** + * 是否最后一批 + */ + private Boolean isLastBatch; + + /** + * 总数据量 + */ + private Long totalCount; + + /** + * 同步数据项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SyncDataItem { + + /** + * 操作类型:CREATE/UPDATE/DELETE + */ + private String action; + + /** + * 业务数据ID + */ + private Long uid; + + /** + * 业务数据快照(JSON) + */ + private String data; + + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/SyncMessage.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/SyncMessage.java new file mode 100644 index 00000000..4a1b0db8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/message/SyncMessage.java @@ -0,0 +1,54 @@ +package com.zt.plat.framework.databus.server.core.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 数据同步消息 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SyncMessage { + + /** + * 同步ID(唯一标识) + */ + private String syncId; + + /** + * 事件记录ID + */ + private Long eventRecordId; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 事件动作 + */ + private String eventAction; + + /** + * 业务数据快照(JSON) + */ + private String dataSnapshot; + + /** + * 数据版本 + */ + private Integer dataVersion; + + /** + * 时间戳 + */ + private Long timestamp; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProvider.java new file mode 100644 index 00000000..faf6ea34 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProvider.java @@ -0,0 +1,120 @@ +package com.zt.plat.framework.databus.server.core.provider; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 数据提供者接口 + *

+ * 用于全量同步时获取数据,由业务模块实现具体的数据获取逻辑 + * + * @author ZT + */ +public interface DataProvider { + + /** + * 获取数据提供者类型标识 + * + * @return 类型标识,如 "ORG"、"USER" + */ + String getProviderType(); + + /** + * 游标分页获取数据 + * + * @param cursorTime 游标时间(首次查询传 null) + * @param cursorId 游标ID(首次查询传 null) + * @param batchSize 批量大小 + * @param tenantId 租户ID + * @return 分页结果 + */ + CursorPageData getPageByCursor(LocalDateTime cursorTime, Long cursorId, int batchSize, Long tenantId); + + /** + * 统计总数 + * + * @param tenantId 租户ID + * @return 总数量 + */ + long count(Long tenantId); + + /** + * 从数据对象中提取 UID + * + * @param data 数据对象 + * @return UID + */ + Long extractUid(T data); + + /** + * 游标分页数据结果 + */ + class CursorPageData { + private List list; + private LocalDateTime nextCursorTime; + private Long nextCursorId; + private int count; + private boolean hasMore; + private long total; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public LocalDateTime getNextCursorTime() { + return nextCursorTime; + } + + public void setNextCursorTime(LocalDateTime nextCursorTime) { + this.nextCursorTime = nextCursorTime; + } + + public Long getNextCursorId() { + return nextCursorId; + } + + public void setNextCursorId(Long nextCursorId) { + this.nextCursorId = nextCursorId; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public boolean isHasMore() { + return hasMore; + } + + public void setHasMore(boolean hasMore) { + this.hasMore = hasMore; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public static CursorPageData of(List list, LocalDateTime nextCursorTime, Long nextCursorId, + int count, boolean hasMore, long total) { + CursorPageData data = new CursorPageData<>(); + data.setList(list); + data.setNextCursorTime(nextCursorTime); + data.setNextCursorId(nextCursorId); + data.setCount(count); + data.setHasMore(hasMore); + data.setTotal(total); + return data; + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProviderRegistry.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProviderRegistry.java new file mode 100644 index 00000000..36ccf227 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/provider/DataProviderRegistry.java @@ -0,0 +1,62 @@ +package com.zt.plat.framework.databus.server.core.provider; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 数据提供者注册中心 + *

+ * 管理所有注册的数据提供者,根据类型获取对应的提供者 + * + * @author ZT + */ +@Slf4j +@Component +public class DataProviderRegistry { + + private final Map> providers = new ConcurrentHashMap<>(); + + /** + * 注册数据提供者 + * + * @param provider 数据提供者 + */ + public void register(DataProvider provider) { + String type = provider.getProviderType(); + providers.put(type, provider); + log.info("[Databus] 数据提供者已注册: type={}", type); + } + + /** + * 获取数据提供者 + * + * @param type 类型标识 + * @return 数据提供者,不存在则返回 null + */ + @SuppressWarnings("unchecked") + public DataProvider getProvider(String type) { + return (DataProvider) providers.get(type); + } + + /** + * 检查是否存在指定类型的提供者 + * + * @param type 类型标识 + * @return 是否存在 + */ + public boolean hasProvider(String type) { + return providers.containsKey(type); + } + + /** + * 获取所有已注册的提供者类型 + * + * @return 类型列表 + */ + public java.util.Set getRegisteredTypes() { + return providers.keySet(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisher.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisher.java new file mode 100644 index 00000000..b151f88c --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisher.java @@ -0,0 +1,28 @@ +package com.zt.plat.framework.databus.server.core.publisher; + +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; + +/** + * Databus 事件发布器接口 + * 用于发布业务数据变更事件到同步流水表 + * + * @author ZT + */ +public interface DatabusEventPublisher { + + /** + * 发布事件(异步) + * + * @param event 事件对象 + */ + void publish(DatabusEvent event); + + /** + * 发布事件(同步,等待入库完成) + * + * @param event 事件对象 + * @return 事件记录ID + */ + Long publishSync(DatabusEvent event); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java new file mode 100644 index 00000000..2ac2c33e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java @@ -0,0 +1,60 @@ +package com.zt.plat.framework.databus.server.core.publisher; + +import cn.hutool.core.bean.BeanUtil; +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Databus 事件发布器实现 + * + * @author ZT + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DatabusEventPublisherImpl implements DatabusEventPublisher { + + private final DatabusSyncEventRecordMapper eventRecordMapper; + + @Async + @Override + public void publish(DatabusEvent event) { + try { + saveEventRecord(event); + } catch (Exception e) { + log.error("[Databus] 发布事件失败, eventType={}, eventAction={}", + event.getEventType(), event.getEventAction(), e); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long publishSync(DatabusEvent event) { + return saveEventRecord(event); + } + + /** + * 保存事件记录到流水表 + */ + private Long saveEventRecord(DatabusEvent event) { + // 查询事件定义ID(这里简化处理,实际应该注入EventMapper查询) + // TODO: 根据 eventType 查询 event_id + + DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO(); + BeanUtil.copyProperties(event, record); + + eventRecordMapper.insert(record); + + log.info("[Databus] 事件记录已保存, id={}, eventType={}, eventAction={}", + record.getId(), event.getEventType(), event.getEventAction()); + + return record.getId(); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusher.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusher.java new file mode 100644 index 00000000..101dd4b8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusher.java @@ -0,0 +1,50 @@ +package com.zt.plat.framework.databus.server.core.pusher; + +import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; + +/** + * 消息推送器接口 + * 支持 MQ 和 HTTP 两种推送方式 + * + * @author ZT + */ +public interface MessagePusher { + + /** + * 通过 MQ 推送消息 + * + * @param topic Topic + * @param message 消息内容 + * @return 消息ID + */ + String pushByMQ(String topic, SyncMessage message); + + /** + * 通过 HTTP 推送消息 + * + * @param endpoint HTTP端点 + * @param message 消息内容 + * @return 是否成功 + */ + boolean pushByHttp(String endpoint, SyncMessage message); + + /** + * 通过 MQ 推送批量消息 + * + * @param topic Topic + * @param message 批量消息内容 + * @return 消息ID + */ + String pushBatchByMQ(String topic, BatchSyncMessage message); + + /** + * 通过 HTTP 推送批量消息 + * + * @param endpoint HTTP端点 + * @param message 批量消息内容 + * @return 是否成功 + */ + boolean pushBatchByHttp(String endpoint, BatchSyncMessage message); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusherImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusherImpl.java new file mode 100644 index 00000000..39955102 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/pusher/MessagePusherImpl.java @@ -0,0 +1,103 @@ +package com.zt.plat.framework.databus.server.core.pusher; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.spring.core.RocketMQTemplate; + +/** + * 消息推送器实现 + * + * @author ZT + */ +@Slf4j +public class MessagePusherImpl implements MessagePusher { + + private RocketMQTemplate rocketMQTemplate; + + public void setRocketMQTemplate(RocketMQTemplate rocketMQTemplate) { + this.rocketMQTemplate = rocketMQTemplate; + } + + @Override + public String pushByMQ(String topic, SyncMessage message) { + if (rocketMQTemplate == null) { + log.warn("[Databus] RocketMQTemplate未配置,无法推送MQ消息"); + throw new RuntimeException("RocketMQTemplate未配置"); + } + + try { + // 使用 RocketMQ 发送消息 + SendResult sendResult = rocketMQTemplate.syncSend(topic, message); + if (sendResult.getSendStatus() != SendStatus.SEND_OK) { + log.error("[Databus] MQ推送失败, topic={}, syncId={}, status={}, msgId={}", + topic, message.getSyncId(), sendResult.getSendStatus(), sendResult.getMsgId()); + throw new RuntimeException("MQ推送失败: " + sendResult.getSendStatus()); + } + log.info("[Databus] MQ推送成功, topic={}, syncId={}, msgId={}", + topic, message.getSyncId(), sendResult.getMsgId()); + return message.getSyncId(); + } catch (Exception e) { + log.error("[Databus] MQ推送失败, topic={}, syncId={}", topic, message.getSyncId(), e); + throw new RuntimeException("MQ推送失败", e); + } + } + + @Override + public boolean pushByHttp(String endpoint, SyncMessage message) { + try { + String jsonBody = JSONUtil.toJsonStr(message); + String response = HttpUtil.post(endpoint, jsonBody); + log.info("[Databus] HTTP推送成功, endpoint={}, syncId={}", endpoint, message.getSyncId()); + return true; + } catch (Exception e) { + log.error("[Databus] HTTP推送失败, endpoint={}, syncId={}", endpoint, message.getSyncId(), e); + return false; + } + } + + @Override + public String pushBatchByMQ(String topic, BatchSyncMessage message) { + if (rocketMQTemplate == null) { + log.warn("[Databus] RocketMQTemplate未配置,无法推送MQ消息"); + throw new RuntimeException("RocketMQTemplate未配置"); + } + + try { + // 使用 RocketMQ 发送批量消息 + SendResult sendResult = rocketMQTemplate.syncSend(topic, message); + if (sendResult.getSendStatus() != SendStatus.SEND_OK) { + log.error("[Databus] 批量MQ推送失败, topic={}, messageId={}, status={}, mqMsgId={}", + topic, message.getMessageId(), sendResult.getSendStatus(), sendResult.getMsgId()); + throw new RuntimeException("批量MQ推送失败: " + sendResult.getSendStatus()); + } + log.info("[Databus] 批量MQ推送成功, topic={}, messageId={}, count={}, mqMsgId={}", + topic, message.getMessageId(), message.getCount(), sendResult.getMsgId()); + return message.getMessageId(); + } catch (Exception e) { + log.error("[Databus] 批量MQ推送失败, topic={}, messageId={}", + topic, message.getMessageId(), e); + throw new RuntimeException("批量MQ推送失败", e); + } + } + + @Override + public boolean pushBatchByHttp(String endpoint, BatchSyncMessage message) { + try { + String jsonBody = JSONUtil.toJsonStr(message); + String response = HttpUtil.post(endpoint, jsonBody); + log.info("[Databus] 批量HTTP推送成功, endpoint={}, messageId={}, count={}", + endpoint, message.getMessageId(), message.getCount()); + return true; + } catch (Exception e) { + log.error("[Databus] 批量HTTP推送失败, endpoint={}, messageId={}", + endpoint, message.getMessageId(), e); + return false; + } + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusFullSyncService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusFullSyncService.java new file mode 100644 index 00000000..62d00b16 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusFullSyncService.java @@ -0,0 +1,51 @@ +package com.zt.plat.framework.databus.server.core.sync; + +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO; + +/** + * Databus 全量同步服务接口 + * + * @author ZT + */ +public interface DatabusFullSyncService { + + /** + * 创建全量同步任务 + * + * @param subscriptionId 订阅关系ID + * @param remark 备注 + * @return 任务ID + */ + Long createFullSyncTask(Long subscriptionId, String remark); + + /** + * 执行全量同步任务 + * + * @param taskId 任务ID + */ + void executeFullSyncTask(Long taskId); + + /** + * 取消全量同步任务 + * + * @param taskId 任务ID + */ + void cancelFullSyncTask(Long taskId); + + /** + * 获取任务详情 + * + * @param taskId 任务ID + * @return 任务详情 + */ + DatabusSyncFullTaskDO getTaskById(Long taskId); + + /** + * 根据任务编号获取任务详情 + * + * @param taskNo 任务编号 + * @return 任务详情 + */ + DatabusSyncFullTaskDO getTaskByTaskNo(String taskNo); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncService.java new file mode 100644 index 00000000..5baeb472 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncService.java @@ -0,0 +1,27 @@ +package com.zt.plat.framework.databus.server.core.sync; + +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; + +/** + * Databus 增量同步服务接口 + * 用于实时处理业务MQ消息,进行三态判断、记录流水、推送到客户端Topic + * + * @author ZT + */ +public interface DatabusIncrementalSyncService { + + /** + * 处理增量同步事件 + *

+ * 核心流程: + * 1. 根据 eventType 查询事件定义,判断是否启用 + * 2. 查询所有启用的订阅关系 + * 3. 对每个订阅进行三态判断(事件/客户端/订阅是否启用) + * 4. 记录到 databus_sync_event_record 流水表 + * 5. 推送到客户端专属 Topic(databus-sync-{eventType}-{clientCode}) + * + * @param event 业务变更事件 + */ + void processEvent(DatabusEvent event); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java new file mode 100644 index 00000000..99766a04 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java @@ -0,0 +1,246 @@ +package com.zt.plat.framework.databus.server.core.sync; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.dal.dataobject.*; +import com.zt.plat.framework.databus.server.dal.mapper.*; +import com.zt.plat.framework.databus.server.enums.SyncStatusEnum; +import com.zt.plat.framework.databus.server.enums.TransportTypeEnum; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; + +/** + * Databus 增量同步服务实现 + *

+ * 核心流程: + * 1. 根据 eventType 查询事件定义,判断是否启用 + * 2. 记录到 event_record 流水表 + * 3. 查询所有启用的订阅关系 + * 4. 对每个订阅进行三态判断(事件/客户端/订阅是否启用) + * 5. 推送到客户端专属 Topic(databus-sync-{eventType}-{clientCode}) + * + * @author ZT + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DatabusIncrementalSyncServiceImpl implements DatabusIncrementalSyncService { + + private final DatabusSyncEventMapper eventMapper; + private final DatabusSyncEventRecordMapper eventRecordMapper; + private final DatabusSyncSubscriptionMapper subscriptionMapper; + private final DatabusSyncClientMapper clientMapper; + private final DatabusSyncLogMapper syncLogMapper; + private final MessagePusher messagePusher; + + @Override + @Transactional(rollbackFor = Exception.class) + public void processEvent(DatabusEvent event) { + log.info("[Databus增量同步] 开始处理事件, eventType={}, eventAction={}", + event.getEventType(), event.getEventAction()); + + // 1. 查询事件定义 + DatabusSyncEventDO eventDef = eventMapper.selectByEventType(event.getEventType()); + if (eventDef == null) { + log.warn("[Databus增量同步] 事件类型未定义, eventType={}", event.getEventType()); + return; + } + + // 2. 判断事件是否启用(三态判断之一) + if (eventDef.getEnabled() != 1) { + log.debug("[Databus增量同步] 事件未启用, eventType={}", event.getEventType()); + return; + } + + // 3. 判断事件是否支持增量同步 + if (eventDef.getSupportIncrementalSync() != 1) { + log.debug("[Databus增量同步] 事件不支持增量同步, eventType={}", event.getEventType()); + return; + } + + // 4. 记录到 event_record 流水表 + DatabusSyncEventRecordDO eventRecord = saveEventRecord(event, eventDef); + + // 5. 查询所有启用的订阅关系 + List subscriptions = subscriptionMapper.selectEnabledByEventId(eventDef.getId()); + if (subscriptions.isEmpty()) { + log.debug("[Databus增量同步] 无启用的订阅, eventType={}", event.getEventType()); + return; + } + + log.info("[Databus增量同步] 找到{}个订阅需要推送, eventType={}", + subscriptions.size(), event.getEventType()); + + // 6. 对每个订阅进行推送 + for (DatabusSyncSubscriptionDO subscription : subscriptions) { + try { + pushToSubscriber(subscription, eventDef, eventRecord); + } catch (Exception e) { + log.error("[Databus增量同步] 推送到订阅者失败, subscriptionId={}, eventType={}", + subscription.getId(), event.getEventType(), e); + } + } + + log.info("[Databus增量同步] 事件处理完成, eventType={}, eventAction={}, recordId={}", + event.getEventType(), event.getEventAction(), eventRecord.getId()); + } + + /** + * 保存事件记录到流水表 + */ + private DatabusSyncEventRecordDO saveEventRecord(DatabusEvent event, DatabusSyncEventDO eventDef) { + DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO(); + BeanUtil.copyProperties(event, record); + record.setEventId(eventDef.getId()); + + eventRecordMapper.insert(record); + + log.info("[Databus增量同步] 事件记录已保存, recordId={}, eventType={}, eventAction={}", + record.getId(), event.getEventType(), event.getEventAction()); + + return record; + } + + /** + * 推送到单个订阅者 + */ + private void pushToSubscriber(DatabusSyncSubscriptionDO subscription, + DatabusSyncEventDO eventDef, + DatabusSyncEventRecordDO eventRecord) { + // 查询客户端信息 + DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId()); + if (client == null) { + log.warn("[Databus增量同步] 客户端不存在, clientId={}", subscription.getClientId()); + return; + } + + // 三态判断之二:客户端是否启用 + if (client.getEnabled() != 1) { + log.debug("[Databus增量同步] 客户端未启用, clientCode={}", client.getClientCode()); + return; + } + + // 三态判断之三:订阅是否启用(已在查询时过滤,这里再确认一次) + if (subscription.getEnabled() != 1) { + log.debug("[Databus增量同步] 订阅未启用, subscriptionId={}", subscription.getId()); + return; + } + + String syncId = IdUtil.fastSimpleUUID(); + LocalDateTime startTime = LocalDateTime.now(); + + // 构建同步消息 + SyncMessage message = SyncMessage.builder() + .syncId(syncId) + .eventRecordId(eventRecord.getId()) + .eventType(eventRecord.getEventType()) + .eventAction(eventRecord.getEventAction()) + .dataSnapshot(eventRecord.getDataSnapshot()) + .dataVersion(eventRecord.getDataVersion()) + .timestamp(System.currentTimeMillis()) + .build(); + + // 构建同步日志 + DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder() + .syncId(syncId) + .eventRecordId(eventRecord.getId()) + .subscriptionId(subscription.getId()) + .clientCode(client.getClientCode()) + .eventType(eventRecord.getEventType()) + .syncMode(2) // 增量同步 + .startTime(startTime) + .status(SyncStatusEnum.PENDING.getStatus()) + .retryCount(0) + .dataCount(1) + .tenantId(client.getTenantId()) + .build(); + + try { + // 根据客户端配置选择推送方式 + if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) { + // MQ 推送:Topic格式 = databus-sync-{eventType}-{clientCode} + String topic = buildClientTopic(client.getMqTopicBase(), eventDef.getEventType(), client.getClientCode()); + String mqMsgId = messagePusher.pushByMQ(topic, message); + + syncLog.setTransportType(TransportTypeEnum.MQ_FIRST.getType()); + syncLog.setMqTopic(topic); + syncLog.setMqMsgId(mqMsgId); + + log.info("[Databus增量同步] MQ推送成功, topic={}, syncId={}", topic, syncId); + + } else if (client.getHttpEnabled() == 1) { + // HTTP 推送 + boolean success = messagePusher.pushByHttp(client.getHttpEndpoint(), message); + if (!success) { + throw new RuntimeException("HTTP推送失败"); + } + + syncLog.setTransportType(TransportTypeEnum.HTTP_ONLY.getType()); + + log.info("[Databus增量同步] HTTP推送成功, endpoint={}, syncId={}", + client.getHttpEndpoint(), syncId); + + } else { + throw new RuntimeException("无可用的推送方式"); + } + + // 更新日志状态为成功 + syncLog.setStatus(SyncStatusEnum.SUCCESS.getStatus()); + syncLog.setEndTime(LocalDateTime.now()); + syncLog.setDuration((int) (System.currentTimeMillis() - + startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())); + + // 更新订阅断点 + updateSubscriptionCheckpoint(subscription, eventRecord.getId()); + + } catch (Exception e) { + log.error("[Databus增量同步] 推送失败, syncId={}, clientCode={}", syncId, client.getClientCode(), e); + + syncLog.setStatus(SyncStatusEnum.FAILED.getStatus()); + syncLog.setErrorMessage(e.getMessage()); + syncLog.setEndTime(LocalDateTime.now()); + } + + // 保存同步日志 + syncLogMapper.insert(syncLog); + } + + /** + * 构建客户端专属Topic + * 格式: {topicBase}-{module}-{entity}-{action}-{clientCode} + * 示例: databus-sync-system-org-create-company-a + * + * @param topicBase 基础Topic名称(如 databus-sync) + * @param eventType 事件类型(格式: system-org-create) + * @param clientCode 客户端编码 + */ + private String buildClientTopic(String topicBase, String eventType, String clientCode) { + // 默认topicBase为 databus-sync + if (topicBase == null || topicBase.isEmpty()) { + topicBase = "databus-sync"; + } + // eventType 格式已经是 system-org-create,直接拼接 + return String.format("%s-%s-%s", topicBase, eventType.toLowerCase(), clientCode); + } + + /** + * 更新订阅断点 + */ + private void updateSubscriptionCheckpoint(DatabusSyncSubscriptionDO subscription, Long eventRecordId) { + subscription.setLastSyncEventId(eventRecordId); + subscription.setLastSyncTime(LocalDateTime.now()); + subscription.setTotalSyncCount((subscription.getTotalSyncCount() == null ? 0 : subscription.getTotalSyncCount()) + 1); + subscription.setTotalSuccessCount((subscription.getTotalSuccessCount() == null ? 0 : subscription.getTotalSuccessCount()) + 1); + subscriptionMapper.updateById(subscription); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncService.java new file mode 100644 index 00000000..00fd9b78 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncService.java @@ -0,0 +1,24 @@ +package com.zt.plat.framework.databus.server.core.sync; + +/** + * Databus 同步服务接口 + * 负责扫描事件记录并推送到客户端 + * + * @author ZT + */ +public interface DatabusSyncService { + + /** + * 执行同步任务 + * 扫描所有启用的订阅关系,推送新事件到客户端 + */ + void executeSyncTask(); + + /** + * 手动触发指定订阅的同步 + * + * @param subscriptionId 订阅关系ID + */ + void triggerSync(Long subscriptionId); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncServiceImpl.java new file mode 100644 index 00000000..72c50892 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusSyncServiceImpl.java @@ -0,0 +1,237 @@ +package com.zt.plat.framework.databus.server.core.sync; + +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zt.plat.framework.databus.server.config.DatabusSyncServerProperties; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.dal.dataobject.*; +import com.zt.plat.framework.databus.server.dal.mapper.*; +import com.zt.plat.framework.databus.server.enums.DeadLetterStatusEnum; +import com.zt.plat.framework.databus.server.enums.SyncStatusEnum; +import com.zt.plat.framework.databus.server.enums.TransportTypeEnum; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * Databus 同步服务实现 + * + * @author ZT + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DatabusSyncServiceImpl implements DatabusSyncService { + + private final DatabusSyncSubscriptionMapper subscriptionMapper; + private final DatabusSyncEventRecordMapper eventRecordMapper; + private final DatabusSyncClientMapper clientMapper; + private final DatabusSyncEventMapper eventMapper; + private final DatabusSyncLogMapper syncLogMapper; + private final DatabusSyncDeadLetterMapper deadLetterMapper; + private final MessagePusher messagePusher; + private final DatabusSyncServerProperties properties; + + @Override + public void executeSyncTask() { + log.info("[Databus] 开始执行同步任务"); + + // 查询所有启用的订阅关系 + List subscriptions = subscriptionMapper.selectList( + new LambdaQueryWrapper() + .eq(DatabusSyncSubscriptionDO::getEnabled, 1) + ); + + for (DatabusSyncSubscriptionDO subscription : subscriptions) { + try { + processSubscription(subscription); + } catch (Exception e) { + log.error("[Databus] 处理订阅失败, subscriptionId={}", subscription.getId(), e); + } + } + + log.info("[Databus] 同步任务执行完成, 处理订阅数={}", subscriptions.size()); + } + + @Override + public void triggerSync(Long subscriptionId) { + DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(subscriptionId); + if (subscription == null) { + log.warn("[Databus] 订阅不存在, subscriptionId={}", subscriptionId); + return; + } + + processSubscription(subscription); + } + + /** + * 处理单个订阅 + */ + @Transactional(rollbackFor = Exception.class) + protected void processSubscription(DatabusSyncSubscriptionDO subscription) { + // 三状态启用检查 + DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId()); + if (client == null || client.getEnabled() != 1) { + log.debug("[Databus] 客户端未启用, clientId={}", subscription.getClientId()); + return; + } + + DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId()); + if (event == null || event.getEnabled() != 1) { + log.debug("[Databus] 事件未启用, eventId={}", subscription.getEventId()); + return; + } + + // 查询新事件记录(断点续传) + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(DatabusSyncEventRecordDO::getEventId, subscription.getEventId()) + .orderByAsc(DatabusSyncEventRecordDO::getId); + + if (subscription.getLastSyncEventId() != null) { + queryWrapper.gt(DatabusSyncEventRecordDO::getId, subscription.getLastSyncEventId()); + } + + // 批量查询 - 使用 Page 分页,兼容达梦等数据库 + Page page = new Page<>(1, subscription.getBatchSize(), false); + List records = eventRecordMapper.selectPage(page, queryWrapper).getRecords(); + + if (records.isEmpty()) { + log.debug("[Databus] 无新事件, subscriptionId={}", subscription.getId()); + return; + } + + log.info("[Databus] 开始同步, subscriptionId={}, eventCount={}", subscription.getId(), records.size()); + + // 推送事件 + for (DatabusSyncEventRecordDO record : records) { + boolean success = pushEvent(subscription, client, record); + if (success) { + // 更新断点 + subscription.setLastSyncEventId(record.getId()); + subscription.setLastSyncTime(LocalDateTime.now()); + subscription.setTotalSyncCount(subscription.getTotalSyncCount() + 1); + subscription.setTotalSuccessCount(subscription.getTotalSuccessCount() + 1); + } else { + subscription.setTotalFailCount(subscription.getTotalFailCount() + 1); + break; // 失败则停止当前批次 + } + } + + // 更新订阅统计 + subscriptionMapper.updateById(subscription); + } + + /** + * 推送单个事件 + */ + private boolean pushEvent(DatabusSyncSubscriptionDO subscription, + DatabusSyncClientDO client, + DatabusSyncEventRecordDO record) { + String syncId = IdUtil.fastSimpleUUID(); + LocalDateTime startTime = LocalDateTime.now(); + + // 构建消息 + SyncMessage message = SyncMessage.builder() + .syncId(syncId) + .eventRecordId(record.getId()) + .eventType(record.getEventType()) + .eventAction(record.getEventAction()) + .dataSnapshot(record.getDataSnapshot()) + .dataVersion(record.getDataVersion()) + .timestamp(System.currentTimeMillis()) + .build(); + + // 记录日志 + DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder() + .syncId(syncId) + .eventRecordId(record.getId()) + .subscriptionId(subscription.getId()) + .clientCode(client.getClientCode()) + .eventType(record.getEventType()) + .syncMode(subscription.getSyncMode()) + .startTime(startTime) + .status(SyncStatusEnum.PENDING.getStatus()) + .retryCount(0) + .tenantId(client.getTenantId()) + .build(); + + try { + // 推送消息 + if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) { + // MQ 推送 + String topic = String.format("%s-%s-%s", + client.getMqTopicBase(), record.getEventType(), client.getClientCode()); + String mqMsgId = messagePusher.pushByMQ(topic, message); + + syncLog.setTransportType(TransportTypeEnum.MQ_FIRST.getType()); + syncLog.setMqTopic(topic); + syncLog.setMqMsgId(mqMsgId); + } else if (client.getHttpEnabled() == 1) { + // HTTP 推送 + boolean success = messagePusher.pushByHttp(client.getHttpEndpoint(), message); + if (!success) { + throw new RuntimeException("HTTP推送失败"); + } + + syncLog.setTransportType(TransportTypeEnum.HTTP_ONLY.getType()); + } else { + throw new RuntimeException("无可用的推送方式"); + } + + // 更新日志状态 + syncLog.setStatus(SyncStatusEnum.SUCCESS.getStatus()); + syncLog.setEndTime(LocalDateTime.now()); + syncLog.setDuration((int) (System.currentTimeMillis() - startTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli())); + syncLogMapper.insert(syncLog); + + return true; + + } catch (Exception e) { + log.error("[Databus] 推送失败, syncId={}, eventRecordId={}", syncId, record.getId(), e); + + // 更新日志状态 + syncLog.setStatus(SyncStatusEnum.FAILED.getStatus()); + syncLog.setErrorMessage(e.getMessage()); + syncLog.setEndTime(LocalDateTime.now()); + syncLogMapper.insert(syncLog); + + // 检查重试次数,超过限制则进入死信队列 + if (syncLog.getRetryCount() >= properties.getRetry().getMaxAttempts()) { + saveToDeadLetter(syncLog, message); + } + + return false; + } + } + + /** + * 保存到死信队列 + */ + private void saveToDeadLetter(DatabusSyncLogDO syncLog, SyncMessage message) { + DatabusSyncDeadLetterDO deadLetter = DatabusSyncDeadLetterDO.builder() + .syncLogId(syncLog.getId()) + .syncId(syncLog.getSyncId()) + .clientCode(syncLog.getClientCode()) + .eventType(syncLog.getEventType()) + .eventRecordId(syncLog.getEventRecordId()) + .retryCount(syncLog.getRetryCount()) + .lastErrorMessage(syncLog.getErrorMessage()) + .lastErrorTime(LocalDateTime.now()) + .messageBody(cn.hutool.json.JSONUtil.toJsonStr(message)) + .status(DeadLetterStatusEnum.PENDING.getStatus()) + .handled(0) + .tenantId(syncLog.getTenantId()) + .build(); + + deadLetterMapper.insert(deadLetter); + + log.warn("[Databus] 消息已进入死信队列, syncId={}", syncLog.getSyncId()); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncClientDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncClientDO.java new file mode 100644 index 00000000..68a3f867 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncClientDO.java @@ -0,0 +1,84 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.*; + +/** + * 数据同步客户端配置 DO + * + * @author ZT + */ +@TableName("databus_sync_client") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncClientDO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 客户端编码 + */ + private String clientCode; + + /** + * 客户端名称 + */ + private String clientName; + + /** + * 启用状态(0-禁用 1-启用) + */ + private Integer enabled; + + /** + * 通信方式(1-MQ优先 2-仅HTTP) + */ + private Integer transportType; + + /** + * MQ是否启用(0-否 1-是) + */ + private Integer mqEnabled; + + /** + * MQ NameServer地址 + */ + private String mqNamesrvAddr; + + /** + * MQ Topic基础名称 + */ + private String mqTopicBase; + + /** + * HTTP是否启用(0-否 1-是) + */ + private Integer httpEnabled; + + /** + * HTTP推送端点 + */ + private String httpEndpoint; + + /** + * 应用Key + */ + private String appKey; + + /** + * 应用Secret(加密存储) + */ + private String appSecret; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncDeadLetterDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncDeadLetterDO.java new file mode 100644 index 00000000..f8067841 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncDeadLetterDO.java @@ -0,0 +1,106 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 数据同步死信队列 DO + * + * @author ZT + */ +@TableName("databus_sync_dead_letter") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncDeadLetterDO extends BaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 同步日志ID + */ + private Long syncLogId; + + /** + * 同步ID + */ + private String syncId; + + /** + * 客户端编码 + */ + private String clientCode; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 事件记录ID + */ + private Long eventRecordId; + + /** + * 已重试次数 + */ + private Integer retryCount; + + /** + * 最后错误信息 + */ + private String lastErrorMessage; + + /** + * 最后错误时间 + */ + private LocalDateTime lastErrorTime; + + /** + * 消息内容(JSON格式) + */ + private String messageBody; + + /** + * 状态(0-待处理 1-已重新投递 2-已忽略) + */ + private Integer status; + + /** + * 是否已处理(0-否 1-是) + */ + private Integer handled; + + /** + * 处理时间 + */ + private LocalDateTime handleTime; + + /** + * 处理人 + */ + private String handler; + + /** + * 处理备注 + */ + private String handleRemark; + + /** + * 租户ID + */ + private Long tenantId; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventDO.java new file mode 100644 index 00000000..7887c4ef --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventDO.java @@ -0,0 +1,76 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.*; + +/** + * 数据同步事件定义 DO + * + * @author ZT + */ +@TableName("databus_sync_event") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncEventDO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 事件类型编码 + */ + private String eventType; + + /** + * 事件名称 + */ + private String eventName; + + /** + * 事件描述 + */ + private String eventDesc; + + /** + * 启用状态(0-禁用 1-启用) + */ + private Integer enabled; + + /** + * 是否支持全量同步(0-否 1-是) + */ + private Integer supportFullSync; + + /** + * 是否支持增量同步(0-否 1-是) + */ + private Integer supportIncrementalSync; + + /** + * 数据结构版本 + */ + private Integer dataVersion; + + /** + * 数据提供者服务名(Feign服务名) + * 用于全量同步时调用对应服务获取数据 + */ + private String dataProviderService; + + /** + * 数据提供者方法标识 + * 如: ORG(组织)、USER(用户) + */ + private String dataProviderMethod; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventRecordDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventRecordDO.java new file mode 100644 index 00000000..a2239361 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncEventRecordDO.java @@ -0,0 +1,87 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 数据同步事件流水 DO + * 用于断点续传 + * + * @author ZT + */ +@TableName("databus_sync_event_record") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncEventRecordDO extends BaseDO { + + /** + * 主键ID(雪花算法,全局唯一递增) + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 事件定义ID + */ + private Long eventId; + + /** + * 事件类型编码 + */ + private String eventType; + + /** + * 事件动作(create-创建 update-更新 delete-删除) + */ + private String eventAction; + + /** + * 完整业务数据快照(JSON格式) + */ + private String dataSnapshot; + + /** + * 数据版本号 + */ + private Integer dataVersion; + + /** + * 来源服务名 + */ + private String sourceService; + + /** + * 来源MQ Topic + */ + private String sourceTopic; + + /** + * 来源MQ消息ID + */ + private String sourceMsgId; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 操作人 + */ + private String operator; + + /** + * 事件发生时间 + */ + private LocalDateTime eventTime; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncFullTaskDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncFullTaskDO.java new file mode 100644 index 00000000..6709a80d --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncFullTaskDO.java @@ -0,0 +1,126 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 数据全量同步任务 DO + * + * @author ZT + */ +@TableName("databus_sync_full_task") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncFullTaskDO extends BaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 任务编号(唯一标识) + */ + private String taskNo; + + /** + * 订阅关系ID + */ + private Long subscriptionId; + + /** + * 客户端编码 + */ + private String clientCode; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 任务状态(0-待执行 1-执行中 2-已完成 3-失败 4-已取消) + */ + private Integer status; + + /** + * 总数据量 + */ + private Long totalCount; + + /** + * 已处理数据量 + */ + private Long processedCount; + + /** + * 成功数据量 + */ + private Long successCount; + + /** + * 失败数据量 + */ + private Long failCount; + + /** + * 总批次数 + */ + private Integer totalBatch; + + /** + * 当前批次号 + */ + private Integer currentBatch; + + /** + * 批量大小 + */ + private Integer batchSize; + + /** + * 游标时间(断点续传) + */ + private LocalDateTime cursorTime; + + /** + * 游标ID(断点续传) + */ + private Long cursorId; + + /** + * 任务开始时间 + */ + private LocalDateTime startTime; + + /** + * 任务结束时间 + */ + private LocalDateTime endTime; + + /** + * 最后一次错误信息 + */ + private String lastErrorMessage; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 备注 + */ + private String remark; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncLogDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncLogDO.java new file mode 100644 index 00000000..51ff7534 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncLogDO.java @@ -0,0 +1,126 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 数据同步日志 DO + * + * @author ZT + */ +@TableName("databus_sync_log") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncLogDO extends BaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 同步ID(唯一标识) + */ + private String syncId; + + /** + * 事件记录ID + */ + private Long eventRecordId; + + /** + * 订阅关系ID + */ + private Long subscriptionId; + + /** + * 客户端编码 + */ + private String clientCode; + + /** + * 事件类型 + */ + private String eventType; + + /** + * 同步模式(1-全量 2-增量) + */ + private Integer syncMode; + + /** + * 传输方式(1-MQ优先 2-仅HTTP) + */ + private Integer transportType; + + /** + * MQ Topic + */ + private String mqTopic; + + /** + * MQ消息ID + */ + private String mqMsgId; + + /** + * 状态(0-待处理 1-成功 2-失败 3-重试中) + */ + private Integer status; + + /** + * 重试次数 + */ + private Integer retryCount; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 结束时间 + */ + private LocalDateTime endTime; + + /** + * 耗时(毫秒) + */ + private Integer duration; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 本批次数据条数 + */ + private Integer dataCount; + + /** + * 批次号(全量同步时使用) + */ + private Integer batchNo; + + /** + * 全量任务ID(全量同步时关联) + */ + private Long fullTaskId; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncSubscriptionDO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncSubscriptionDO.java new file mode 100644 index 00000000..a11aa085 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/dataobject/DatabusSyncSubscriptionDO.java @@ -0,0 +1,91 @@ +package com.zt.plat.framework.databus.server.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 数据同步订阅关系 DO + * + * @author ZT + */ +@TableName("databus_sync_subscription") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusSyncSubscriptionDO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 客户端ID + */ + private Long clientId; + + /** + * 事件ID + */ + private Long eventId; + + /** + * 订阅启用状态(0-禁用 1-启用) + */ + private Integer enabled; + + /** + * 同步模式(1-全量 2-增量) + */ + private Integer syncMode; + + /** + * 时效性(1-实时 2-准实时 3-批量) + */ + private Integer timeliness; + + /** + * 时效值(秒) + */ + private Integer timelinessValue; + + /** + * 批量大小 + */ + private Integer batchSize; + + /** + * 最后同步的事件记录ID(断点续传) + */ + private Long lastSyncEventId; + + /** + * 最后同步时间 + */ + private LocalDateTime lastSyncTime; + + /** + * 总同步次数 + */ + private Long totalSyncCount; + + /** + * 成功次数 + */ + private Long totalSuccessCount; + + /** + * 失败次数 + */ + private Long totalFailCount; + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncClientMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncClientMapper.java new file mode 100644 index 00000000..da34a1c5 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncClientMapper.java @@ -0,0 +1,38 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 数据同步客户端 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncClientMapper extends BaseMapperX { + + default DatabusSyncClientDO selectByClientCode(String clientCode) { + return selectOne(DatabusSyncClientDO::getClientCode, clientCode); + } + + default PageResult selectPage(DatabusSyncClientPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DatabusSyncClientDO::getClientCode, reqVO.getClientCode()) + .likeIfPresent(DatabusSyncClientDO::getClientName, reqVO.getClientName()) + .eqIfPresent(DatabusSyncClientDO::getEnabled, reqVO.getEnabled()) + .eqIfPresent(DatabusSyncClientDO::getTransportType, reqVO.getTransportType()) + .orderByDesc(DatabusSyncClientDO::getId)); + } + + default List selectList() { + return selectList(new LambdaQueryWrapperX() + .orderByDesc(DatabusSyncClientDO::getId)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncDeadLetterMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncDeadLetterMapper.java new file mode 100644 index 00000000..69ce53d3 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncDeadLetterMapper.java @@ -0,0 +1,29 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 数据同步死信队列 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncDeadLetterMapper extends BaseMapperX { + + default PageResult selectPage(DatabusSyncDeadLetterPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(DatabusSyncDeadLetterDO::getSyncId, reqVO.getSyncId()) + .likeIfPresent(DatabusSyncDeadLetterDO::getClientCode, reqVO.getClientCode()) + .likeIfPresent(DatabusSyncDeadLetterDO::getEventType, reqVO.getEventType()) + .eqIfPresent(DatabusSyncDeadLetterDO::getStatus, reqVO.getStatus()) + .eqIfPresent(DatabusSyncDeadLetterDO::getHandled, reqVO.getHandled()) + .betweenIfPresent(DatabusSyncDeadLetterDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd()) + .orderByDesc(DatabusSyncDeadLetterDO::getId)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventMapper.java new file mode 100644 index 00000000..a7a9880f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventMapper.java @@ -0,0 +1,37 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 数据同步事件定义 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncEventMapper extends BaseMapperX { + + default DatabusSyncEventDO selectByEventType(String eventType) { + return selectOne(DatabusSyncEventDO::getEventType, eventType); + } + + default PageResult selectPage(DatabusSyncEventPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DatabusSyncEventDO::getEventType, reqVO.getEventType()) + .likeIfPresent(DatabusSyncEventDO::getEventName, reqVO.getEventName()) + .eqIfPresent(DatabusSyncEventDO::getEnabled, reqVO.getEnabled()) + .orderByDesc(DatabusSyncEventDO::getId)); + } + + default List selectList() { + return selectList(new LambdaQueryWrapperX() + .orderByDesc(DatabusSyncEventDO::getId)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventRecordMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventRecordMapper.java new file mode 100644 index 00000000..7f26d3d9 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncEventRecordMapper.java @@ -0,0 +1,15 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 数据同步事件流水 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncEventRecordMapper extends BaseMapperX { + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncFullTaskMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncFullTaskMapper.java new file mode 100644 index 00000000..5f3d6e70 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncFullTaskMapper.java @@ -0,0 +1,62 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 数据全量同步任务 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncFullTaskMapper extends BaseMapperX { + + /** + * 根据任务编号查询 + */ + default DatabusSyncFullTaskDO selectByTaskNo(String taskNo) { + return selectOne(DatabusSyncFullTaskDO::getTaskNo, taskNo); + } + + /** + * 查询指定订阅的进行中任务 + */ + default DatabusSyncFullTaskDO selectRunningBySubscriptionId(Long subscriptionId) { + return selectOne(new LambdaQueryWrapperX() + .eq(DatabusSyncFullTaskDO::getSubscriptionId, subscriptionId) + .in(DatabusSyncFullTaskDO::getStatus, 0, 1)); // 待执行或执行中 + } + + /** + * 查询待执行的任务列表 + * 使用 MyBatis-Plus 分页机制,兼容达梦等数据库 + */ + default List selectPendingTasks(int limit) { + // 使用 Page 分页而不是 LIMIT 语法,以兼容达梦数据库 + Page page = new Page<>(1, limit, false); + return selectPage(page, new LambdaQueryWrapperX() + .eq(DatabusSyncFullTaskDO::getStatus, 0) + .orderByAsc(DatabusSyncFullTaskDO::getCreateTime)).getRecords(); + } + + /** + * 分页查询 + */ + default PageResult selectPage(DatabusSyncFullTaskPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DatabusSyncFullTaskDO::getTaskNo, reqVO.getTaskNo()) + .eqIfPresent(DatabusSyncFullTaskDO::getSubscriptionId, reqVO.getSubscriptionId()) + .eqIfPresent(DatabusSyncFullTaskDO::getClientCode, reqVO.getClientCode()) + .eqIfPresent(DatabusSyncFullTaskDO::getEventType, reqVO.getEventType()) + .eqIfPresent(DatabusSyncFullTaskDO::getStatus, reqVO.getStatus()) + .orderByDesc(DatabusSyncFullTaskDO::getId)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncLogMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncLogMapper.java new file mode 100644 index 00000000..2fd52caa --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncLogMapper.java @@ -0,0 +1,30 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 数据同步日志 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncLogMapper extends BaseMapperX { + + default PageResult selectPage(DatabusSyncPushLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(DatabusSyncLogDO::getSyncId, reqVO.getSyncId()) + .eqIfPresent(DatabusSyncLogDO::getSubscriptionId, reqVO.getSubscriptionId()) + .likeIfPresent(DatabusSyncLogDO::getClientCode, reqVO.getClientCode()) + .likeIfPresent(DatabusSyncLogDO::getEventType, reqVO.getEventType()) + .eqIfPresent(DatabusSyncLogDO::getStatus, reqVO.getStatus()) + .eqIfPresent(DatabusSyncLogDO::getTransportType, reqVO.getTransportType()) + .betweenIfPresent(DatabusSyncLogDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd()) + .orderByDesc(DatabusSyncLogDO::getId)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java new file mode 100644 index 00000000..4d30f98a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java @@ -0,0 +1,41 @@ +package com.zt.plat.framework.databus.server.dal.mapper; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 数据同步订阅关系 Mapper + * + * @author ZT + */ +@Mapper +public interface DatabusSyncSubscriptionMapper extends BaseMapperX { + + default DatabusSyncSubscriptionDO selectByClientIdAndEventId(Long clientId, Long eventId) { + return selectOne(new LambdaQueryWrapperX() + .eq(DatabusSyncSubscriptionDO::getClientId, clientId) + .eq(DatabusSyncSubscriptionDO::getEventId, eventId)); + } + + default PageResult selectPage(DatabusSyncSubscriptionPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(DatabusSyncSubscriptionDO::getClientId, reqVO.getClientId()) + .eqIfPresent(DatabusSyncSubscriptionDO::getEventId, reqVO.getEventId()) + .eqIfPresent(DatabusSyncSubscriptionDO::getEnabled, reqVO.getEnabled()) + .orderByDesc(DatabusSyncSubscriptionDO::getId)); + } + + /** + * 根据事件ID查询所有启用的订阅 + */ + default java.util.List selectEnabledByEventId(Long eventId) { + return selectList(new LambdaQueryWrapperX() + .eq(DatabusSyncSubscriptionDO::getEventId, eventId) + .eq(DatabusSyncSubscriptionDO::getEnabled, 1)); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DataProviderTypeEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DataProviderTypeEnum.java new file mode 100644 index 00000000..00ad24b6 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DataProviderTypeEnum.java @@ -0,0 +1,36 @@ +package com.zt.plat.framework.databus.server.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据提供者类型枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum DataProviderTypeEnum { + + ORG("ORG", "组织机构"), + USER("USER", "用户"); + + /** + * 类型编码 + */ + private final String code; + /** + * 类型名称 + */ + private final String name; + + public static DataProviderTypeEnum getByCode(String code) { + for (DataProviderTypeEnum value : values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DeadLetterStatusEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DeadLetterStatusEnum.java new file mode 100644 index 00000000..1a3e8ecf --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/DeadLetterStatusEnum.java @@ -0,0 +1,51 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 死信队列状态枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum DeadLetterStatusEnum implements ArrayValuable { + + PENDING(0, "待处理"), + REDELIVERED(1, "已重新投递"), + IGNORED(2, "已忽略"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(DeadLetterStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isPending(Integer status) { + return ObjUtil.equal(PENDING.status, status); + } + + public static boolean isRedelivered(Integer status) { + return ObjUtil.equal(REDELIVERED.status, status); + } + + public static boolean isIgnored(Integer status) { + return ObjUtil.equal(IGNORED.status, status); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/ErrorCodeConstants.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/ErrorCodeConstants.java new file mode 100644 index 00000000..893312d4 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/ErrorCodeConstants.java @@ -0,0 +1,30 @@ +package com.zt.plat.framework.databus.server.enums; + +import com.zt.plat.framework.common.exception.ErrorCode; + +/** + * Databus 同步服务错误码枚举类 + *

+ * databus 系统,使用 1-010-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 数据同步事件 1-010-001-000 ========== + ErrorCode EVENT_NOT_EXISTS = new ErrorCode(1_010_001_000, "数据同步事件不存在"); + ErrorCode EVENT_TYPE_DUPLICATE = new ErrorCode(1_010_001_001, "事件类型编码已存在"); + + // ========== 数据同步客户端 1-010-002-000 ========== + ErrorCode CLIENT_NOT_EXISTS = new ErrorCode(1_010_002_000, "数据同步客户端不存在"); + ErrorCode CLIENT_CODE_DUPLICATE = new ErrorCode(1_010_002_001, "客户端编码已存在"); + + // ========== 数据同步订阅 1-010-003-000 ========== + ErrorCode SUBSCRIPTION_NOT_EXISTS = new ErrorCode(1_010_003_000, "数据同步订阅不存在"); + ErrorCode SUBSCRIPTION_DUPLICATE = new ErrorCode(1_010_003_001, "该客户端已订阅该事件,不能重复订阅"); + + // ========== 数据同步推送日志 1-010-004-000 ========== + ErrorCode PUSH_LOG_NOT_EXISTS = new ErrorCode(1_010_004_000, "数据同步推送日志不存在"); + + // ========== 数据同步死信队列 1-010-005-000 ========== + ErrorCode DEAD_LETTER_NOT_EXISTS = new ErrorCode(1_010_005_000, "数据同步死信队列记录不存在"); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/FullTaskStatusEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/FullTaskStatusEnum.java new file mode 100644 index 00000000..a8aa2d4c --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/FullTaskStatusEnum.java @@ -0,0 +1,75 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 全量任务状态枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum FullTaskStatusEnum implements ArrayValuable { + + PENDING(0, "待执行"), + RUNNING(1, "执行中"), + COMPLETED(2, "已完成"), + FAILED(3, "失败"), + CANCELLED(4, "已取消"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(FullTaskStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isPending(Integer status) { + return ObjUtil.equal(PENDING.status, status); + } + + public static boolean isRunning(Integer status) { + return ObjUtil.equal(RUNNING.status, status); + } + + public static boolean isCompleted(Integer status) { + return ObjUtil.equal(COMPLETED.status, status); + } + + public static boolean isFailed(Integer status) { + return ObjUtil.equal(FAILED.status, status); + } + + public static boolean isCancelled(Integer status) { + return ObjUtil.equal(CANCELLED.status, status); + } + + /** + * 是否可以取消 + */ + public static boolean canCancel(Integer status) { + return isPending(status) || isRunning(status); + } + + /** + * 是否已终止(完成/失败/取消) + */ + public static boolean isTerminated(Integer status) { + return isCompleted(status) || isFailed(status) || isCancelled(status); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncModeEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncModeEnum.java new file mode 100644 index 00000000..100c7daf --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncModeEnum.java @@ -0,0 +1,46 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 同步模式枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum SyncModeEnum implements ArrayValuable { + + FULL(1, "全量同步"), + INCREMENTAL(2, "增量同步"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(SyncModeEnum::getMode).toArray(Integer[]::new); + + /** + * 模式值 + */ + private final Integer mode; + /** + * 模式名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isFull(Integer mode) { + return ObjUtil.equal(FULL.mode, mode); + } + + public static boolean isIncremental(Integer mode) { + return ObjUtil.equal(INCREMENTAL.mode, mode); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncStatusEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncStatusEnum.java new file mode 100644 index 00000000..0bacda69 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/SyncStatusEnum.java @@ -0,0 +1,56 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 同步状态枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum SyncStatusEnum implements ArrayValuable { + + PENDING(0, "待处理"), + SUCCESS(1, "成功"), + FAILED(2, "失败"), + RETRYING(3, "重试中"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(SyncStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isPending(Integer status) { + return ObjUtil.equal(PENDING.status, status); + } + + public static boolean isSuccess(Integer status) { + return ObjUtil.equal(SUCCESS.status, status); + } + + public static boolean isFailed(Integer status) { + return ObjUtil.equal(FAILED.status, status); + } + + public static boolean isRetrying(Integer status) { + return ObjUtil.equal(RETRYING.status, status); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TimelinessEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TimelinessEnum.java new file mode 100644 index 00000000..67b96c39 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TimelinessEnum.java @@ -0,0 +1,51 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 时效性枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum TimelinessEnum implements ArrayValuable { + + REALTIME(1, "实时"), + NEAR_REALTIME(2, "准实时"), + BATCH(3, "批量"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TimelinessEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isRealtime(Integer type) { + return ObjUtil.equal(REALTIME.type, type); + } + + public static boolean isNearRealtime(Integer type) { + return ObjUtil.equal(NEAR_REALTIME.type, type); + } + + public static boolean isBatch(Integer type) { + return ObjUtil.equal(BATCH.type, type); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TransportTypeEnum.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TransportTypeEnum.java new file mode 100644 index 00000000..c0fbf707 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/enums/TransportTypeEnum.java @@ -0,0 +1,46 @@ +package com.zt.plat.framework.databus.server.enums; + +import cn.hutool.core.util.ObjUtil; +import com.zt.plat.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 传输方式枚举 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum TransportTypeEnum implements ArrayValuable { + + MQ_FIRST(1, "MQ优先"), + HTTP_ONLY(2, "仅HTTP"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TransportTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型值 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isMqFirst(Integer type) { + return ObjUtil.equal(MQ_FIRST.type, type); + } + + public static boolean isHttpOnly(Integer type) { + return ObjUtil.equal(HTTP_ONLY.type, type); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/producer/DatabusMessageProducer.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/producer/DatabusMessageProducer.java new file mode 100644 index 00000000..16807566 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/producer/DatabusMessageProducer.java @@ -0,0 +1,195 @@ +package com.zt.plat.framework.databus.server.producer; + +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.databus.server.config.DatabusServerProperties; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.message.Message; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Databus 消息生产者 + *

+ * 负责将数据变更消息发送到各个客户端的 Topic + * + * @author ZT + */ +@Slf4j +@RequiredArgsConstructor +public class DatabusMessageProducer { + + private final DatabusServerProperties properties; + + private DefaultMQProducer producer; + + @PostConstruct + public void init() throws MQClientException { + if (!properties.isEnabled() || !properties.getMq().isEnabled()) { + log.info("[Databus Server] MQ 发送未启用"); + return; + } + + producer = new DefaultMQProducer(properties.getMq().getProducerGroup()); + producer.setNamesrvAddr(properties.getMq().getNameServer()); + producer.setSendMsgTimeout(properties.getMq().getSendMsgTimeout()); + producer.start(); + + log.info("[Databus Server] 消息生产者启动成功, nameServer={}, producerGroup={}", + properties.getMq().getNameServer(), properties.getMq().getProducerGroup()); + } + + @PreDestroy + public void destroy() { + if (producer != null) { + producer.shutdown(); + log.info("[Databus Server] 消息生产者已关闭"); + } + } + + /** + * 发送增量消息到所有客户端 + * + * @param message 消息 + * @param 数据类型 + */ + public void send(DatabusMessage message) { + if (producer == null) { + log.warn("[Databus Server] Producer 未初始化,无法发送消息"); + return; + } + + List clients = properties.getClients(); + if (clients == null || clients.isEmpty()) { + log.warn("[Databus Server] 未配置客户端列表,无法发送消息"); + return; + } + + String messageJson = JSONUtil.toJsonStr(message); + DatabusEventType eventType = message.getEventType(); + + for (String clientCode : clients) { + try { + String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode); + Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8)); + mqMessage.setKeys(message.getMessageId()); + + SendResult result = producer.send(mqMessage); + log.debug("[Databus Server] 消息发送成功, topic={}, messageId={}, result={}", + topic, message.getMessageId(), result.getMsgId()); + } catch (Exception e) { + log.error("[Databus Server] 消息发送失败, clientCode={}, eventType={}, messageId={}", + clientCode, eventType.name(), message.getMessageId(), e); + } + } + } + + /** + * 发送增量消息到指定客户端 + * + * @param message 消息 + * @param clientCode 客户端编码 + * @param 数据类型 + */ + public void sendTo(DatabusMessage message, String clientCode) { + if (producer == null) { + log.warn("[Databus Server] Producer 未初始化,无法发送消息"); + return; + } + + String messageJson = JSONUtil.toJsonStr(message); + DatabusEventType eventType = message.getEventType(); + String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode); + + try { + Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8)); + mqMessage.setKeys(message.getMessageId()); + + SendResult result = producer.send(mqMessage); + log.info("[Databus Server] 消息发送成功, topic={}, messageId={}, result={}", + topic, message.getMessageId(), result.getMsgId()); + } catch (Exception e) { + log.error("[Databus Server] 消息发送失败, clientCode={}, eventType={}, messageId={}", + clientCode, eventType.name(), message.getMessageId(), e); + throw new RuntimeException("消息发送失败", e); + } + } + + /** + * 发送批量消息到所有客户端 + * + * @param message 批量消息 + * @param 数据类型 + */ + public void sendBatch(DatabusBatchMessage message) { + if (producer == null) { + log.warn("[Databus Server] Producer 未初始化,无法发送消息"); + return; + } + + List clients = properties.getClients(); + if (clients == null || clients.isEmpty()) { + log.warn("[Databus Server] 未配置客户端列表,无法发送消息"); + return; + } + + String messageJson = JSONUtil.toJsonStr(message); + DatabusEventType eventType = message.getEventType(); + + for (String clientCode : clients) { + try { + String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode); + Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8)); + mqMessage.setKeys(message.getMessageId()); + + SendResult result = producer.send(mqMessage); + log.debug("[Databus Server] 批量消息发送成功, topic={}, batchNo={}/{}, result={}", + topic, message.getBatchNo(), message.getTotalBatch(), result.getMsgId()); + } catch (Exception e) { + log.error("[Databus Server] 批量消息发送失败, clientCode={}, eventType={}, batchNo={}", + clientCode, eventType.name(), message.getBatchNo(), e); + } + } + } + + /** + * 发送批量消息到指定客户端 + * + * @param message 批量消息 + * @param clientCode 客户端编码 + * @param 数据类型 + */ + public void sendBatchTo(DatabusBatchMessage message, String clientCode) { + if (producer == null) { + log.warn("[Databus Server] Producer 未初始化,无法发送消息"); + return; + } + + String messageJson = JSONUtil.toJsonStr(message); + DatabusEventType eventType = message.getEventType(); + String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode); + + try { + Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8)); + mqMessage.setKeys(message.getMessageId()); + + SendResult result = producer.send(mqMessage); + log.info("[Databus Server] 批量消息发送成功, topic={}, batchNo={}/{}, result={}", + topic, message.getBatchNo(), message.getTotalBatch(), result.getMsgId()); + } catch (Exception e) { + log.error("[Databus Server] 批量消息发送失败, clientCode={}, eventType={}, batchNo={}", + clientCode, eventType.name(), message.getBatchNo(), e); + throw new RuntimeException("批量消息发送失败", e); + } + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java new file mode 100644 index 00000000..7e634a61 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java @@ -0,0 +1,70 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO; + +import java.util.List; + +/** + * 数据同步客户端 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncClientService { + + /** + * 创建数据同步客户端 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createClient(DatabusSyncClientSaveReqVO createReqVO); + + /** + * 更新数据同步客户端 + * + * @param updateReqVO 更新信息 + */ + void updateClient(DatabusSyncClientSaveReqVO updateReqVO); + + /** + * 删除数据同步客户端 + * + * @param id 编号 + */ + void deleteClient(Long id); + + /** + * 获得数据同步客户端 + * + * @param id 编号 + * @return 数据同步客户端 + */ + DatabusSyncClientDO getClient(Long id); + + /** + * 获得数据同步客户端分页 + * + * @param pageReqVO 分页查询 + * @return 数据同步客户端分页 + */ + PageResult getClientPage(DatabusSyncClientPageReqVO pageReqVO); + + /** + * 获得数据同步客户端列表 + * + * @return 数据同步客户端列表 + */ + List getClientList(); + + /** + * 更新客户端启用状态 + * + * @param id 编号 + * @param enabled 启用状态 + */ + void updateClientStatus(Long id, Integer enabled); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncDeadLetterService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncDeadLetterService.java new file mode 100644 index 00000000..e922a8ad --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncDeadLetterService.java @@ -0,0 +1,54 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; + +import java.util.List; + +/** + * 数据同步死信队列 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncDeadLetterService { + + /** + * 获得数据同步死信队列 + * + * @param id 编号 + * @return 数据同步死信队列 + */ + DatabusSyncDeadLetterDO getDeadLetter(Long id); + + /** + * 获得数据同步死信队列分页 + * + * @param pageReqVO 分页查询 + * @return 数据同步死信队列分页 + */ + PageResult getDeadLetterPage(DatabusSyncDeadLetterPageReqVO pageReqVO); + + /** + * 重新投递死信消息 + * + * @param id 死信ID + */ + void reprocessDeadLetter(Long id); + + /** + * 批量重新投递死信消息 + * + * @param ids 死信ID列表 + */ + void batchReprocessDeadLetter(List ids); + + /** + * 标记为已处理 + * + * @param id 死信ID + * @param remark 处理备注 + */ + void markHandled(Long id, String remark); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java new file mode 100644 index 00000000..fe7c2b3f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java @@ -0,0 +1,70 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; + +import java.util.List; + +/** + * 数据同步事件 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncEventService { + + /** + * 创建数据同步事件 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createEvent(DatabusSyncEventSaveReqVO createReqVO); + + /** + * 更新数据同步事件 + * + * @param updateReqVO 更新信息 + */ + void updateEvent(DatabusSyncEventSaveReqVO updateReqVO); + + /** + * 删除数据同步事件 + * + * @param id 编号 + */ + void deleteEvent(Long id); + + /** + * 获得数据同步事件 + * + * @param id 编号 + * @return 数据同步事件 + */ + DatabusSyncEventDO getEvent(Long id); + + /** + * 获得数据同步事件分页 + * + * @param pageReqVO 分页查询 + * @return 数据同步事件分页 + */ + PageResult getEventPage(DatabusSyncEventPageReqVO pageReqVO); + + /** + * 获得数据同步事件列表 + * + * @return 数据同步事件列表 + */ + List getEventList(); + + /** + * 更新事件启用状态 + * + * @param id 编号 + * @param enabled 启用状态 + */ + void updateEventStatus(Long id, Integer enabled); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncLogService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncLogService.java new file mode 100644 index 00000000..26cc97b5 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncLogService.java @@ -0,0 +1,37 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; + +/** + * 数据同步推送日志 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncLogService { + + /** + * 获得数据同步推送日志 + * + * @param id 编号 + * @return 数据同步推送日志 + */ + DatabusSyncLogDO getPushLog(Long id); + + /** + * 获得数据同步推送日志分页 + * + * @param pageReqVO 分页查询 + * @return 数据同步推送日志分页 + */ + PageResult getPushLogPage(DatabusSyncPushLogPageReqVO pageReqVO); + + /** + * 重试推送 + * + * @param id 日志ID + */ + void retryPush(Long id); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java new file mode 100644 index 00000000..88239aaf --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java @@ -0,0 +1,75 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; + +/** + * 数据同步订阅 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncSubscriptionService { + + /** + * 创建数据同步订阅 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSubscription(DatabusSyncSubscriptionSaveReqVO createReqVO); + + /** + * 更新数据同步订阅 + * + * @param updateReqVO 更新信息 + */ + void updateSubscription(DatabusSyncSubscriptionSaveReqVO updateReqVO); + + /** + * 删除数据同步订阅 + * + * @param id 编号 + */ + void deleteSubscription(Long id); + + /** + * 获得数据同步订阅 + * + * @param id 编号 + * @return 数据同步订阅 + */ + DatabusSyncSubscriptionDO getSubscription(Long id); + + /** + * 获得数据同步订阅分页 + * + * @param pageReqVO 分页查询 + * @return 数据同步订阅分页 + */ + PageResult getSubscriptionPage(DatabusSyncSubscriptionPageReqVO pageReqVO); + + /** + * 更新订阅启用状态 + * + * @param id 编号 + * @param enabled 启用状态 + */ + void updateSubscriptionStatus(Long id, Integer enabled); + + /** + * 重置订阅断点 + * + * @param id 编号 + */ + void resetCheckpoint(Long id); + + /** + * 手动触发同步 + * + * @param id 编号 + */ + void triggerSync(Long id); + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java new file mode 100644 index 00000000..d92f8800 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java @@ -0,0 +1,328 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.databus.server.config.DatabusSyncServerProperties; +import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage; +import com.zt.plat.framework.databus.server.core.provider.DataProvider; +import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.core.sync.DatabusFullSyncService; +import com.zt.plat.framework.databus.server.dal.dataobject.*; +import com.zt.plat.framework.databus.server.dal.mapper.*; +import com.zt.plat.framework.databus.server.enums.*; +import com.zt.plat.module.databus.enums.DatabusEventType; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Databus Full Sync Service Implementation + * + * @author ZT + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DatabusFullSyncServiceImpl implements DatabusFullSyncService { + + private final DatabusSyncFullTaskMapper fullTaskMapper; + private final DatabusSyncSubscriptionMapper subscriptionMapper; + private final DatabusSyncClientMapper clientMapper; + private final DatabusSyncEventMapper eventMapper; + private final DatabusSyncLogMapper syncLogMapper; + private final MessagePusher messagePusher; + private final DatabusSyncServerProperties properties; + private final DataProviderRegistry dataProviderRegistry; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createFullSyncTask(Long subscriptionId, String remark) { + DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(subscriptionId); + if (subscription == null) { + throw new RuntimeException("Subscription not found"); + } + + DatabusSyncFullTaskDO runningTask = fullTaskMapper.selectRunningBySubscriptionId(subscriptionId); + if (runningTask != null) { + throw new RuntimeException("Full sync task already running: " + runningTask.getTaskNo()); + } + + DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId()); + DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId()); + + if (client == null || event == null) { + throw new RuntimeException("Client or event config not found"); + } + + if (event.getSupportFullSync() != 1) { + throw new RuntimeException("Event does not support full sync"); + } + + DatabusSyncFullTaskDO task = DatabusSyncFullTaskDO.builder() + .taskNo(IdUtil.fastSimpleUUID()) + .subscriptionId(subscriptionId) + .clientCode(client.getClientCode()) + .eventType(event.getEventType()) + .status(FullTaskStatusEnum.PENDING.getStatus()) + .totalCount(0L) + .processedCount(0L) + .successCount(0L) + .failCount(0L) + .totalBatch(0) + .currentBatch(0) + .batchSize(subscription.getBatchSize()) + .tenantId(client.getTenantId()) + .remark(remark) + .build(); + + fullTaskMapper.insert(task); + log.info("[Databus] Full sync task created, taskId={}, taskNo={}", task.getId(), task.getTaskNo()); + + return task.getId(); + } + + @Override + @Async + public void executeFullSyncTask(Long taskId) { + DatabusSyncFullTaskDO task = fullTaskMapper.selectById(taskId); + if (task == null) { + log.error("[Databus] Task not found, taskId={}", taskId); + return; + } + // 允许 PENDING、RUNNING、FAILED 状态执行(支持重试) + if (FullTaskStatusEnum.isCompleted(task.getStatus()) || + FullTaskStatusEnum.isCancelled(task.getStatus())) { + log.warn("[Databus] Task status not allowed, taskId={}, status={}", taskId, task.getStatus()); + return; + } + + task.setStatus(FullTaskStatusEnum.RUNNING.getStatus()); + task.setStartTime(LocalDateTime.now()); + fullTaskMapper.updateById(task); + + try { + DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(task.getSubscriptionId()); + DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId()); + DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId()); + + String providerType = event.getDataProviderMethod(); + if (providerType == null) { + throw new RuntimeException("Event data provider type not configured"); + } + + DataProvider dataProvider = dataProviderRegistry.getProvider(providerType); + if (dataProvider == null) { + throw new RuntimeException("Data provider not found: " + providerType); + } + + executeGenericFullSync(task, subscription, client, event, dataProvider); + + task.setStatus(FullTaskStatusEnum.COMPLETED.getStatus()); + task.setEndTime(LocalDateTime.now()); + fullTaskMapper.updateById(task); + + log.info("[Databus] Full sync task completed, taskId={}, totalCount={}, successCount={}", + taskId, task.getTotalCount(), task.getSuccessCount()); + + } catch (Exception e) { + log.error("[Databus] Full sync task failed, taskId={}", taskId, e); + + task.setStatus(FullTaskStatusEnum.FAILED.getStatus()); + task.setEndTime(LocalDateTime.now()); + task.setLastErrorMessage(e.getMessage()); + fullTaskMapper.updateById(task); + } + } + + private void executeGenericFullSync(DatabusSyncFullTaskDO task, + DatabusSyncSubscriptionDO subscription, + DatabusSyncClientDO client, + DatabusSyncEventDO event, + DataProvider dataProvider) { + long totalCount = dataProvider.count(task.getTenantId()); + int totalBatch = (int) Math.ceil((double) totalCount / task.getBatchSize()); + + task.setTotalCount(totalCount); + task.setTotalBatch(totalBatch); + fullTaskMapper.updateById(task); + + LocalDateTime cursorTime = task.getCursorTime(); + Long cursorId = task.getCursorId(); + int batchNo = task.getCurrentBatch(); + + while (true) { + DatabusSyncFullTaskDO currentTask = fullTaskMapper.selectById(task.getId()); + if (FullTaskStatusEnum.isCancelled(currentTask.getStatus())) { + log.info("[Databus] Task cancelled, taskId={}", task.getId()); + return; + } + + batchNo++; + DataProvider.CursorPageData page = dataProvider.getPageByCursor( + cursorTime, cursorId, task.getBatchSize(), task.getTenantId()); + + if (CollUtil.isEmpty(page.getList())) { + break; + } + + boolean isLastBatch = !page.isHasMore(); + BatchSyncMessage message = buildBatchMessage( + task, event, page.getList(), dataProvider, batchNo, totalBatch, isLastBatch, totalCount); + + boolean success = pushBatchMessage(client, event.getEventType(), message); + + recordSyncLog(task, subscription, client, event, message, success, batchNo); + + task.setCurrentBatch(batchNo); + task.setCursorTime(page.getNextCursorTime()); + task.setCursorId(page.getNextCursorId()); + task.setProcessedCount(task.getProcessedCount() + page.getCount()); + if (success) { + task.setSuccessCount(task.getSuccessCount() + page.getCount()); + } else { + task.setFailCount(task.getFailCount() + page.getCount()); + } + fullTaskMapper.updateById(task); + + if (isLastBatch) { + break; + } + cursorTime = page.getNextCursorTime(); + cursorId = page.getNextCursorId(); + } + } + + private BatchSyncMessage buildBatchMessage(DatabusSyncFullTaskDO task, + DatabusSyncEventDO event, + List dataList, + DataProvider dataProvider, + int batchNo, + int totalBatch, + boolean isLastBatch, + long totalCount) { + List items = new ArrayList<>(); + for (T data : dataList) { + Long uid = dataProvider.extractUid(data); + items.add(BatchSyncMessage.SyncDataItem.builder() + .action("FULL") + .uid(uid) + .data(JSONUtil.toJsonStr(data)) + .build()); + } + + return BatchSyncMessage.builder() + .messageId(IdUtil.fastSimpleUUID()) + .requestId(task.getTaskNo()) + .eventType(event.getEventType()) + .syncMode(SyncModeEnum.FULL.getMode()) + .dataVersion(event.getDataVersion()) + .timestamp(System.currentTimeMillis()) + .tenantId(task.getTenantId()) + .dataList(items) + .count(items.size()) + .fullTaskId(task.getId()) + .batchNo(batchNo) + .totalBatch(totalBatch) + .isLastBatch(isLastBatch) + .totalCount(totalCount) + .build(); + } + + private boolean pushBatchMessage(DatabusSyncClientDO client, String eventType, + BatchSyncMessage message) { + try { + if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) { + // 使用 getByTopicSuffix 支持数据库存储的 topic 格式(如 system-post-full) + DatabusEventType databusEventType = DatabusEventType.getByTopicSuffix(eventType); + if (databusEventType == null) { + log.error("[Databus] Unknown event type: {}", eventType); + return false; + } + String topicBase = client.getMqTopicBase(); + if (topicBase == null || topicBase.isEmpty()) { + topicBase = "databus-sync"; + } + String topic = databusEventType.getTopic(topicBase, client.getClientCode()); + log.info("[Databus] Pushing batch message, topic={}, eventType={}, clientCode={}, messageId={}", + topic, eventType, client.getClientCode(), message.getMessageId()); + messagePusher.pushBatchByMQ(topic, message); + } else if (client.getHttpEnabled() == 1) { + return messagePusher.pushBatchByHttp(client.getHttpEndpoint(), message); + } else { + throw new RuntimeException("No available push method"); + } + return true; + } catch (Exception e) { + log.error("[Databus] Push batch message failed, messageId={}", message.getMessageId(), e); + return false; + } + } + + private void recordSyncLog(DatabusSyncFullTaskDO task, + DatabusSyncSubscriptionDO subscription, + DatabusSyncClientDO client, + DatabusSyncEventDO event, + BatchSyncMessage message, + boolean success, + int batchNo) { + DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder() + .syncId(message.getMessageId()) + .eventRecordId(0L) + .subscriptionId(subscription.getId()) + .clientCode(client.getClientCode()) + .eventType(event.getEventType()) + .syncMode(SyncModeEnum.FULL.getMode()) + .transportType(TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1 + ? TransportTypeEnum.MQ_FIRST.getType() + : TransportTypeEnum.HTTP_ONLY.getType()) + .status(success ? SyncStatusEnum.SUCCESS.getStatus() : SyncStatusEnum.FAILED.getStatus()) + .retryCount(0) + .startTime(LocalDateTime.now()) + .endTime(LocalDateTime.now()) + .tenantId(task.getTenantId()) + .dataCount(message.getCount()) + .batchNo(batchNo) + .fullTaskId(task.getId()) + .build(); + + syncLogMapper.insert(syncLog); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelFullSyncTask(Long taskId) { + DatabusSyncFullTaskDO task = fullTaskMapper.selectById(taskId); + if (task == null) { + throw new RuntimeException("Task not found"); + } + + if (!FullTaskStatusEnum.canCancel(task.getStatus())) { + throw new RuntimeException("Task status not allowed to cancel"); + } + + task.setStatus(FullTaskStatusEnum.CANCELLED.getStatus()); + task.setEndTime(LocalDateTime.now()); + fullTaskMapper.updateById(task); + + log.info("[Databus] Full sync task cancelled, taskId={}", taskId); + } + + @Override + public DatabusSyncFullTaskDO getTaskById(Long taskId) { + return fullTaskMapper.selectById(taskId); + } + + @Override + public DatabusSyncFullTaskDO getTaskByTaskNo(String taskNo) { + return fullTaskMapper.selectByTaskNo(taskNo); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java new file mode 100644 index 00000000..496463c9 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java @@ -0,0 +1,115 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncClientConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncClientMapper; +import com.zt.plat.framework.databus.server.service.DatabusSyncClientService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*; + +/** + * 数据同步客户端 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +@Validated +public class DatabusSyncClientServiceImpl implements DatabusSyncClientService { + + @Resource + private DatabusSyncClientMapper clientMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createClient(DatabusSyncClientSaveReqVO createReqVO) { + // 校验客户端编码唯一性 + validateClientCodeUnique(null, createReqVO.getClientCode()); + + // 插入 + DatabusSyncClientDO client = DatabusSyncClientConvert.INSTANCE.convert(createReqVO); + clientMapper.insert(client); + return client.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateClient(DatabusSyncClientSaveReqVO updateReqVO) { + // 校验存在 + validateClientExists(updateReqVO.getId()); + // 校验客户端编码唯一性 + validateClientCodeUnique(updateReqVO.getId(), updateReqVO.getClientCode()); + + // 更新 + DatabusSyncClientDO updateObj = DatabusSyncClientConvert.INSTANCE.convert(updateReqVO); + clientMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteClient(Long id) { + // 校验存在 + validateClientExists(id); + // 删除 + clientMapper.deleteById(id); + } + + private void validateClientExists(Long id) { + if (clientMapper.selectById(id) == null) { + throw exception(CLIENT_NOT_EXISTS); + } + } + + private void validateClientCodeUnique(Long id, String clientCode) { + DatabusSyncClientDO client = clientMapper.selectByClientCode(clientCode); + if (client == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的客户端 + if (id == null) { + throw exception(CLIENT_CODE_DUPLICATE); + } + if (!client.getId().equals(id)) { + throw exception(CLIENT_CODE_DUPLICATE); + } + } + + @Override + public DatabusSyncClientDO getClient(Long id) { + return clientMapper.selectById(id); + } + + @Override + public PageResult getClientPage(DatabusSyncClientPageReqVO pageReqVO) { + return clientMapper.selectPage(pageReqVO); + } + + @Override + public List getClientList() { + return clientMapper.selectList(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateClientStatus(Long id, Integer enabled) { + // 校验存在 + validateClientExists(id); + // 更新状态 + DatabusSyncClientDO updateObj = new DatabusSyncClientDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + clientMapper.updateById(updateObj); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java new file mode 100644 index 00000000..ee16880b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java @@ -0,0 +1,99 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncDeadLetterMapper; +import com.zt.plat.framework.databus.server.enums.DeadLetterStatusEnum; +import com.zt.plat.framework.databus.server.service.DatabusSyncDeadLetterService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.DEAD_LETTER_NOT_EXISTS; + +/** + * 数据同步死信队列 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +@Validated +public class DatabusSyncDeadLetterServiceImpl implements DatabusSyncDeadLetterService { + + @Resource + private DatabusSyncDeadLetterMapper deadLetterMapper; + + @Override + public DatabusSyncDeadLetterDO getDeadLetter(Long id) { + return deadLetterMapper.selectById(id); + } + + @Override + public PageResult getDeadLetterPage(DatabusSyncDeadLetterPageReqVO pageReqVO) { + return deadLetterMapper.selectPage(pageReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void reprocessDeadLetter(Long id) { + // 校验存在 + DatabusSyncDeadLetterDO deadLetter = deadLetterMapper.selectById(id); + if (deadLetter == null) { + throw exception(DEAD_LETTER_NOT_EXISTS); + } + + // TODO: 实现重新投递逻辑,将消息重新发送到 MQ 或 HTTP + log.info("[reprocessDeadLetter] 重新投递死信消息,ID: {}, 同步ID: {}", id, deadLetter.getSyncId()); + + // 更新状态为已重新投递 + DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO(); + updateObj.setId(id); + updateObj.setStatus(DeadLetterStatusEnum.REDELIVERED.getStatus()); + updateObj.setHandled(1); + updateObj.setHandleTime(LocalDateTime.now()); + deadLetterMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchReprocessDeadLetter(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + for (Long id : ids) { + try { + reprocessDeadLetter(id); + } catch (Exception e) { + log.error("[batchReprocessDeadLetter] 重新投递失败,ID: {}", id, e); + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void markHandled(Long id, String remark) { + // 校验存在 + DatabusSyncDeadLetterDO deadLetter = deadLetterMapper.selectById(id); + if (deadLetter == null) { + throw exception(DEAD_LETTER_NOT_EXISTS); + } + + // 标记为已处理(忽略) + DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO(); + updateObj.setId(id); + updateObj.setStatus(DeadLetterStatusEnum.IGNORED.getStatus()); + updateObj.setHandled(1); + updateObj.setHandleTime(LocalDateTime.now()); + updateObj.setHandleRemark(remark); + deadLetterMapper.updateById(updateObj); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java new file mode 100644 index 00000000..355c5f77 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java @@ -0,0 +1,115 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncEventConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventMapper; +import com.zt.plat.framework.databus.server.service.DatabusSyncEventService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*; + +/** + * 数据同步事件 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +@Validated +public class DatabusSyncEventServiceImpl implements DatabusSyncEventService { + + @Resource + private DatabusSyncEventMapper eventMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createEvent(DatabusSyncEventSaveReqVO createReqVO) { + // 校验事件类型唯一性 + validateEventTypeUnique(null, createReqVO.getEventType()); + + // 插入 + DatabusSyncEventDO event = DatabusSyncEventConvert.INSTANCE.convert(createReqVO); + eventMapper.insert(event); + return event.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateEvent(DatabusSyncEventSaveReqVO updateReqVO) { + // 校验存在 + validateEventExists(updateReqVO.getId()); + // 校验事件类型唯一性 + validateEventTypeUnique(updateReqVO.getId(), updateReqVO.getEventType()); + + // 更新 + DatabusSyncEventDO updateObj = DatabusSyncEventConvert.INSTANCE.convert(updateReqVO); + eventMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteEvent(Long id) { + // 校验存在 + validateEventExists(id); + // 删除 + eventMapper.deleteById(id); + } + + private void validateEventExists(Long id) { + if (eventMapper.selectById(id) == null) { + throw exception(EVENT_NOT_EXISTS); + } + } + + private void validateEventTypeUnique(Long id, String eventType) { + DatabusSyncEventDO event = eventMapper.selectByEventType(eventType); + if (event == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的事件 + if (id == null) { + throw exception(EVENT_TYPE_DUPLICATE); + } + if (!event.getId().equals(id)) { + throw exception(EVENT_TYPE_DUPLICATE); + } + } + + @Override + public DatabusSyncEventDO getEvent(Long id) { + return eventMapper.selectById(id); + } + + @Override + public PageResult getEventPage(DatabusSyncEventPageReqVO pageReqVO) { + return eventMapper.selectPage(pageReqVO); + } + + @Override + public List getEventList() { + return eventMapper.selectList(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateEventStatus(Long id, Integer enabled) { + // 校验存在 + validateEventExists(id); + // 更新状态 + DatabusSyncEventDO updateObj = new DatabusSyncEventDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + eventMapper.updateById(updateObj); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java new file mode 100644 index 00000000..64910c9a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java @@ -0,0 +1,50 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncLogMapper; +import com.zt.plat.framework.databus.server.service.DatabusSyncLogService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.PUSH_LOG_NOT_EXISTS; + +/** + * 数据同步推送日志 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +@Validated +public class DatabusSyncLogServiceImpl implements DatabusSyncLogService { + + @Resource + private DatabusSyncLogMapper pushLogMapper; + + @Override + public DatabusSyncLogDO getPushLog(Long id) { + return pushLogMapper.selectById(id); + } + + @Override + public PageResult getPushLogPage(DatabusSyncPushLogPageReqVO pageReqVO) { + return pushLogMapper.selectPage(pageReqVO); + } + + @Override + public void retryPush(Long id) { + // 校验存在 + DatabusSyncLogDO pushLog = pushLogMapper.selectById(id); + if (pushLog == null) { + throw exception(PUSH_LOG_NOT_EXISTS); + } + // TODO: 实现重试逻辑,将日志重新入队或触发推送 + log.info("[retryPush] 重试推送,日志ID: {}, 同步ID: {}", id, pushLog.getSyncId()); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java new file mode 100644 index 00000000..20952417 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java @@ -0,0 +1,129 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; +import com.zt.plat.framework.databus.server.convert.DatabusSyncSubscriptionConvert; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncSubscriptionMapper; +import com.zt.plat.framework.databus.server.service.DatabusSyncSubscriptionService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*; + +/** + * 数据同步订阅 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +@Validated +public class DatabusSyncSubscriptionServiceImpl implements DatabusSyncSubscriptionService { + + @Resource + private DatabusSyncSubscriptionMapper subscriptionMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSubscription(DatabusSyncSubscriptionSaveReqVO createReqVO) { + // 校验订阅唯一性(同一个客户端不能重复订阅同一个事件) + validateSubscriptionUnique(null, createReqVO.getClientId(), createReqVO.getEventId()); + + // 插入 + DatabusSyncSubscriptionDO subscription = DatabusSyncSubscriptionConvert.INSTANCE.convert(createReqVO); + subscriptionMapper.insert(subscription); + return subscription.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSubscription(DatabusSyncSubscriptionSaveReqVO updateReqVO) { + // 校验存在 + validateSubscriptionExists(updateReqVO.getId()); + // 校验订阅唯一性 + validateSubscriptionUnique(updateReqVO.getId(), updateReqVO.getClientId(), updateReqVO.getEventId()); + + // 更新 + DatabusSyncSubscriptionDO updateObj = DatabusSyncSubscriptionConvert.INSTANCE.convert(updateReqVO); + subscriptionMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSubscription(Long id) { + // 校验存在 + validateSubscriptionExists(id); + // 删除 + subscriptionMapper.deleteById(id); + } + + private void validateSubscriptionExists(Long id) { + if (subscriptionMapper.selectById(id) == null) { + throw exception(SUBSCRIPTION_NOT_EXISTS); + } + } + + private void validateSubscriptionUnique(Long id, Long clientId, Long eventId) { + DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectByClientIdAndEventId(clientId, eventId); + if (subscription == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的订阅 + if (id == null) { + throw exception(SUBSCRIPTION_DUPLICATE); + } + if (!subscription.getId().equals(id)) { + throw exception(SUBSCRIPTION_DUPLICATE); + } + } + + @Override + public DatabusSyncSubscriptionDO getSubscription(Long id) { + return subscriptionMapper.selectById(id); + } + + @Override + public PageResult getSubscriptionPage(DatabusSyncSubscriptionPageReqVO pageReqVO) { + return subscriptionMapper.selectPage(pageReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSubscriptionStatus(Long id, Integer enabled) { + // 校验存在 + validateSubscriptionExists(id); + // 更新状态 + DatabusSyncSubscriptionDO updateObj = new DatabusSyncSubscriptionDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + subscriptionMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void resetCheckpoint(Long id) { + // 校验存在 + validateSubscriptionExists(id); + // 重置断点 + DatabusSyncSubscriptionDO updateObj = new DatabusSyncSubscriptionDO(); + updateObj.setId(id); + updateObj.setLastSyncEventId(null); + updateObj.setLastSyncTime(null); + subscriptionMapper.updateById(updateObj); + } + + @Override + public void triggerSync(Long id) { + // 校验存在 + validateSubscriptionExists(id); + // TODO: 发送消息到 MQ 或调用异步任务触发同步 + log.info("[triggerSync] 手动触发同步,订阅ID: {}", id); + } + +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring.factories b/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..0a736d5e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.zt.plat.framework.databus.server.config.DatabusSyncServerAutoConfiguration diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..46075a66 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.zt.plat.framework.databus.server.config.DatabusServerAutoConfiguration +com.zt.plat.framework.databus.server.config.DatabusSyncServerAutoConfiguration diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusBatchMessage.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusBatchMessage.java new file mode 100644 index 00000000..3047ac10 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusBatchMessage.java @@ -0,0 +1,106 @@ +package com.zt.plat.module.databus.api.message; + +import com.zt.plat.module.databus.enums.DatabusEventType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * Databus 全量同步批量消息 + *

+ * 用于全量同步场景,支持分批传输 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusBatchMessage implements Serializable { + + /** + * 消息ID(用于幂等) + */ + private String messageId; + + /** + * 全量同步任务ID + */ + private String taskId; + + /** + * 事件类型 + */ + private DatabusEventType eventType; + + /** + * 当前批次号(从1开始) + */ + private Integer batchNo; + + /** + * 总批次数 + */ + private Integer totalBatch; + + /** + * 当前批次数据条数 + */ + private Integer count; + + /** + * 总数据条数 + */ + private Integer totalCount; + + /** + * 是否最后一批 + */ + private Boolean isLastBatch; + + /** + * 数据列�� + */ + private List dataList; + + /** + * 消息产生时间 + */ + private LocalDateTime timestamp; + + /** + * 来源系统 + */ + private String source; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建批量消息 + */ + public static DatabusBatchMessage of(DatabusEventType eventType, String taskId, + int batchNo, int totalBatch, + List dataList, int totalCount) { + DatabusBatchMessage msg = new DatabusBatchMessage<>(); + msg.setMessageId(java.util.UUID.randomUUID().toString()); + msg.setTaskId(taskId); + msg.setEventType(eventType); + msg.setBatchNo(batchNo); + msg.setTotalBatch(totalBatch); + msg.setCount(dataList != null ? dataList.size() : 0); + msg.setTotalCount(totalCount); + msg.setIsLastBatch(batchNo >= totalBatch); + msg.setDataList(dataList); + msg.setTimestamp(LocalDateTime.now()); + return msg; + } + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusMessage.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusMessage.java new file mode 100644 index 00000000..ccf5e4a0 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/message/DatabusMessage.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.databus.api.message; + +import com.zt.plat.module.databus.enums.DatabusEventType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Databus 增量同步消息 + *

+ * 业务推送、服务端消费、服务端转发、客户端消费统一使用此消息体 + * + * @author ZT + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DatabusMessage implements Serializable { + + /** + * 消息ID(用于幂等) + */ + private String messageId; + + /** + * 事件类型 + */ + private DatabusEventType eventType; + + /** + * 业务数据ID + */ + private Long dataId; + + /** + * 业务数据(强类型) + */ + private T data; + + /** + * 消息产生时间 + */ + private LocalDateTime timestamp; + + /** + * 来源系统 + */ + private String source; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建简单消息 + */ + public static DatabusMessage of(DatabusEventType eventType, Long dataId, T data) { + DatabusMessage msg = new DatabusMessage<>(); + msg.setMessageId(java.util.UUID.randomUUID().toString()); + msg.setEventType(eventType); + msg.setDataId(dataId); + msg.setData(data); + msg.setTimestamp(LocalDateTime.now()); + return msg; + } + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java new file mode 100644 index 00000000..da726cd3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java @@ -0,0 +1,306 @@ +package com.zt.plat.module.databus.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Databus 事件类型枚举 + *

+ * 三级结构: 模块_数据类型_操作 + *

+ * Topic 命名规则: + * - 业务推送到服务端: {topicBase}-{module}-{entity}-{action} + * - 服务端转发到客户端: {topicBase}-{module}-{entity}-{action}-{clientCode} + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum DatabusEventType { + + // ==================== SYSTEM 系统模块 ==================== + + /** + * 用户-创建 + */ + SYSTEM_USER_CREATE("system", "user", "create", "用户创建"), + + /** + * 用户-更新 + */ + SYSTEM_USER_UPDATE("system", "user", "update", "用户更新"), + + /** + * 用户-删除 + */ + SYSTEM_USER_DELETE("system", "user", "delete", "用户删除"), + + /** + * 用户-全量同步 + */ + SYSTEM_USER_FULL("system", "user", "full", "用户全量同步"), + + /** + * 部门-创建 + */ + SYSTEM_DEPT_CREATE("system", "dept", "create", "部门创建"), + + /** + * 部门-更新 + */ + SYSTEM_DEPT_UPDATE("system", "dept", "update", "部门更新"), + + /** + * 部门-删除 + */ + SYSTEM_DEPT_DELETE("system", "dept", "delete", "部门删除"), + + /** + * 部门-全量同步 + */ + SYSTEM_DEPT_FULL("system", "dept", "full", "部门全量同步"), + + /** + * 组织机构-创建(兼容老代码,保留但不推荐使用) + */ + @Deprecated + SYSTEM_ORG_CREATE("system", "org", "create", "组织机构创建"), + + /** + * 组织机构-更新(兼容老代码,保留但不推荐使用) + */ + @Deprecated + SYSTEM_ORG_UPDATE("system", "org", "update", "组织机构更新"), + + /** + * 组织机构-删除(兼容老代码,保留但不推荐使用) + */ + @Deprecated + SYSTEM_ORG_DELETE("system", "org", "delete", "组织机构删除"), + + /** + * 组织机构-全量同步(兼容老代码,保留但不推荐使用) + */ + @Deprecated + SYSTEM_ORG_FULL("system", "org", "full", "组织机构全量同步"), + + /** + * 岗位-创建 + */ + SYSTEM_POST_CREATE("system", "post", "create", "岗位创建"), + + /** + * 岗位-更新 + */ + SYSTEM_POST_UPDATE("system", "post", "update", "岗位更新"), + + /** + * 岗位-删除 + */ + SYSTEM_POST_DELETE("system", "post", "delete", "岗位删除"), + + /** + * 岗位-全量同步 + */ + SYSTEM_POST_FULL("system", "post", "full", "岗位全量同步"), + + /** + * 角色-创建 + */ + SYSTEM_ROLE_CREATE("system", "role", "create", "角色创建"), + + /** + * 角色-更新 + */ + SYSTEM_ROLE_UPDATE("system", "role", "update", "角色更新"), + + /** + * 角色-删除 + */ + SYSTEM_ROLE_DELETE("system", "role", "delete", "角色删除"), + + /** + * 角色-全量同步 + */ + SYSTEM_ROLE_FULL("system", "role", "full", "角色全量同步"), + + /** + * 字典-创建 + */ + SYSTEM_DICT_CREATE("system", "dict", "create", "字典创建"), + + /** + * 字典-更新 + */ + SYSTEM_DICT_UPDATE("system", "dict", "update", "字典更新"), + + /** + * 字典-删除 + */ + SYSTEM_DICT_DELETE("system", "dict", "delete", "字典删除"), + + /** + * 字典-全量同步 + */ + SYSTEM_DICT_FULL("system", "dict", "full", "字典全量同步"), + + // ==================== BASE 基础模块 ==================== + + /** + * 物料-创建 + */ + BASE_MATERIAL_CREATE("base", "material", "create", "物料创建"), + + /** + * 物料-更新 + */ + BASE_MATERIAL_UPDATE("base", "material", "update", "物料更新"), + + /** + * 物料-删除 + */ + BASE_MATERIAL_DELETE("base", "material", "delete", "物料删除"), + + /** + * 物料-全量同步 + */ + BASE_MATERIAL_FULL("base", "material", "full", "物料全量同步"), + + /** + * 供应��-创建 + */ + BASE_SUPPLIER_CREATE("base", "supplier", "create", "供应商创建"), + + /** + * 供应商-更新 + */ + BASE_SUPPLIER_UPDATE("base", "supplier", "update", "供应商更新"), + + /** + * 供应商-删除 + */ + BASE_SUPPLIER_DELETE("base", "supplier", "delete", "供应商删除"), + + /** + * 供应商-全量同步 + */ + BASE_SUPPLIER_FULL("base", "supplier", "full", "供应商全量同步"), + + /** + * 客户-创建 + */ + BASE_CUSTOMER_CREATE("base", "customer", "create", "客户创建"), + + /** + * 客户-更新 + */ + BASE_CUSTOMER_UPDATE("base", "customer", "update", "客户更新"), + + /** + * 客户-删除 + */ + BASE_CUSTOMER_DELETE("base", "customer", "delete", "客户删除"), + + /** + * 客户-全量同步 + */ + BASE_CUSTOMER_FULL("base", "customer", "full", "客户全量同步"), + + ; + + /** + * 模块编码 + */ + private final String module; + + /** + * 实体编码 + */ + private final String entity; + + /** + * 操作编码 + */ + private final String action; + + /** + * 事件名称 + */ + private final String name; + + /** + * 获取Topic后缀(不含topicBase和clientCode) + * 格式: {module}-{entity}-{action} + */ + public String getTopicSuffix() { + return String.format("%s-%s-%s", module, entity, action); + } + + /** + * 获取完整Topic名称(服务端转发用) + * 格式: {topicBase}-{module}-{entity}-{action}-{clientCode} + */ + public String getTopic(String topicBase, String clientCode) { + return String.format("%s-%s-%s-%s-%s", topicBase, module, entity, action, clientCode); + } + + /** + * 获取完整Topic名称(业务推送用,不带clientCode) + * 格式: {topicBase}-{module}-{entity}-{action} + */ + public String getTopic(String topicBase) { + return String.format("%s-%s-%s-%s", topicBase, module, entity, action); + } + + /** + * 根据Topic后缀获取枚举 + * + * @param topicSuffix Topic后缀(格式: module-entity-action) + * @return 枚举值,未找到返回null + */ + public static DatabusEventType getByTopicSuffix(String topicSuffix) { + if (topicSuffix == null) { + return null; + } + for (DatabusEventType type : values()) { + if (type.getTopicSuffix().equalsIgnoreCase(topicSuffix)) { + return type; + } + } + return null; + } + + /** + * 根据模块、实体、操作获取枚举 + * + * @param module 模块编码 + * @param entity 实体编码 + * @param action 操作编码 + * @return 枚举值,未找到返回null + */ + public static DatabusEventType getByModuleEntityAction(String module, String entity, String action) { + for (DatabusEventType type : values()) { + if (type.getModule().equalsIgnoreCase(module) + && type.getEntity().equalsIgnoreCase(entity) + && type.getAction().equalsIgnoreCase(action)) { + return type; + } + } + return null; + } + + /** + * 判断是否为全量同步事件 + */ + public boolean isFullSync() { + return "full".equalsIgnoreCase(this.action); + } + + /** + * 判断是否为增量同步事件 + */ + public boolean isIncrementalSync() { + return !isFullSync(); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/pom.xml b/zt-module-databus/zt-module-databus-server/pom.xml index 008a387f..e94168ad 100644 --- a/zt-module-databus/zt-module-databus-server/pom.xml +++ b/zt-module-databus/zt-module-databus-server/pom.xml @@ -42,6 +42,13 @@ ${revision} + + + com.zt.plat + zt-spring-boot-starter-databus-server + ${revision} + + com.zt.plat diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java new file mode 100644 index 00000000..e3794b25 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java @@ -0,0 +1,90 @@ +package com.zt.plat.module.databus.mq.consumer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService; +import com.zt.plat.module.system.api.mq.DatabusDeptChangeMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * Databus 部门变更消息消费者 + *

+ * 消费来自 system-server 的部门变更消息,通过增量同步服务进行: + * 1. 三态判断(事件/客户端/订阅是否启用) + * 2. 记录到 event_record 流水表 + * 3. 推送到客户端专属 Topic(databus-sync-{clientCode}) + * + * @author ZT + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = DatabusDeptChangeMessage.TOPIC, + consumerGroup = DatabusDeptChangeMessage.TOPIC + "_CONSUMER" +) +public class DatabusDeptChangeConsumer implements RocketMQListener { + + private static final String SOURCE_SERVICE = "system-server"; + + @Resource + private DatabusIncrementalSyncService databusIncrementalSyncService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public void onMessage(DatabusDeptChangeMessage message) { + log.info("[Databus] 收到部门变更消息, action={}, deptId={}", message.getAction(), message.getDeptId()); + + try { + // 构建完整的业务数据快照 + Map dataMap = new HashMap<>(); + dataMap.put("id", message.getDeptId()); + dataMap.put("code", message.getDeptCode()); + dataMap.put("name", message.getDeptName()); + dataMap.put("shortName", message.getShortName()); + dataMap.put("parentId", message.getParentId()); + dataMap.put("sort", message.getSort()); + dataMap.put("leaderUserId", message.getLeaderUserId()); + dataMap.put("phone", message.getPhone()); + dataMap.put("email", message.getStatus()); + dataMap.put("isCompany", message.getIsCompany()); + dataMap.put("isGroup", message.getIsGroup()); + dataMap.put("deptSource", message.getDeptSource()); + dataMap.put("tenantId", message.getTenantId()); + dataMap.put("eventTime", message.getEventTime()); + + // 构建完整的事件类型: system-dept-{action} + String eventType = String.format("system-dept-%s", message.getAction().toLowerCase()); + + // 构建 Databus 事件 + DatabusEvent databusEvent = DatabusEvent.builder() + .eventType(eventType) + .eventAction(message.getAction()) + .dataSnapshot(objectMapper.writeValueAsString(dataMap)) + .dataVersion(1) + .sourceService(SOURCE_SERVICE) + .sourceTopic(DatabusDeptChangeMessage.TOPIC) + .tenantId(message.getTenantId()) + .eventTime(message.getEventTime()) + .build(); + + // 调用增量同步服务处理(三态判断 + 记录流水 + 推送客户端Topic) + databusIncrementalSyncService.processEvent(databusEvent); + + log.info("[Databus] 部门变更事件处理完成, eventType={}, deptId={}", + eventType, message.getDeptId()); + } catch (Exception e) { + log.error("[Databus] 处理部门变更消息失败, action={}, deptId={}", + message.getAction(), message.getDeptId(), e); + throw new RuntimeException("处理部门变更消息失败", e); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusPostChangeConsumer.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusPostChangeConsumer.java new file mode 100644 index 00000000..c51bd5e3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusPostChangeConsumer.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.databus.mq.consumer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService; +import com.zt.plat.module.system.api.mq.DatabusPostChangeMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * Databus 岗位变更消息消费者 + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = DatabusPostChangeMessage.TOPIC, + consumerGroup = DatabusPostChangeMessage.TOPIC + "_CONSUMER" +) +public class DatabusPostChangeConsumer implements RocketMQListener { + + private static final String SOURCE_SERVICE = "system-server"; + + @Resource + private DatabusIncrementalSyncService databusIncrementalSyncService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public void onMessage(DatabusPostChangeMessage message) { + log.info("[Databus] 收到岗位变更消息, action={}, postId={}", message.getAction(), message.getPostId()); + + try { + Map dataMap = new HashMap<>(); + dataMap.put("id", message.getPostId()); + dataMap.put("code", message.getPostCode()); + dataMap.put("name", message.getPostName()); + dataMap.put("sort", message.getSort()); + dataMap.put("status", message.getStatus()); + dataMap.put("remark", message.getRemark()); + dataMap.put("tenantId", message.getTenantId()); + dataMap.put("eventTime", message.getEventTime()); + + String eventType = String.format("system-post-%s", message.getAction().toLowerCase()); + + DatabusEvent databusEvent = DatabusEvent.builder() + .eventType(eventType) + .eventAction(message.getAction()) + .dataSnapshot(objectMapper.writeValueAsString(dataMap)) + .dataVersion(1) + .sourceService(SOURCE_SERVICE) + .sourceTopic(DatabusPostChangeMessage.TOPIC) + .tenantId(message.getTenantId()) + .eventTime(message.getEventTime()) + .build(); + + databusIncrementalSyncService.processEvent(databusEvent); + + log.info("[Databus] 岗位变更事件处理完成, eventType={}, postId={}", + eventType, message.getPostId()); + } catch (Exception e) { + log.error("[Databus] 处理岗位变更消息失败, action={}, postId={}", + message.getAction(), message.getPostId(), e); + throw new RuntimeException("处理岗位变更消息失败", e); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusUserChangeConsumer.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusUserChangeConsumer.java new file mode 100644 index 00000000..f7af1838 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusUserChangeConsumer.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.databus.mq.consumer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService; +import com.zt.plat.module.system.api.mq.DatabusUserChangeMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * Databus 用户变更消息消费者 + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = DatabusUserChangeMessage.TOPIC, + consumerGroup = DatabusUserChangeMessage.TOPIC + "_CONSUMER" +) +public class DatabusUserChangeConsumer implements RocketMQListener { + + private static final String SOURCE_SERVICE = "system-server"; + + @Resource + private DatabusIncrementalSyncService databusIncrementalSyncService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public void onMessage(DatabusUserChangeMessage message) { + log.info("[Databus] 收到用户变更消息, action={}, userId={}", message.getAction(), message.getUserId()); + + try { + Map dataMap = new HashMap<>(); + dataMap.put("id", message.getUserId()); + dataMap.put("username", message.getUsername()); + dataMap.put("nickname", message.getNickname()); + dataMap.put("remark", message.getRemark()); + dataMap.put("deptIds", message.getDeptIds()); + dataMap.put("postIds", message.getPostIds()); + dataMap.put("email", message.getEmail()); + dataMap.put("mobile", message.getMobile()); + dataMap.put("sex", message.getSex()); + dataMap.put("avatar", message.getAvatar()); + dataMap.put("status", message.getStatus()); + dataMap.put("userSource", message.getUserSource()); + dataMap.put("tenantId", message.getTenantId()); + dataMap.put("eventTime", message.getEventTime()); + + String eventType = String.format("system-user-%s", message.getAction().toLowerCase()); + + DatabusEvent databusEvent = DatabusEvent.builder() + .eventType(eventType) + .eventAction(message.getAction()) + .dataSnapshot(objectMapper.writeValueAsString(dataMap)) + .dataVersion(1) + .sourceService(SOURCE_SERVICE) + .sourceTopic(DatabusUserChangeMessage.TOPIC) + .tenantId(message.getTenantId()) + .eventTime(message.getEventTime()) + .build(); + + databusIncrementalSyncService.processEvent(databusEvent); + + log.info("[Databus] 用户变更事件处理完成, eventType={}, userId={}", + eventType, message.getUserId()); + } catch (Exception e) { + log.error("[Databus] 处理用户变更消息失败, action={}, userId={}", + message.getAction(), message.getUserId(), e); + throw new RuntimeException("处理用户变更消息失败", e); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java new file mode 100644 index 00000000..1423be81 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java @@ -0,0 +1,85 @@ +package com.zt.plat.module.databus.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.databus.server.core.provider.DataProvider; +import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 部门数据提供者 + *

+ * 通过 Feign 调用 system-server 获取部门数据 + * + * @author ZT + */ +@Slf4j +@Component +public class DeptDataFeignProvider implements DataProvider { + + public static final String PROVIDER_TYPE = "DEPT"; + + @Resource + private DatabusDeptProviderApi deptProviderApi; + + @Resource + private DataProviderRegistry dataProviderRegistry; + + @PostConstruct + public void init() { + dataProviderRegistry.register(this); + } + + @Override + public String getProviderType() { + return PROVIDER_TYPE; + } + + @Override + public CursorPageData getPageByCursor(LocalDateTime cursorTime, Long cursorId, + int batchSize, Long tenantId) { + CursorPageReqDTO reqDTO = CursorPageReqDTO.builder() + .cursorTime(cursorTime) + .cursorId(cursorId) + .batchSize(batchSize) + .tenantId(tenantId) + .build(); + + CommonResult> result = deptProviderApi.getPageByCursor(reqDTO); + if (!result.isSuccess()) { + throw new RuntimeException("获取部门数据失败: " + result.getMsg()); + } + + CursorPageResult pageResult = result.getData(); + return CursorPageData.of( + pageResult.getList(), + pageResult.getNextCursorTime(), + pageResult.getNextCursorId(), + pageResult.getCount(), + Boolean.TRUE.equals(pageResult.getHasMore()), + (pageResult.getTotal() != null ? pageResult.getTotal() : 0L) + ); + } + + @Override + public long count(Long tenantId) { + CommonResult result = deptProviderApi.count(tenantId); + if (!result.isSuccess()) { + throw new RuntimeException("获取部门总数失败: " + result.getMsg()); + } + return result.getData(); + } + + @Override + public Long extractUid(DatabusDeptData data) { + return data.getId(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java new file mode 100644 index 00000000..cfb4d8e3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.databus.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.databus.server.core.provider.DataProvider; +import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.provider.DatabusPostProviderApi; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 岗位数据提供者 + */ +@Slf4j +@Component +public class PostDataFeignProvider implements DataProvider { + + public static final String PROVIDER_TYPE = "POST"; + + @Resource + private DatabusPostProviderApi postProviderApi; + + @Resource + private DataProviderRegistry dataProviderRegistry; + + @PostConstruct + public void init() { + dataProviderRegistry.register(this); + } + + @Override + public String getProviderType() { + return PROVIDER_TYPE; + } + + @Override + public CursorPageData getPageByCursor(LocalDateTime cursorTime, Long cursorId, + int batchSize, Long tenantId) { + CursorPageReqDTO reqDTO = CursorPageReqDTO.builder() + .cursorTime(cursorTime) + .cursorId(cursorId) + .batchSize(batchSize) + .tenantId(tenantId) + .build(); + + CommonResult> result = postProviderApi.getPageByCursor(reqDTO); + if (!result.isSuccess()) { + throw new RuntimeException("获取岗位数据失败: " + result.getMsg()); + } + + CursorPageResult pageResult = result.getData(); + return CursorPageData.of( + pageResult.getList(), + pageResult.getNextCursorTime(), + pageResult.getNextCursorId(), + pageResult.getCount(), + Boolean.TRUE.equals(pageResult.getHasMore()), + (pageResult.getTotal() != null ? pageResult.getTotal() : 0L) + ); + } + + @Override + public long count(Long tenantId) { + CommonResult result = postProviderApi.count(tenantId); + if (!result.isSuccess()) { + throw new RuntimeException("获取岗位总数失败: " + result.getMsg()); + } + return result.getData(); + } + + @Override + public Long extractUid(DatabusPostData data) { + return data.getId(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java new file mode 100644 index 00000000..b2c162e3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.databus.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.databus.server.core.provider.DataProvider; +import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry; +import com.zt.plat.module.databus.api.dto.CursorPageReqDTO; +import com.zt.plat.module.databus.api.dto.CursorPageResult; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.provider.DatabusUserProviderApi; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 用户数据提供者 + */ +@Slf4j +@Component +public class UserDataFeignProvider implements DataProvider { + + public static final String PROVIDER_TYPE = "USER"; + + @Resource + private DatabusUserProviderApi userProviderApi; + + @Resource + private DataProviderRegistry dataProviderRegistry; + + @PostConstruct + public void init() { + dataProviderRegistry.register(this); + } + + @Override + public String getProviderType() { + return PROVIDER_TYPE; + } + + @Override + public CursorPageData getPageByCursor(LocalDateTime cursorTime, Long cursorId, + int batchSize, Long tenantId) { + CursorPageReqDTO reqDTO = CursorPageReqDTO.builder() + .cursorTime(cursorTime) + .cursorId(cursorId) + .batchSize(batchSize) + .tenantId(tenantId) + .build(); + + CommonResult> result = userProviderApi.getPageByCursor(reqDTO); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户数据失败: " + result.getMsg()); + } + + CursorPageResult pageResult = result.getData(); + return CursorPageData.of( + pageResult.getList(), + pageResult.getNextCursorTime(), + pageResult.getNextCursorId(), + pageResult.getCount(), + Boolean.TRUE.equals(pageResult.getHasMore()), + (pageResult.getTotal() != null ? pageResult.getTotal() : 0L) + ); + } + + @Override + public long count(Long tenantId) { + CommonResult result = userProviderApi.count(tenantId); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户总数失败: " + result.getMsg()); + } + return result.getData(); + } + + @Override + public Long extractUid(DatabusAdminUserData data) { + return data.getId(); + } +} From 94ab320fa8908c5d0162241c68c54c9c964dada9 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Tue, 2 Dec 2025 00:23:09 +0800 Subject: [PATCH 03/10] =?UTF-8?q?feat(system-server):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20DataBus=20MapStruct=20=E6=95=B0=E6=8D=AE=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DatabusDeptConvert (DeptDO → DatabusDeptData) - 新增 DatabusUserConvert (AdminUserDO → DatabusAdminUserData) - 新增 DatabusPostConvert (PostDO → DatabusPostData) 说明: - 转换器忽略了需要额外查询的复杂字段(如 deptType, leaderUserName, depts, posts) - PostDO 不包含 tenantId,已配置忽略该字段映射 - 验证 system-server 编译通过 Ref: docs/databus/implementation-checklist.md 任务 20-22 --- .../convert/databus/DatabusDeptConvert.java | 36 +++++++++++++++++++ .../convert/databus/DatabusPostConvert.java | 35 ++++++++++++++++++ .../convert/databus/DatabusUserConvert.java | 36 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusDeptConvert.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusPostConvert.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusUserConvert.java diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusDeptConvert.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusDeptConvert.java new file mode 100644 index 00000000..e601729b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusDeptConvert.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.system.convert.databus; + +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 部门数据转换器 + *

+ * 用于将 DeptDO 转换为 DatabusDeptData + * + * @author ZT + */ +@Mapper +public interface DatabusDeptConvert { + + DatabusDeptConvert INSTANCE = Mappers.getMapper(DatabusDeptConvert.class); + + /** + * DeptDO → DatabusDeptData + *

+ * 注意:deptType 和 leaderUserName 字段需要额外处理,这里不做映射 + */ + @Mapping(target = "deptType", ignore = true) + @Mapping(target = "leaderUserName", ignore = true) + DatabusDeptData convert(DeptDO dept); + + /** + * List → List + */ + List convertList(List deptList); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusPostConvert.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusPostConvert.java new file mode 100644 index 00000000..bff389e8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusPostConvert.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.system.convert.databus; + +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.system.dal.dataobject.dept.PostDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 岗位数据转换器 + *

+ * 用于将 PostDO 转换为 DatabusPostData + * + * @author ZT + */ +@Mapper +public interface DatabusPostConvert { + + DatabusPostConvert INSTANCE = Mappers.getMapper(DatabusPostConvert.class); + + /** + * PostDO → DatabusPostData + *

+ * 注意:tenantId 字段在 PostDO 中不存在,需要额外处理 + */ + @Mapping(target = "tenantId", ignore = true) + DatabusPostData convert(PostDO post); + + /** + * List → List + */ + List convertList(List postList); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusUserConvert.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusUserConvert.java new file mode 100644 index 00000000..22c3f569 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/databus/DatabusUserConvert.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.system.convert.databus; + +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 用户数据转换器 + *

+ * 用于将 AdminUserDO 转换为 DatabusAdminUserData + * + * @author ZT + */ +@Mapper +public interface DatabusUserConvert { + + DatabusUserConvert INSTANCE = Mappers.getMapper(DatabusUserConvert.class); + + /** + * AdminUserDO → DatabusAdminUserData + *

+ * 注意:depts 和 posts 字段需要额外查询和处理,这里不做映射 + */ + @Mapping(target = "depts", ignore = true) + @Mapping(target = "posts", ignore = true) + DatabusAdminUserData convert(AdminUserDO user); + + /** + * List → List + */ + List convertList(List userList); +} From 8329f9c8342462d3ef1fd58fcb048671def618d7 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Tue, 2 Dec 2025 01:07:30 +0800 Subject: [PATCH 04/10] =?UTF-8?q?feat(databus-client):=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=B2=97=E4=BD=8D=20Handler=20=E5=8F=8A=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=AA=8C=E8=AF=81=EF=BC=88=E4=BB=BB=E5=8A=A1=2075-88?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增文件(任务 75-88): - PostSyncService.java - 岗位同步服务接口 - PostSyncServiceImpl.java - 岗位同步服务实现(Feign调用) - SystemPostCreateHandler.java - 岗位创建事件处理器 - SystemPostUpdateHandler.java - 岗位更新事件处理器 - SystemPostDeleteHandler.java - 岗位删除事件处理器 - SystemPostFullHandler.java - 岗位全量同步处理器(批量) 修复问题: 1. 修复 DTO 导入:DeptSaveReqVO → DeptSaveReqDTO, PostSaveReqVO → PostSaveReqDTO 2. 修复注解:@Resource(required=false) → @Autowired(required=false) 3. 修复 PostApi 包路径:com.zt.plat.module.system.api.post → com.zt.plat.module.system.api.dept 4. 修复 DeptSaveReqDTO 字段映射(移除不存在的字段:code, shortName, isCompany, isGroup, deptSource) 5. 修复 AdminUserSaveReqDTO 字段映射: - deptIds: List → Set - postIds: List → Set 编译结果:✅ BUILD SUCCESS(28个源文件) Ref: docs/databus/implementation-checklist.md 任务 75-88 --- .../client/handler/dept/DeptSyncService.java | 44 +++++++ .../handler/dept/DeptSyncServiceImpl.java | 105 ++++++++++++++++ .../handler/dept/SystemDeptCreateHandler.java | 50 ++++++++ .../handler/dept/SystemDeptDeleteHandler.java | 49 ++++++++ .../handler/dept/SystemDeptFullHandler.java | 68 +++++++++++ .../handler/dept/SystemDeptUpdateHandler.java | 49 ++++++++ .../client/handler/post/PostSyncService.java | 103 +++------------- .../handler/post/PostSyncServiceImpl.java | 103 ++++++++++++++++ .../handler/post/SystemPostCreateHandler.java | 50 ++++++++ .../handler/post/SystemPostDeleteHandler.java | 49 ++++++++ .../handler/post/SystemPostFullHandler.java | 51 ++++---- .../handler/post/SystemPostUpdateHandler.java | 50 ++++++++ .../handler/user/AdminUserSyncService.java | 44 +++++++ .../user/AdminUserSyncServiceImpl.java | 112 ++++++++++++++++++ .../handler/user/SystemUserCreateHandler.java | 49 ++++++++ .../handler/user/SystemUserDeleteHandler.java | 49 ++++++++ .../handler/user/SystemUserFullHandler.java | 68 +++++++++++ .../handler/user/SystemUserUpdateHandler.java | 49 ++++++++ 18 files changed, 1031 insertions(+), 111 deletions(-) create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptCreateHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptDeleteHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptFullHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptUpdateHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostCreateHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostDeleteHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostUpdateHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserCreateHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserDeleteHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserFullHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserUpdateHandler.java diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncService.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncService.java new file mode 100644 index 00000000..42feee49 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncService.java @@ -0,0 +1,44 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.module.databus.api.data.DatabusDeptData; + +/** + * 部门同步服务接口 + *

+ * 分公司需要实现此接口,完成数据的本地持久化 + * 或通过默认实现 {@link DeptSyncServiceImpl} 使用 Feign 调用远程 API + * + * @author ZT + */ +public interface DeptSyncService { + + /** + * 创建部门(增量同步) + * + * @param data 部门数据 + */ + void create(DatabusDeptData data); + + /** + * 更��部门(增量同步) + * + * @param data 部门数据 + */ + void update(DatabusDeptData data); + + /** + * 删除部门(增量同步) + * + * @param id 部门ID + */ + void delete(Long id); + + /** + * 全量同步单条数据 + *

+ * 逻辑:存在则更新,不存在则插入 + * + * @param data 部门数据 + */ + void fullSync(DatabusDeptData data); +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java new file mode 100644 index 00000000..ca760435 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java @@ -0,0 +1,105 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +/** + * 部门同步服务实现(通过 Feign API 调用远程服务) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 系统中存在 DeptApi 接口(Feign 客户端) + *

+ * 如果分公司需要自定义实现,可以创建自己的 DeptSyncService Bean, + * 此默认实现会自动失效(@ConditionalOnMissingBean) + * + * @author ZT + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.DeptApi") +public class DeptSyncServiceImpl implements DeptSyncService { + + @Autowired(required = false) + private DeptApi deptApi; // Feign 远程调用接口 + + @Override + public void create(DatabusDeptData data) { + if (deptApi == null) { + log.warn("[DeptSync] DeptApi未注入,跳过创建部门操作, deptId={}", data.getId()); + return; + } + DeptSaveReqDTO dto = buildDeptDTO(data); + deptApi.createDept(dto).checkError(); + log.info("[DeptSync] 部门创建成功, deptId={}, deptName={}", dto.getId(), dto.getName()); + } + + @Override + public void update(DatabusDeptData data) { + if (deptApi == null) { + log.warn("[DeptSync] DeptApi未注入,跳过更新部门操作, deptId={}", data.getId()); + return; + } + DeptSaveReqDTO dto = buildDeptDTO(data); + deptApi.updateDept(dto).checkError(); + log.info("[DeptSync] 部门更新成功, deptId={}, deptName={}", dto.getId(), dto.getName()); + } + + @Override + public void delete(Long id) { + if (deptApi == null) { + log.warn("[DeptSync] DeptApi未注入,跳过删除部门操作, deptId={}", id); + return; + } + deptApi.deleteDept(id).checkError(); + log.info("[DeptSync] 部门删除成功, deptId={}", id); + } + + @Override + public void fullSync(DatabusDeptData data) { + if (deptApi == null) { + log.warn("[DeptSync] DeptApi未注入,跳过全量同步部门操作, deptId={}", data.getId()); + return; + } + DeptSaveReqDTO dto = buildDeptDTO(data); + try { + // 尝试获取,存在则更新,不存在则创建 + var existing = deptApi.getDept(dto.getId()); + if (existing.isSuccess() && existing.getData() != null) { + deptApi.updateDept(dto).checkError(); + log.info("[DeptSync] 部门全量同步-更新成功, deptId={}", dto.getId()); + } else { + deptApi.createDept(dto).checkError(); + log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId()); + } + } catch (Exception e) { + // 获取失败,尝试创建 + log.warn("[DeptSync] 部门获取失败,尝试创建, deptId={}", dto.getId()); + deptApi.createDept(dto).checkError(); + log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId()); + } + } + + /** + * 构建部门 DTO(用于 Feign 调用) + */ + private DeptSaveReqDTO buildDeptDTO(DatabusDeptData data) { + DeptSaveReqDTO dto = new DeptSaveReqDTO(); + dto.setId(data.getId()); + dto.setName(data.getName()); + dto.setParentId(data.getParentId()); + dto.setSort(data.getSort()); + dto.setLeaderUserId(data.getLeaderUserId()); + dto.setPhone(data.getPhone()); + dto.setEmail(data.getEmail()); + dto.setStatus(data.getStatus()); + return dto; + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptCreateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptCreateHandler.java new file mode 100644 index 00000000..7bcad72f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptCreateHandler.java @@ -0,0 +1,50 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 部门创建事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 DeptSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(DeptSyncService.class) +public class SystemDeptCreateHandler implements SyncEventHandler { + + @Resource + private DeptSyncService deptSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_DEPT_CREATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusDeptData data = message.getData(); + log.info("[DeptSync] 收到部门创建事件, id={}, name={}, parentId={}", + data.getId(), data.getName(), data.getParentId()); + + try { + deptSyncService.create(data); + log.info("[DeptSync] 部门创建成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[DeptSync] 部门创建失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptDeleteHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptDeleteHandler.java new file mode 100644 index 00000000..4ac8c687 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptDeleteHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 部门删除事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 DeptSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(DeptSyncService.class) +public class SystemDeptDeleteHandler implements SyncEventHandler { + + @Resource + private DeptSyncService deptSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_DEPT_DELETE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusDeptData data = message.getData(); + log.info("[DeptSync] 收到部门删除事件, id={}", data.getId()); + + try { + deptSyncService.delete(data.getId()); + log.info("[DeptSync] 部门删除成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[DeptSync] 部门删除失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptFullHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptFullHandler.java new file mode 100644 index 00000000..fc68dc38 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptFullHandler.java @@ -0,0 +1,68 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 部门全量同步事件处理器(批量处理) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 DeptSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(DeptSyncService.class) +public class SystemDeptFullHandler implements BatchSyncEventHandler { + + @Resource + private DeptSyncService deptSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_DEPT_FULL; + } + + @Override + public void onFullSyncStart(DatabusBatchMessage message) { + log.info("[DeptSync] 开始部门全量同步, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); + } + + @Override + public void handleBatch(DatabusBatchMessage message) { + log.info("[DeptSync] 处理部门批次数据, taskId={}, batchNo={}/{}, size={}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch(), + message.getDataList().size()); + + // 逐条处理全量同步数据 + for (DatabusDeptData data : message.getDataList()) { + try { + deptSyncService.fullSync(data); + log.debug("[DeptSync] 部门全量同步成功, id={}, name={}", data.getId(), data.getName()); + } catch (Exception e) { + log.error("[DeptSync] 部门全量同步失败, id={}, name={}", data.getId(), data.getName(), e); + // 单条失败不影响其他数据,继续处理 + } + } + + log.info("[DeptSync] 部门批次处理完成, taskId={}, batchNo={}/{}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch()); + } + + @Override + public void onFullSyncComplete(DatabusBatchMessage message) { + log.info("[DeptSync] 部门全量同步完成, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptUpdateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptUpdateHandler.java new file mode 100644 index 00000000..8e7f68ed --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/SystemDeptUpdateHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.dept; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusDeptData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 部门更新事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 DeptSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(DeptSyncService.class) +public class SystemDeptUpdateHandler implements SyncEventHandler { + + @Resource + private DeptSyncService deptSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_DEPT_UPDATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusDeptData data = message.getData(); + log.info("[DeptSync] 收到部门更新事件, id={}, name={}", data.getId(), data.getName()); + + try { + deptSyncService.update(data); + log.info("[DeptSync] 部门更新成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[DeptSync] 部门更新失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java index f42330a9..f5346d2d 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncService.java @@ -1,108 +1,43 @@ package com.zt.plat.framework.databus.client.handler.post; -import com.zt.plat.module.databus.api.data.PostData; -import com.zt.plat.module.system.api.dept.PostApi; -import com.zt.plat.module.system.api.dept.dto.PostSaveReqDTO; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; +import com.zt.plat.module.databus.api.data.DatabusPostData; /** - * 岗位同步业务逻辑 + * 岗位同步服务接口 *

- * 被各个 PostHandler 共享使用 + * 客户端可以自定义实现此接口,覆盖默认的 Feign 调用实现 + *

+ * 默认实现:{@link PostSyncServiceImpl}(通过 Feign 远程调用) * * @author ZT */ -@Slf4j -@Service -@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") -@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.PostApi") -public class PostSyncService { - - @Autowired(required = false) - private PostApi postApi; +public interface PostSyncService { /** * 创建岗位 + * + * @param data 岗位数据 */ - public void create(PostData data) { - if (postApi == null) { - log.warn("[PostSync] PostApi未注入,跳过创建岗位操作, postId={}", data.getId()); - return; - } - PostSaveReqDTO dto = buildPostDTO(data); - postApi.createPost(dto).checkError(); - log.info("[PostSync] 创建岗位成功, postId={}, postName={}", dto.getId(), dto.getName()); - } + void create(DatabusPostData data); /** * 更新岗位 + * + * @param data 岗位数据 */ - public void update(PostData data) { - if (postApi == null) { - log.warn("[PostSync] PostApi未注入,跳过更新岗位操作, postId={}", data.getId()); - return; - } - PostSaveReqDTO dto = buildPostDTO(data); - postApi.updatePost(dto).checkError(); - log.info("[PostSync] 更新岗位成功, postId={}, postName={}", dto.getId(), dto.getName()); - } + void update(DatabusPostData data); /** * 删除岗位 + * + * @param id 岗位 ID */ - public void delete(PostData data) { - if (postApi == null) { - log.warn("[PostSync] PostApi未注入,跳过删除岗位操作, postId={}", data.getId()); - return; - } - Long postId = data.getId(); - if (postId != null) { - postApi.deletePost(postId).checkError(); - log.info("[PostSync] 删除岗位成功, postId={}", postId); - } - } + void delete(Long id); /** - * 全量同步单条数据(存在则更新,不存在则创建) + * 全量同步岗位(创建或更新) + * + * @param data 岗位数据 */ - public void fullSync(PostData data) { - if (postApi == null) { - log.warn("[PostSync] PostApi未注入,跳过全量同步岗位操作, postId={}", data.getId()); - return; - } - PostSaveReqDTO dto = buildPostDTO(data); - try { - if (dto.getId() != null) { - var existing = postApi.getPost(dto.getId()); - if (existing.isSuccess() && existing.getData() != null) { - postApi.updatePost(dto).checkError(); - } else { - postApi.createPost(dto).checkError(); - } - } else { - postApi.createPost(dto).checkError(); - } - } catch (Exception e) { - postApi.createPost(dto).checkError(); - } - } - - /** - * 构建岗位DTO - */ - private PostSaveReqDTO buildPostDTO(PostData data) { - PostSaveReqDTO dto = new PostSaveReqDTO(); - dto.setId(data.getId()); - dto.setCode(data.getCode()); - dto.setName(data.getName()); - dto.setSort(data.getSort()); - dto.setStatus(data.getStatus()); - dto.setRemark(data.getRemark()); - return dto; - } - + void fullSync(DatabusPostData data); } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java new file mode 100644 index 00000000..463fdc6e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java @@ -0,0 +1,103 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.dept.dto.PostSaveReqDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +/** + * 岗位同步服务默认实现(通过 Feign 调用) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. PostApi 类存在于 classpath + *

+ * 客户端可以自定义实现 PostSyncService 接口覆盖此默认实现 + * + * @author ZT + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.PostApi") +public class PostSyncServiceImpl implements PostSyncService { + + @Autowired(required = false) + private PostApi postApi; + + @Override + public void create(DatabusPostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过创建岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + postApi.createPost(dto).checkError(); + log.info("[PostSync] 岗位创建成功, postId={}, postName={}", dto.getId(), dto.getName()); + } + + @Override + public void update(DatabusPostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过更新岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + postApi.updatePost(dto).checkError(); + log.info("[PostSync] 岗位更新成功, postId={}, postName={}", dto.getId(), dto.getName()); + } + + @Override + public void delete(Long id) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过删除岗位操作, postId={}", id); + return; + } + postApi.deletePost(id).checkError(); + log.info("[PostSync] 岗位删除成功, postId={}", id); + } + + @Override + public void fullSync(DatabusPostData data) { + if (postApi == null) { + log.warn("[PostSync] PostApi未注入,跳过全量同步岗位操作, postId={}", data.getId()); + return; + } + PostSaveReqDTO dto = buildPostDTO(data); + try { + // 尝试获取,存在则更新,不存在则创建 + var existing = postApi.getPost(dto.getId()); + if (existing.isSuccess() && existing.getData() != null) { + postApi.updatePost(dto).checkError(); + log.info("[PostSync] 岗位全量同步-更新成功, postId={}, postName={}", dto.getId(), dto.getName()); + } else { + postApi.createPost(dto).checkError(); + log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName()); + } + } catch (Exception e) { + // 获取失败,尝试创建 + try { + postApi.createPost(dto).checkError(); + log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName()); + } catch (Exception createEx) { + log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}", dto.getId(), dto.getName(), createEx); + throw createEx; + } + } + } + + private PostSaveReqDTO buildPostDTO(DatabusPostData data) { + PostSaveReqDTO dto = new PostSaveReqDTO(); + dto.setId(data.getId()); + dto.setName(data.getName()); + dto.setCode(data.getCode()); + dto.setSort(data.getSort()); + dto.setStatus(data.getStatus()); + dto.setRemark(data.getRemark()); + return dto; + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostCreateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostCreateHandler.java new file mode 100644 index 00000000..98eb55df --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostCreateHandler.java @@ -0,0 +1,50 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 岗位创建事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 PostSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(PostSyncService.class) +public class SystemPostCreateHandler implements SyncEventHandler { + + @Resource + private PostSyncService postSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_POST_CREATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusPostData data = message.getData(); + log.info("[PostSync] 收到岗位创建事件, id={}, name={}, code={}", + data.getId(), data.getName(), data.getCode()); + + try { + postSyncService.create(data); + log.info("[PostSync] 岗位创建成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[PostSync] 岗位创建失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostDeleteHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostDeleteHandler.java new file mode 100644 index 00000000..645648a2 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostDeleteHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 岗位删除事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 PostSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(PostSyncService.class) +public class SystemPostDeleteHandler implements SyncEventHandler { + + @Resource + private PostSyncService postSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_POST_DELETE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusPostData data = message.getData(); + log.info("[PostSync] 收到岗位删除事件, id={}", data.getId()); + + try { + postSyncService.delete(data.getId()); + log.info("[PostSync] 岗位删除成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[PostSync] 岗位删除失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java index 50727a74..10be89a0 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostFullHandler.java @@ -1,7 +1,7 @@ package com.zt.plat.framework.databus.client.handler.post; import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; -import com.zt.plat.module.databus.api.data.PostData; +import com.zt.plat.module.databus.api.data.DatabusPostData; import com.zt.plat.module.databus.api.message.DatabusBatchMessage; import com.zt.plat.module.databus.enums.DatabusEventType; import jakarta.annotation.Resource; @@ -11,7 +11,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; /** - * 岗位全量同步事件处理器 + * 岗位全量同步事件处理器(批量处理) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 PostSyncService Bean * * @author ZT */ @@ -19,7 +23,7 @@ import org.springframework.stereotype.Component; @Component @ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") @ConditionalOnBean(PostSyncService.class) -public class SystemPostFullHandler implements BatchSyncEventHandler { +public class SystemPostFullHandler implements BatchSyncEventHandler { @Resource private PostSyncService postSyncService; @@ -30,42 +34,35 @@ public class SystemPostFullHandler implements BatchSyncEventHandler { } @Override - public void onFullSyncStart(DatabusBatchMessage message) { - log.info("[PostSync] 全量同步开始, taskId={}, totalCount={}, totalBatch={}", - message.getTaskId(), message.getTotalCount(), message.getTotalBatch()); + public void onFullSyncStart(DatabusBatchMessage message) { + log.info("[PostSync] 开始岗位全量同步, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); } @Override - public void handleBatch(DatabusBatchMessage message) { - log.info("[PostSync] 处理批次, batchNo={}/{}, count={}", - message.getBatchNo(), message.getTotalBatch(), message.getCount()); + public void handleBatch(DatabusBatchMessage message) { + log.info("[PostSync] 处理岗位批次数据, taskId={}, batchNo={}/{}, size={}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch(), + message.getDataList().size()); - if (message.getDataList() == null || message.getDataList().isEmpty()) { - log.warn("[PostSync] 数据列表为空, batchNo={}", message.getBatchNo()); - return; - } - - int successCount = 0; - int failCount = 0; - - for (PostData data : message.getDataList()) { + // 逐条处理全量同步数据 + for (DatabusPostData data : message.getDataList()) { try { postSyncService.fullSync(data); - successCount++; + log.debug("[PostSync] 岗位全量同步成功, id={}, name={}", data.getId(), data.getName()); } catch (Exception e) { - failCount++; - log.error("[PostSync] 处理数据项失败, postId={}", data.getId(), e); + log.error("[PostSync] 岗位全量同步失败, id={}, name={}", data.getId(), data.getName(), e); + // 单条失败不影响其他数据,继续处理 } } - log.info("[PostSync] 批次处理完成, batchNo={}, success={}, fail={}", - message.getBatchNo(), successCount, failCount); + log.info("[PostSync] 岗位批次处理完成, taskId={}, batchNo={}/{}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch()); } @Override - public void onFullSyncComplete(DatabusBatchMessage message) { - log.info("[PostSync] 全量同步完成, taskId={}, totalCount={}", - message.getTaskId(), message.getTotalCount()); + public void onFullSyncComplete(DatabusBatchMessage message) { + log.info("[PostSync] 岗位全量同步完成, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); } - } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostUpdateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostUpdateHandler.java new file mode 100644 index 00000000..e8d40e82 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/SystemPostUpdateHandler.java @@ -0,0 +1,50 @@ +package com.zt.plat.framework.databus.client.handler.post; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusPostData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 岗位更新事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 PostSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(PostSyncService.class) +public class SystemPostUpdateHandler implements SyncEventHandler { + + @Resource + private PostSyncService postSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_POST_UPDATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusPostData data = message.getData(); + log.info("[PostSync] 收到岗位更新事件, id={}, name={}, code={}", + data.getId(), data.getName(), data.getCode()); + + try { + postSyncService.update(data); + log.info("[PostSync] 岗位更新成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[PostSync] 岗位更新失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncService.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncService.java new file mode 100644 index 00000000..3409a96f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncService.java @@ -0,0 +1,44 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; + +/** + * 用户同步服务接口 + *

+ * 分公司需要实现此接口,完成数据的本地持久化 + * 或通过默认实现 {@link AdminUserSyncServiceImpl} 使用 Feign 调用远程 API + * + * @author ZT + */ +public interface AdminUserSyncService { + + /** + * 创建用户(增量同步) + * + * @param data 用户数据 + */ + void create(DatabusAdminUserData data); + + /** + * 更新用户(增量同步) + * + * @param data 用户数据 + */ + void update(DatabusAdminUserData data); + + /** + * 删除用户(增量同步) + * + * @param id 用户ID + */ + void delete(Long id); + + /** + * 全量同步单条数据 + *

+ * 逻辑:存在则更新,不存在则插入 + * + * @param data 用户数据 + */ + void fullSync(DatabusAdminUserData data); +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java new file mode 100644 index 00000000..d40cfd51 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java @@ -0,0 +1,112 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.system.api.user.AdminUserApi; +import com.zt.plat.module.system.api.user.dto.AdminUserSaveReqDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.Set; + +/** + * 用户同步服务实现(通过 Feign API 调用远程服务) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 系统中存在 AdminUserApi 接口(Feign 客户端) + *

+ * 如果分公司需要自定义实现,可以创建自己的 AdminUserSyncService Bean, + * 此默认实现会自动失效(@ConditionalOnMissingBean) + * + * @author ZT + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnClass(name = "com.zt.plat.module.system.api.user.AdminUserApi") +public class AdminUserSyncServiceImpl implements AdminUserSyncService { + + @Autowired(required = false) + private AdminUserApi adminUserApi; // Feign 远程调用接口 + + @Override + public void create(DatabusAdminUserData data) { + if (adminUserApi == null) { + log.warn("[UserSync] AdminUserApi未注入,跳过创建用户操作, userId={}", data.getId()); + return; + } + AdminUserSaveReqDTO dto = buildUserDTO(data); + adminUserApi.createUser(dto).checkError(); + log.info("[UserSync] 用户创建成功, userId={}, username={}", dto.getId(), dto.getUsername()); + } + + @Override + public void update(DatabusAdminUserData data) { + if (adminUserApi == null) { + log.warn("[UserSync] AdminUserApi未注入,跳过更新用户操作, userId={}", data.getId()); + return; + } + AdminUserSaveReqDTO dto = buildUserDTO(data); + adminUserApi.updateUser(dto).checkError(); + log.info("[UserSync] 用户更新成功, userId={}, username={}", dto.getId(), dto.getUsername()); + } + + @Override + public void delete(Long id) { + if (adminUserApi == null) { + log.warn("[UserSync] AdminUserApi未注入,跳过删除用户操作, userId={}", id); + return; + } + adminUserApi.deleteUser(id).checkError(); + log.info("[UserSync] 用户删除成功, userId={}", id); + } + + @Override + public void fullSync(DatabusAdminUserData data) { + if (adminUserApi == null) { + log.warn("[UserSync] AdminUserApi未注入,跳过全量同步用户操作, userId={}", data.getId()); + return; + } + AdminUserSaveReqDTO dto = buildUserDTO(data); + try { + // 尝试获取,存在则更新,不存在则创建 + var existing = adminUserApi.getUser(dto.getId()); + if (existing.isSuccess() && existing.getData() != null) { + adminUserApi.updateUser(dto).checkError(); + log.info("[UserSync] 用户全量同步-更新成功, userId={}", dto.getId()); + } else { + adminUserApi.createUser(dto).checkError(); + log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId()); + } + } catch (Exception e) { + // 获取失败,尝试创建 + log.warn("[UserSync] 用户获取失败,尝试创建, userId={}", dto.getId()); + adminUserApi.createUser(dto).checkError(); + log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId()); + } + } + + /** + * 构建用户 DTO(用于 Feign 调用) + */ + private AdminUserSaveReqDTO buildUserDTO(DatabusAdminUserData data) { + AdminUserSaveReqDTO dto = new AdminUserSaveReqDTO(); + dto.setId(data.getId()); + dto.setUsername(data.getUsername()); + dto.setNickname(data.getNickname()); + dto.setRemark(data.getRemark()); + // 将 List 转换为 Set + dto.setDeptIds(data.getDeptIds() != null ? new HashSet<>(data.getDeptIds()) : null); + dto.setPostIds(data.getPostIds() != null ? new HashSet<>(data.getPostIds()) : null); + dto.setEmail(data.getEmail()); + dto.setMobile(data.getMobile()); + dto.setSex(data.getSex()); + dto.setAvatar(data.getAvatar()); + dto.setStatus(data.getStatus()); + return dto; + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserCreateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserCreateHandler.java new file mode 100644 index 00000000..875043e7 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserCreateHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 用户创建事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 AdminUserSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(AdminUserSyncService.class) +public class SystemUserCreateHandler implements SyncEventHandler { + + @Resource + private AdminUserSyncService adminUserSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_USER_CREATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusAdminUserData data = message.getData(); + log.info("[UserSync] 收到用户创建事件, id={}, username={}", data.getId(), data.getUsername()); + + try { + adminUserSyncService.create(data); + log.info("[UserSync] 用户创建成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[UserSync] 用户创建失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserDeleteHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserDeleteHandler.java new file mode 100644 index 00000000..139286ae --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserDeleteHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 用户删除事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 AdminUserSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(AdminUserSyncService.class) +public class SystemUserDeleteHandler implements SyncEventHandler { + + @Resource + private AdminUserSyncService adminUserSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_USER_DELETE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusAdminUserData data = message.getData(); + log.info("[UserSync] 收到用户删除事件, id={}", data.getId()); + + try { + adminUserSyncService.delete(data.getId()); + log.info("[UserSync] 用户删除成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[UserSync] 用户删除失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserFullHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserFullHandler.java new file mode 100644 index 00000000..37a2dd72 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserFullHandler.java @@ -0,0 +1,68 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 用户全量同步事件处理器(批量处理) + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 AdminUserSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(AdminUserSyncService.class) +public class SystemUserFullHandler implements BatchSyncEventHandler { + + @Resource + private AdminUserSyncService adminUserSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_USER_FULL; + } + + @Override + public void onFullSyncStart(DatabusBatchMessage message) { + log.info("[UserSync] 开始用户全量同步, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); + } + + @Override + public void handleBatch(DatabusBatchMessage message) { + log.info("[UserSync] 处理用户批次数据, taskId={}, batchNo={}/{}, size={}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch(), + message.getDataList().size()); + + // 逐条处理全量同步数据 + for (DatabusAdminUserData data : message.getDataList()) { + try { + adminUserSyncService.fullSync(data); + log.debug("[UserSync] 用户全量同步成功, id={}, username={}", data.getId(), data.getUsername()); + } catch (Exception e) { + log.error("[UserSync] 用户全量同步失败, id={}, username={}", data.getId(), data.getUsername(), e); + // 单条失败不影响其他数据,继续处理 + } + } + + log.info("[UserSync] 用户批次处理完成, taskId={}, batchNo={}/{}", + message.getTaskId(), message.getBatchNo(), message.getTotalBatch()); + } + + @Override + public void onFullSyncComplete(DatabusBatchMessage message) { + log.info("[UserSync] 用户全量同步完成, taskId={}, totalBatch={}", + message.getTaskId(), message.getTotalBatch()); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserUpdateHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserUpdateHandler.java new file mode 100644 index 00000000..46704f92 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/SystemUserUpdateHandler.java @@ -0,0 +1,49 @@ +package com.zt.plat.framework.databus.client.handler.user; + +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.data.DatabusAdminUserData; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * 用户更新事件处理器 + *

+ * 使用条件: + * 1. zt.databus.sync.client.enabled=true + * 2. 存在 AdminUserSyncService Bean + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@ConditionalOnBean(AdminUserSyncService.class) +public class SystemUserUpdateHandler implements SyncEventHandler { + + @Resource + private AdminUserSyncService adminUserSyncService; + + @Override + public DatabusEventType getSupportedEventType() { + return DatabusEventType.SYSTEM_USER_UPDATE; + } + + @Override + public void handle(DatabusMessage message) { + DatabusAdminUserData data = message.getData(); + log.info("[UserSync] 收到用户更新事件, id={}, username={}", data.getId(), data.getUsername()); + + try { + adminUserSyncService.update(data); + log.info("[UserSync] 用户更新成功, id={}", data.getId()); + } catch (Exception e) { + log.error("[UserSync] 用户更新失败, id={}", data.getId(), e); + throw e; // 抛出异常触发重试 + } + } +} From adf3ec601a33223f82b88e2fdd3a0ae2ced6c732 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Tue, 2 Dec 2025 01:18:46 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat(databus-client):=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=A0=B8=E5=BF=83=E7=BB=84=E4=BB=B6=E5=8F=8A=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=AA=8C=E8=AF=81=EF=BC=88=E4=BB=BB=E5=8A=A1=2089-92?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增核心组件(任务 89-90): 1. HandlerRegistry.java - Handler 策略注册表 - 自动注册所有 SyncEventHandler 和 BatchSyncEventHandler - 根据 eventType 路由到对应 Handler - 提供 getIncrementalHandler/getBatchHandler 方法 - 支持 hasIncrementalHandler/hasBatchHandler 检查 2. DatabusClientConsumer.java - 统一消费者 - 监听 databus-sync-{clientCode} Topic(简化版) - 根据消息字段判断增量/批量消息 - 调用 HandlerRegistry 路由到具体 Handler - 支持全量同步生命周期回调(onFullSyncStart/onFullSyncComplete) 已存在接口(任务 91-92): 1. SyncEventHandler.java - 增量同步 Handler 接口 2. BatchSyncEventHandler.java - 全量同步 Handler 接口 架构设计: - 策略模式:通过 HandlerRegistry 动态路由 - Topic 简化:databus-sync-{clientCode}(所有事件共用) - 消息路由:通过 eventType 字段区分事件类型 - 条件装配:@ConditionalOnProperty 灵活启用/禁用 编译结果:✅ BUILD SUCCESS(30个源文件) Ref: docs/databus/implementation-checklist.md 任务 89-92 --- .../core/consumer/DatabusClientConsumer.java | 157 ++++++++++++++++++ .../client/core/registry/HandlerRegistry.java | 136 +++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/registry/HandlerRegistry.java diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java new file mode 100644 index 00000000..087dbf63 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java @@ -0,0 +1,157 @@ +package com.zt.plat.framework.databus.client.core.consumer; + +import com.alibaba.fastjson2.JSON; +import com.zt.plat.framework.databus.client.core.registry.HandlerRegistry; +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.api.message.DatabusBatchMessage; +import com.zt.plat.module.databus.api.message.DatabusMessage; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +/** + * DataBus 客户端统一消费者 + *

+ * 架构设计: + * 1. 监听客户端专属 Topic: databus-sync-{clientCode} + * 2. 消息体包含 eventType 字段,用于路由到具体 Handler + * 3. 根据 eventType 从 HandlerRegistry 获取对应 Handler + * 4. 区分增量消息和批量消息,调用不同的 Handler + *

+ * Topic 简化前后对比: + * - 旧格式:databus-sync-system-dept-create-branch-001(每个事件一个 Topic) + * - 新格式:databus-sync-branch-001(所有事件共用一个 Topic) + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +@RocketMQMessageListener( + topic = "${zt.databus.sync.client.mq.topic:databus-sync}-${zt.databus.sync.client.client-code}", + consumerGroup = "${zt.databus.sync.client.mq.consumer-group:databus-client-consumer}-${zt.databus.sync.client.client-code}" +) +public class DatabusClientConsumer implements RocketMQListener { + + @Resource + private HandlerRegistry handlerRegistry; + + @Override + public void onMessage(String body) { + log.debug("[DatabusClient] 收到消息, body={}", body); + + try { + // 1. 解析消息获取 eventType + DatabusEventType eventType = parseEventType(body); + if (eventType == null) { + log.error("[DatabusClient] 无法解析 eventType, body={}", body); + return; + } + + log.info("[DatabusClient] 收到消息, eventType={}", eventType); + + // 2. 根据 eventType 判断消息类型并分发 + if (isBatchMessage(body)) { + // 批量消息(全量同步) + handleBatchMessage(body, eventType); + } else { + // 增量消息 + handleIncrementalMessage(body, eventType); + } + + } catch (Exception e) { + log.error("[DatabusClient] 消息处理失败, body={}", body, e); + throw e; // 抛出异常触发重试 + } + } + + /** + * 处理批量消息(全量同步) + */ + @SuppressWarnings("unchecked") + private void handleBatchMessage(String body, DatabusEventType eventType) { + // 1. 获取 BatchHandler + BatchSyncEventHandler handler = handlerRegistry.getBatchHandler(eventType); + if (handler == null) { + log.warn("[DatabusClient] 未找到全量Handler, eventType={}", eventType); + return; + } + + // 2. 解析批量消息 + DatabusBatchMessage message = JSON.parseObject(body, DatabusBatchMessage.class); + + // 3. 全量同步开始回调(第一批) + if (message.getBatchNo() == 1) { + handler.onFullSyncStart(message); + } + + // 4. 处理批次数据 + handler.handleBatch(message); + + // 5. 全量同步完成回调(最后一批) + if (message.getBatchNo().equals(message.getTotalBatch())) { + handler.onFullSyncComplete(message); + } + + log.info("[DatabusClient] 批量消息处理完成, eventType={}, taskId={}, batchNo={}/{}", + eventType, message.getTaskId(), message.getBatchNo(), message.getTotalBatch()); + } + + /** + * 处理增量消息 + */ + @SuppressWarnings("unchecked") + private void handleIncrementalMessage(String body, DatabusEventType eventType) { + // 1. 获取 SyncEventHandler + SyncEventHandler handler = handlerRegistry.getIncrementalHandler(eventType); + if (handler == null) { + log.warn("[DatabusClient] 未找到增量Handler, eventType={}", eventType); + return; + } + + // 2. 解析增量消息 + DatabusMessage message = JSON.parseObject(body, DatabusMessage.class); + + // 3. 处理消息 + handler.handle(message); + + log.info("[DatabusClient] 增量消息处理完成, eventType={}, messageId={}", + eventType, message.getMessageId()); + } + + /** + * 解析 eventType + */ + private DatabusEventType parseEventType(String body) { + try { + // 先尝试从 JSON 中提取 eventType 字段 + String eventTypeStr = JSON.parseObject(body).getString("eventType"); + if (eventTypeStr != null) { + return DatabusEventType.valueOf(eventTypeStr); + } + } catch (Exception e) { + log.error("[DatabusClient] 解析 eventType 失败", e); + } + return null; + } + + /** + * 判断是否为批量消息 + */ + private boolean isBatchMessage(String body) { + try { + // 批量消息包含 taskId, batchNo, totalBatch 字段 + var json = JSON.parseObject(body); + return json.containsKey("taskId") + && json.containsKey("batchNo") + && json.containsKey("totalBatch"); + } catch (Exception e) { + return false; + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/registry/HandlerRegistry.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/registry/HandlerRegistry.java new file mode 100644 index 00000000..a1baa90d --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/registry/HandlerRegistry.java @@ -0,0 +1,136 @@ +package com.zt.plat.framework.databus.client.core.registry; + +import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler; +import com.zt.plat.framework.databus.client.handler.SyncEventHandler; +import com.zt.plat.module.databus.enums.DatabusEventType; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Handler 策略注册表 + *

+ * 负责管理所有 Handler 实例,根�� eventType 路由到对应的 Handler + *

+ * 架构设计: + * - 增量同步:使用 SyncEventHandler 处理单条消息 + * - 全量同步:使用 BatchSyncEventHandler 处理批量消息 + * + * @author ZT + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") +public class HandlerRegistry { + + /** + * 增量同步 Handler 映射表 + *

+ * Key: DatabusEventType(事件类型) + * Value: SyncEventHandler(增量同步处理器) + */ + private final Map> incrementalHandlers = new HashMap<>(); + + /** + * 全量同步 Handler 映射表 + *

+ * Key: DatabusEventType(事件类型) + * Value: BatchSyncEventHandler(批量同步处理器) + */ + private final Map> batchHandlers = new HashMap<>(); + + /** + * 自动注入所有 SyncEventHandler Bean + */ + @Autowired(required = false) + private List> syncEventHandlers; + + /** + * 自动注入所有 BatchSyncEventHandler Bean + */ + @Autowired(required = false) + private List> batchSyncEventHandlers; + + /** + * 初始化注册表 + *

+ * 在 Bean 创建后自动调用,注册所有 Handler + */ + @PostConstruct + public void init() { + log.info("[HandlerRegistry] 开始初始化 Handler 注册表..."); + + // 注册增量同步 Handler + if (syncEventHandlers != null) { + for (SyncEventHandler handler : syncEventHandlers) { + DatabusEventType eventType = handler.getSupportedEventType(); + incrementalHandlers.put(eventType, handler); + log.info("[HandlerRegistry] 注册增量Handler: {} -> {}", + eventType, handler.getClass().getSimpleName()); + } + } + + // 注册全量同步 Handler + if (batchSyncEventHandlers != null) { + for (BatchSyncEventHandler handler : batchSyncEventHandlers) { + DatabusEventType eventType = handler.getSupportedEventType(); + batchHandlers.put(eventType, handler); + log.info("[HandlerRegistry] 注册全量Handler: {} -> {}", + eventType, handler.getClass().getSimpleName()); + } + } + + log.info("[HandlerRegistry] 初始化完成, 增量Handler={}个, 全量Handler={}个", + incrementalHandlers.size(), batchHandlers.size()); + } + + /** + * 获取增量同步 Handler + * + * @param eventType 事件类型 + * @param 数据类型 + * @return 对应的 Handler,不存在则返回 null + */ + @SuppressWarnings("unchecked") + public SyncEventHandler getIncrementalHandler(DatabusEventType eventType) { + return (SyncEventHandler) incrementalHandlers.get(eventType); + } + + /** + * 获取全量同步 Handler + * + * @param eventType 事件类型 + * @param 数据类型 + * @return 对应的 Handler,不存在则返回 null + */ + @SuppressWarnings("unchecked") + public BatchSyncEventHandler getBatchHandler(DatabusEventType eventType) { + return (BatchSyncEventHandler) batchHandlers.get(eventType); + } + + /** + * 检查是否存在增量同步 Handler + * + * @param eventType 事件类型 + * @return 是否存在 + */ + public boolean hasIncrementalHandler(DatabusEventType eventType) { + return incrementalHandlers.containsKey(eventType); + } + + /** + * 检查是否存在全量同步 Handler + * + * @param eventType 事件类型 + * @return 是否存在 + */ + public boolean hasBatchHandler(DatabusEventType eventType) { + return batchHandlers.containsKey(eventType); + } +} From 6ac4a356cd810dbc2c204081b6e6a21005b847a3 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Wed, 3 Dec 2025 11:10:57 +0800 Subject: [PATCH 06/10] =?UTF-8?q?fix(databus):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=92=8C=E9=98=B2=E6=AD=A2=E6=B6=88=E6=81=AF=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复消息格式不匹配问题 - 增量消息:兼容 SyncMessage 格式,从 dataSnapshot 字段反序列化数据 - 批量消息:添加 getDataType() 方法获取泛型类型,正确转换 JSONObject 2. 防止消息循环 - 添加 zt.databus.change.producer.enabled 配置项 - 客户端禁用变更消息发送,避免 客户端写入 → 发送变更 → 循环 3. 修复 Feign 客户端注入 - 在 RpcConfiguration 中添加 DeptApi、PostApi - 确保客户端能通过 Feign 调用本地 system-server API 相关文件: - DatabusClientConsumer.java: 修复消息解析逻辑 - BatchSyncEventHandler.java: 添加 getDataType() 方法 - DatabusChangeProducer.java: 添加 enabled 开关 - RpcConfiguration.java: 启用 DeptApi/PostApi Feign 客户端 Ref: 修复 ClassCastException 和消息循环问题 --- pom.xml | 2 +- zt-framework/pom.xml | 3 +- .../core/consumer/DatabusClientConsumer.java | 103 +++++++++++++++--- .../client/handler/BatchSyncEventHandler.java | 28 +++++ .../pom.xml | 7 ++ .../consumer/DatabusDeptChangeConsumer.java | 6 +- .../consumer/DatabusPostChangeConsumer.java | 5 +- .../consumer/DatabusUserChangeConsumer.java | 5 +- .../admin/DatabusSyncClientController.java | 9 ++ .../admin/DatabusSyncEventController.java | 9 ++ .../DatabusSyncStatisticsController.java | 38 +++++++ .../DatabusSyncSubscriptionController.java | 18 +++ .../DatabusSyncClientBatchStatusReqVO.java | 26 +++++ .../DatabusSyncEventBatchStatusReqVO.java | 26 +++++ .../DatabusSyncStatisticsRespVO.java | 38 +++++++ ...tabusSyncSubscriptionBatchStatusReqVO.java | 26 +++++ .../DatabusIncrementalSyncServiceImpl.java | 20 +++- .../mapper/DatabusSyncSubscriptionMapper.java | 9 ++ .../provider/DeptDataFeignProvider.java | 2 +- .../provider/PostDataFeignProvider.java | 2 +- .../provider/UserDataFeignProvider.java | 2 +- .../service/DatabusSyncClientService.java | 8 ++ .../service/DatabusSyncEventService.java | 8 ++ .../service/DatabusSyncStatisticsService.java | 18 +++ .../DatabusSyncSubscriptionService.java | 16 +++ .../impl/DatabusFullSyncServiceImpl.java | 4 +- .../impl/DatabusSyncClientServiceImpl.java | 13 +++ .../impl/DatabusSyncEventServiceImpl.java | 13 +++ .../DatabusSyncStatisticsServiceImpl.java | 95 ++++++++++++++++ .../DatabusSyncSubscriptionServiceImpl.java | 18 +++ .../databus/enums/DatabusEventType.java | 32 +++++- .../rpc/config/RpcConfiguration.java | 12 +- .../src/main/resources/application-dev.yml | 29 ++++- .../src/main/resources/application-local.yml | 7 ++ .../src/main/resources/application.yml | 7 +- .../databus/DatabusChangeProducer.java | 28 +++++ .../src/main/resources/application-dev.yaml | 8 +- 37 files changed, 659 insertions(+), 41 deletions(-) rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/consumer/DatabusDeptChangeConsumer.java (93%) rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/consumer/DatabusPostChangeConsumer.java (91%) rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/consumer/DatabusUserChangeConsumer.java (92%) create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncStatisticsController.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientBatchStatusReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventBatchStatusReqVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/statistics/DatabusSyncStatisticsRespVO.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionBatchStatusReqVO.java rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/provider/DeptDataFeignProvider.java (98%) rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/provider/PostDataFeignProvider.java (97%) rename {zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus => zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server}/provider/UserDataFeignProvider.java (98%) create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncStatisticsService.java create mode 100644 zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncStatisticsServiceImpl.java diff --git a/pom.xml b/pom.xml index 8c4d3911..35973e7e 100644 --- a/pom.xml +++ b/pom.xml @@ -235,7 +235,7 @@ dev 172.16.46.63:30848 - dev + hwc DEFAULT_GROUP nacos P@ssword25 diff --git a/zt-framework/pom.xml b/zt-framework/pom.xml index f60ab308..241a211e 100644 --- a/zt-framework/pom.xml +++ b/zt-framework/pom.xml @@ -17,7 +17,8 @@ zt-spring-boot-starter-web zt-spring-boot-starter-security zt-spring-boot-starter-websocket - + zt-spring-boot-starter-databus-server + zt-spring-boot-starter-databus-client zt-spring-boot-starter-monitor zt-spring-boot-starter-protection diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java index 087dbf63..4a7c5cdb 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java @@ -56,7 +56,7 @@ public class DatabusClientConsumer implements RocketMQListener { log.info("[DatabusClient] 收到消息, eventType={}", eventType); // 2. 根据 eventType 判断消息类型并分发 - if (isBatchMessage(body)) { + if (isBatchMessage(eventType)) { // 批量消息(全量同步) handleBatchMessage(body, eventType); } else { @@ -82,18 +82,60 @@ public class DatabusClientConsumer implements RocketMQListener { return; } - // 2. 解析批量消息 - DatabusBatchMessage message = JSON.parseObject(body, DatabusBatchMessage.class); + // 2. 获取数据类型 + Class dataType = handler.getDataType(); - // 3. 全量同步开始回调(第一批) + // 3. 解析批量消息(兼容服务端 BatchSyncMessage 格式) + var json = JSON.parseObject(body); + + // 兼容处理:服务端使用 fullTaskId,转换为 taskId + String taskId = json.getString("taskId"); + if (taskId == null) { + taskId = String.valueOf(json.getLong("fullTaskId")); + } + + DatabusBatchMessage message = new DatabusBatchMessage<>(); + message.setMessageId(json.getString("messageId")); + message.setTaskId(taskId); + message.setEventType(eventType); + message.setBatchNo(json.getInteger("batchNo")); + message.setTotalBatch(json.getInteger("totalBatch")); + message.setCount(json.getInteger("count")); + message.setTotalCount(json.getInteger("totalCount") != null ? json.getInteger("totalCount") : 0); + message.setIsLastBatch(json.getBoolean("isLastBatch")); + message.setTenantId(json.getLong("tenantId")); + + // 解析 dataList(服务端是 SyncDataItem 列表,需要提取 data 字段并转换为具体类型) + var dataListJson = json.getJSONArray("dataList"); + if (dataListJson != null) { + java.util.List dataList = new java.util.ArrayList<>(); + for (int i = 0; i < dataListJson.size(); i++) { + var item = dataListJson.getJSONObject(i); + // 服务端 SyncDataItem 结构:{action, uid, data} + // data 字段是 JSON 字符串,需要解析成具体类型 + String dataStr = item.getString("data"); + if (dataStr != null) { + // 使用 handler.getDataType() 反序列化成具体类型 + Object data = JSON.parseObject(dataStr, dataType); + dataList.add(data); + } else { + // 如果没有 data 字段,可能是直接的数据对象,也需要转换类型 + Object data = JSON.parseObject(item.toJSONString(), dataType); + dataList.add(data); + } + } + message.setDataList(dataList); + } + + // 4. 全量同步开始回调(第一批) if (message.getBatchNo() == 1) { handler.onFullSyncStart(message); } - // 4. 处理批次数据 + // 5. 处理批次数据 handler.handleBatch(message); - // 5. 全量同步完成回调(最后一批) + // 6. 全量同步完成回调(最后一批) if (message.getBatchNo().equals(message.getTotalBatch())) { handler.onFullSyncComplete(message); } @@ -104,6 +146,15 @@ public class DatabusClientConsumer implements RocketMQListener { /** * 处理增量消息 + *

+ * 兼容服务端 SyncMessage 格式: + * - syncId: 同步ID + * - eventRecordId: 事件记录ID + * - eventType: 事件类型 + * - eventAction: 事件动作 + * - dataSnapshot: 业务数据快照(JSON字符串) + * - dataVersion: 数据版本 + * - timestamp: 时间戳 */ @SuppressWarnings("unchecked") private void handleIncrementalMessage(String body, DatabusEventType eventType) { @@ -114,14 +165,36 @@ public class DatabusClientConsumer implements RocketMQListener { return; } - // 2. 解析增量消息 - DatabusMessage message = JSON.parseObject(body, DatabusMessage.class); + // 2. 解析增量消息(兼容服务端 SyncMessage 格式) + var json = JSON.parseObject(body); + + // 从 dataSnapshot 字段解析业务数据 + String dataSnapshot = json.getString("dataSnapshot"); + Object data = null; + Long dataId = null; + if (dataSnapshot != null && !dataSnapshot.isEmpty()) { + // dataSnapshot 是 JSON 字符串,需要解析成具体类型 + var dataJson = JSON.parseObject(dataSnapshot); + // 获取数据类型并反序列化 + Class dataType = handler.getDataType(); + data = JSON.parseObject(dataSnapshot, dataType); + // 提取 dataId + dataId = dataJson.getLong("id"); + } + + // 构建 DatabusMessage + DatabusMessage message = new DatabusMessage<>(); + message.setMessageId(json.getString("syncId")); + message.setEventType(eventType); + message.setDataId(dataId); + message.setData(data); + message.setTenantId(json.getLong("tenantId")); // 3. 处理消息 handler.handle(message); - log.info("[DatabusClient] 增量消息处理完成, eventType={}, messageId={}", - eventType, message.getMessageId()); + log.info("[DatabusClient] 增量消息处理完成, eventType={}, syncId={}, dataId={}", + eventType, message.getMessageId(), dataId); } /** @@ -143,13 +216,11 @@ public class DatabusClientConsumer implements RocketMQListener { /** * 判断是否为批量消息 */ - private boolean isBatchMessage(String body) { + private boolean isBatchMessage(DatabusEventType eventType) { try { - // 批量消息包含 taskId, batchNo, totalBatch 字段 - var json = JSON.parseObject(body); - return json.containsKey("taskId") - && json.containsKey("batchNo") - && json.containsKey("totalBatch"); + // 批量消息包含 batchNo, totalBatch 字段 + // 服务端使用 fullTaskId,客户端 API 使用 taskId,兼容两种格式 + return eventType.getAction().equals("full"); } catch (Exception e) { return false; } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java index 4ab5a573..83c21f69 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/BatchSyncEventHandler.java @@ -3,6 +3,9 @@ package com.zt.plat.framework.databus.client.handler; import com.zt.plat.module.databus.api.message.DatabusBatchMessage; import com.zt.plat.module.databus.enums.DatabusEventType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + /** * 批量同步事件处理器接口 *

@@ -48,4 +51,29 @@ public interface BatchSyncEventHandler { // 默认空实现,子类可覆盖 } + /** + * 获取数据类型 + *

+ * 默认通过反射获取泛型类型参数,子类可以覆盖此方法提供具体类型 + * + * @return 数据类型的 Class 对象 + */ + @SuppressWarnings("unchecked") + default Class getDataType() { + Type[] genericInterfaces = this.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericInterface; + if (parameterizedType.getRawType().equals(BatchSyncEventHandler.class)) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length > 0 && typeArguments[0] instanceof Class) { + return (Class) typeArguments[0]; + } + } + } + } + // 如果无法获取泛型类型,返回 Object.class + return (Class) Object.class; + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/pom.xml b/zt-framework/zt-spring-boot-starter-databus-server/pom.xml index 59150712..88c2555d 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/pom.xml +++ b/zt-framework/zt-spring-boot-starter-databus-server/pom.xml @@ -27,6 +27,13 @@ ${revision} + + + com.zt.plat + zt-module-system-api + ${revision} + + cn.hutool hutool-all diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusDeptChangeConsumer.java similarity index 93% rename from zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java rename to zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusDeptChangeConsumer.java index e3794b25..474d8eb2 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/mq/consumer/DatabusDeptChangeConsumer.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusDeptChangeConsumer.java @@ -1,4 +1,4 @@ -package com.zt.plat.module.databus.mq.consumer; +package com.zt.plat.framework.databus.server.consumer; import com.fasterxml.jackson.databind.ObjectMapper; import com.zt.plat.framework.databus.server.core.event.DatabusEvent; @@ -61,8 +61,8 @@ public class DatabusDeptChangeConsumer implements RocketMQListener batchUpdateClientStatus(@Valid @RequestBody DatabusSyncClientBatchStatusReqVO reqVO) { + clientService.batchUpdateClientStatus(reqVO.getIds(), reqVO.getEnabled()); + return success(true); + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java index c74227d2..6e4f4a64 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncEventController.java @@ -2,6 +2,7 @@ package com.zt.plat.framework.databus.server.controller.admin; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventBatchStatusReqVO; import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO; import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventRespVO; import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO; @@ -89,4 +90,12 @@ public class DatabusSyncEventController { return success(true); } + @PutMapping("/batch-status") + @Operation(summary = "批量修改事件启用状态") + @PreAuthorize("@ss.hasPermission('databus:sync:event:update')") + public CommonResult batchUpdateEventStatus(@Valid @RequestBody DatabusSyncEventBatchStatusReqVO reqVO) { + eventService.batchUpdateEventStatus(reqVO.getIds(), reqVO.getEnabled()); + return success(true); + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncStatisticsController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncStatisticsController.java new file mode 100644 index 00000000..fb2f0a7d --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncStatisticsController.java @@ -0,0 +1,38 @@ +package com.zt.plat.framework.databus.server.controller.admin; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.statistics.DatabusSyncStatisticsRespVO; +import com.zt.plat.framework.databus.server.service.DatabusSyncStatisticsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * DataBus 同步统计 Controller + * + * @author ZT + */ +@Tag(name = "管理后台 - DataBus 同步统计") +@RestController +@RequestMapping("/databus/sync") +@Validated +public class DatabusSyncStatisticsController { + + @Resource + private DatabusSyncStatisticsService statisticsService; + + @GetMapping("/statistics") + @Operation(summary = "获取同步统计数据") + @PreAuthorize("@ss.hasPermission('databus:sync:query')") + public CommonResult getStatistics() { + DatabusSyncStatisticsRespVO statistics = statisticsService.getStatistics(); + return success(statistics); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java index 737f138f..7fd88490 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/DatabusSyncSubscriptionController.java @@ -2,6 +2,7 @@ package com.zt.plat.framework.databus.server.controller.admin; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionBatchStatusReqVO; import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionRespVO; import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; @@ -97,4 +98,21 @@ public class DatabusSyncSubscriptionController { return success(true); } + @GetMapping("/list-by-client") + @Operation(summary = "根据客户端ID获取订阅列表") + @Parameter(name = "clientId", description = "客户端ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')") + public CommonResult> getSubscriptionListByClient(@RequestParam("clientId") Long clientId) { + java.util.List list = subscriptionService.getSubscriptionListByClient(clientId); + return success(DatabusSyncSubscriptionConvert.INSTANCE.convertList(list)); + } + + @PutMapping("/batch-status") + @Operation(summary = "批量修改订阅启用状态") + @PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')") + public CommonResult batchUpdateSubscriptionStatus(@Valid @RequestBody DatabusSyncSubscriptionBatchStatusReqVO reqVO) { + subscriptionService.batchUpdateSubscriptionStatus(reqVO.getIds(), reqVO.getEnabled()); + return success(true); + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientBatchStatusReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientBatchStatusReqVO.java new file mode 100644 index 00000000..6789d281 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/client/DatabusSyncClientBatchStatusReqVO.java @@ -0,0 +1,26 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.client; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 客户端批量状态更新请求 VO + * + * @author ZT + */ +@Schema(description = "管理后台 - 客户端批量状态更新请求 VO") +@Data +public class DatabusSyncClientBatchStatusReqVO { + + @Schema(description = "客户端ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "客户端ID列表不能为空") + private List ids; + + @Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventBatchStatusReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventBatchStatusReqVO.java new file mode 100644 index 00000000..8a657b66 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/event/DatabusSyncEventBatchStatusReqVO.java @@ -0,0 +1,26 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.event; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 事件批量状态更新请求 VO + * + * @author ZT + */ +@Schema(description = "管理后台 - 事件批量状态更新请求 VO") +@Data +public class DatabusSyncEventBatchStatusReqVO { + + @Schema(description = "事件ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "事件ID列表不能为空") + private List ids; + + @Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/statistics/DatabusSyncStatisticsRespVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/statistics/DatabusSyncStatisticsRespVO.java new file mode 100644 index 00000000..94f94f65 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/statistics/DatabusSyncStatisticsRespVO.java @@ -0,0 +1,38 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.statistics; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * DataBus 同步统计响应 VO + * + * @author ZT + */ +@Schema(description = "管理后台 - DataBus 同步统计响应 VO") +@Data +public class DatabusSyncStatisticsRespVO { + + @Schema(description = "事件总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12") + private Integer totalEvents; + + @Schema(description = "客户端总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Integer totalClients; + + @Schema(description = "订阅总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "60") + private Integer totalSubscriptions; + + @Schema(description = "活跃订阅数", requiredMode = Schema.RequiredMode.REQUIRED, example = "58") + private Integer activeSubscriptions; + + @Schema(description = "今日推送总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1234") + private Integer todayPushCount; + + @Schema(description = "今日成功数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200") + private Integer todaySuccessCount; + + @Schema(description = "今日失败数", requiredMode = Schema.RequiredMode.REQUIRED, example = "34") + private Integer todayFailureCount; + + @Schema(description = "死信队列数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer deadLetterCount; +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionBatchStatusReqVO.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionBatchStatusReqVO.java new file mode 100644 index 00000000..3f3af776 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/controller/admin/vo/subscription/DatabusSyncSubscriptionBatchStatusReqVO.java @@ -0,0 +1,26 @@ +package com.zt.plat.framework.databus.server.controller.admin.vo.subscription; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 订阅批量状态更新请求 VO + * + * @author ZT + */ +@Schema(description = "管理后台 - 订阅批量状态更新请求 VO") +@Data +public class DatabusSyncSubscriptionBatchStatusReqVO { + + @Schema(description = "订阅ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]") + @NotEmpty(message = "订阅ID列表不能为空") + private List ids; + + @Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "启用状态不能为空") + private Integer enabled; +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java index 99766a04..8a537b87 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/sync/DatabusIncrementalSyncServiceImpl.java @@ -9,6 +9,7 @@ import com.zt.plat.framework.databus.server.dal.dataobject.*; import com.zt.plat.framework.databus.server.dal.mapper.*; import com.zt.plat.framework.databus.server.enums.SyncStatusEnum; import com.zt.plat.framework.databus.server.enums.TransportTypeEnum; +import com.zt.plat.framework.tenant.core.util.TenantUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -48,6 +49,14 @@ public class DatabusIncrementalSyncServiceImpl implements DatabusIncrementalSync log.info("[Databus增量同步] 开始处理事件, eventType={}, eventAction={}", event.getEventType(), event.getEventAction()); + // 使用 TenantUtils.executeIgnore 忽略租户隔离,因为事件定义、客户端、订阅关系是全局共享的 + TenantUtils.executeIgnore(() -> processEventInternal(event)); + } + + /** + * 内部处理事件逻辑(忽略租户隔离后执行) + */ + private void processEventInternal(DatabusEvent event) { // 1. 查询事件定义 DatabusSyncEventDO eventDef = eventMapper.selectByEventType(event.getEventType()); if (eventDef == null) { @@ -216,11 +225,11 @@ public class DatabusIncrementalSyncServiceImpl implements DatabusIncrementalSync /** * 构建客户端专属Topic - * 格式: {topicBase}-{module}-{entity}-{action}-{clientCode} - * 示例: databus-sync-system-org-create-company-a + * 格式: {topicBase}-{clientCode}(简化版,所有事件共用一个 Topic) + * 示例: databus-sync-branch-001 * * @param topicBase 基础Topic名称(如 databus-sync) - * @param eventType 事件类型(格式: system-org-create) + * @param eventType 事件类型(格式: system-org-create)- 已不再使用,保留参数兼容性 * @param clientCode 客户端编码 */ private String buildClientTopic(String topicBase, String eventType, String clientCode) { @@ -228,8 +237,9 @@ public class DatabusIncrementalSyncServiceImpl implements DatabusIncrementalSync if (topicBase == null || topicBase.isEmpty()) { topicBase = "databus-sync"; } - // eventType 格式已经是 system-org-create,直接拼接 - return String.format("%s-%s-%s", topicBase, eventType.toLowerCase(), clientCode); + // 简化 Topic 格式:databus-sync-{clientCode} + // 不再为每个事件创建独立 Topic,而是通过消息体中的 eventType 字段路由 + return String.format("%s-%s", topicBase, clientCode); } /** diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java index 4d30f98a..2882b11d 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/dal/mapper/DatabusSyncSubscriptionMapper.java @@ -38,4 +38,13 @@ public interface DatabusSyncSubscriptionMapper extends BaseMapperX selectListByClientId(Long clientId) { + return selectList(new LambdaQueryWrapperX() + .eq(DatabusSyncSubscriptionDO::getClientId, clientId) + .orderByDesc(DatabusSyncSubscriptionDO::getId)); + } + } diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/DeptDataFeignProvider.java similarity index 98% rename from zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java rename to zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/DeptDataFeignProvider.java index 1423be81..1afc83c4 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/DeptDataFeignProvider.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/DeptDataFeignProvider.java @@ -1,4 +1,4 @@ -package com.zt.plat.module.databus.provider; +package com.zt.plat.framework.databus.server.provider; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.databus.server.core.provider.DataProvider; diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/PostDataFeignProvider.java similarity index 97% rename from zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java rename to zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/PostDataFeignProvider.java index cfb4d8e3..23010f49 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/PostDataFeignProvider.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/PostDataFeignProvider.java @@ -1,4 +1,4 @@ -package com.zt.plat.module.databus.provider; +package com.zt.plat.framework.databus.server.provider; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.databus.server.core.provider.DataProvider; diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDataFeignProvider.java similarity index 98% rename from zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java rename to zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDataFeignProvider.java index b2c162e3..52304db0 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/provider/UserDataFeignProvider.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDataFeignProvider.java @@ -1,4 +1,4 @@ -package com.zt.plat.module.databus.provider; +package com.zt.plat.framework.databus.server.provider; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.databus.server.core.provider.DataProvider; diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java index 7e634a61..dc0c813a 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncClientService.java @@ -67,4 +67,12 @@ public interface DatabusSyncClientService { */ void updateClientStatus(Long id, Integer enabled); + /** + * 批量更新客户端启用状态 + * + * @param ids 编号列表 + * @param enabled 启用状态 + */ + void batchUpdateClientStatus(List ids, Integer enabled); + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java index fe7c2b3f..2de4779b 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncEventService.java @@ -67,4 +67,12 @@ public interface DatabusSyncEventService { */ void updateEventStatus(Long id, Integer enabled); + /** + * 批量更新事件启用状态 + * + * @param ids 编号列表 + * @param enabled 启用状态 + */ + void batchUpdateEventStatus(List ids, Integer enabled); + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncStatisticsService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncStatisticsService.java new file mode 100644 index 00000000..01140d7f --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncStatisticsService.java @@ -0,0 +1,18 @@ +package com.zt.plat.framework.databus.server.service; + +import com.zt.plat.framework.databus.server.controller.admin.vo.statistics.DatabusSyncStatisticsRespVO; + +/** + * DataBus 同步统计 Service 接口 + * + * @author ZT + */ +public interface DatabusSyncStatisticsService { + + /** + * 获取 DataBus 同步统计数据 + * + * @return 统计数据 + */ + DatabusSyncStatisticsRespVO getStatistics(); +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java index 88239aaf..adcddfe5 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/DatabusSyncSubscriptionService.java @@ -72,4 +72,20 @@ public interface DatabusSyncSubscriptionService { */ void triggerSync(Long id); + /** + * 根据客户端 ID 获取订阅列表 + * + * @param clientId 客户端 ID + * @return 订阅列表 + */ + java.util.List getSubscriptionListByClient(Long clientId); + + /** + * 批量更新订阅启用状态 + * + * @param ids 编号列表 + * @param enabled 启用状态 + */ + void batchUpdateSubscriptionStatus(java.util.List ids, Integer enabled); + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java index d92f8800..3d13c7f8 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusFullSyncServiceImpl.java @@ -240,8 +240,8 @@ public class DatabusFullSyncServiceImpl implements DatabusFullSyncService { BatchSyncMessage message) { try { if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) { - // 使用 getByTopicSuffix 支持数据库存储的 topic 格式(如 system-post-full) - DatabusEventType databusEventType = DatabusEventType.getByTopicSuffix(eventType); + // 使用 getByEventType 支持大写下划线格式(如 SYSTEM_POST_FULL) + DatabusEventType databusEventType = DatabusEventType.getByEventType(eventType); if (databusEventType == null) { log.error("[Databus] Unknown event type: {}", eventType); return false; diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java index 496463c9..482b2046 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncClientServiceImpl.java @@ -112,4 +112,17 @@ public class DatabusSyncClientServiceImpl implements DatabusSyncClientService { clientMapper.updateById(updateObj); } + @Override + @Transactional(rollbackFor = Exception.class) + public void batchUpdateClientStatus(List ids, Integer enabled) { + // 批量更新状态 + for (Long id : ids) { + DatabusSyncClientDO updateObj = new DatabusSyncClientDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + clientMapper.updateById(updateObj); + } + log.info("[batchUpdateClientStatus] 批量更新客户端状态完成, ids={}, enabled={}", ids, enabled); + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java index 355c5f77..0c736d15 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncEventServiceImpl.java @@ -112,4 +112,17 @@ public class DatabusSyncEventServiceImpl implements DatabusSyncEventService { eventMapper.updateById(updateObj); } + @Override + @Transactional(rollbackFor = Exception.class) + public void batchUpdateEventStatus(List ids, Integer enabled) { + // 批量更新状态 + for (Long id : ids) { + DatabusSyncEventDO updateObj = new DatabusSyncEventDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + eventMapper.updateById(updateObj); + } + log.info("[batchUpdateEventStatus] 批量更新事件状态完成, ids={}, enabled={}", ids, enabled); + } + } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncStatisticsServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncStatisticsServiceImpl.java new file mode 100644 index 00000000..6db697bc --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncStatisticsServiceImpl.java @@ -0,0 +1,95 @@ +package com.zt.plat.framework.databus.server.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zt.plat.framework.databus.server.controller.admin.vo.statistics.DatabusSyncStatisticsRespVO; +import com.zt.plat.framework.databus.server.dal.dataobject.*; +import com.zt.plat.framework.databus.server.dal.mapper.*; +import com.zt.plat.framework.databus.server.service.DatabusSyncStatisticsService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * DataBus 同步统计 Service 实现类 + * + * @author ZT + */ +@Slf4j +@Service +public class DatabusSyncStatisticsServiceImpl implements DatabusSyncStatisticsService { + + @Resource + private DatabusSyncEventMapper eventMapper; + + @Resource + private DatabusSyncClientMapper clientMapper; + + @Resource + private DatabusSyncSubscriptionMapper subscriptionMapper; + + @Resource + private DatabusSyncLogMapper logMapper; + + @Resource + private DatabusSyncDeadLetterMapper deadLetterMapper; + + @Override + public DatabusSyncStatisticsRespVO getStatistics() { + DatabusSyncStatisticsRespVO vo = new DatabusSyncStatisticsRespVO(); + + // 1. 事件总数 + vo.setTotalEvents(Math.toIntExact(eventMapper.selectCount(null))); + + // 2. 客户端总数 + vo.setTotalClients(Math.toIntExact(clientMapper.selectCount(null))); + + // 3. 订阅总数 + vo.setTotalSubscriptions(Math.toIntExact(subscriptionMapper.selectCount(null))); + + // 4. 活跃订阅数(enabled = 1) + vo.setActiveSubscriptions(Math.toIntExact(subscriptionMapper.selectCount( + new LambdaQueryWrapper() + .eq(DatabusSyncSubscriptionDO::getEnabled, 1) + ))); + + // 5. 今日推送统计(基于 create_time) + LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); + LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); + + // 今日推送总数 + vo.setTodayPushCount(Math.toIntExact(logMapper.selectCount( + new LambdaQueryWrapper() + .ge(DatabusSyncLogDO::getCreateTime, todayStart) + .le(DatabusSyncLogDO::getCreateTime, todayEnd) + ))); + + // 今日成功数(status = 'SUCCESS') + vo.setTodaySuccessCount(Math.toIntExact(logMapper.selectCount( + new LambdaQueryWrapper() + .ge(DatabusSyncLogDO::getCreateTime, todayStart) + .le(DatabusSyncLogDO::getCreateTime, todayEnd) + .eq(DatabusSyncLogDO::getStatus, "SUCCESS") + ))); + + // 今日失败数(status = 'FAILURE') + vo.setTodayFailureCount(Math.toIntExact(logMapper.selectCount( + new LambdaQueryWrapper() + .ge(DatabusSyncLogDO::getCreateTime, todayStart) + .le(DatabusSyncLogDO::getCreateTime, todayEnd) + .eq(DatabusSyncLogDO::getStatus, "FAILURE") + ))); + + // 6. 死信队列数量(handled = 0) + vo.setDeadLetterCount(Math.toIntExact(deadLetterMapper.selectCount( + new LambdaQueryWrapper() + .eq(DatabusSyncDeadLetterDO::getHandled, 0) + ))); + + log.info("[DatabusSyncStatistics] 统计数据获取成功: {}", vo); + return vo; + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java index 20952417..6feb607c 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java @@ -126,4 +126,22 @@ public class DatabusSyncSubscriptionServiceImpl implements DatabusSyncSubscripti log.info("[triggerSync] 手动触发同步,订阅ID: {}", id); } + @Override + public java.util.List getSubscriptionListByClient(Long clientId) { + return subscriptionMapper.selectListByClientId(clientId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchUpdateSubscriptionStatus(java.util.List ids, Integer enabled) { + // 批量更新状态 + for (Long id : ids) { + DatabusSyncSubscriptionDO updateObj = new DatabusSyncSubscriptionDO(); + updateObj.setId(id); + updateObj.setEnabled(enabled); + subscriptionMapper.updateById(updateObj); + } + log.info("[batchUpdateSubscriptionStatus] 批量更新订阅状态完成, ids={}, enabled={}", ids, enabled); + } + } diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java index da726cd3..bb853bfe 100644 --- a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/enums/DatabusEventType.java @@ -238,10 +238,13 @@ public enum DatabusEventType { /** * 获取完整Topic名称(服务端转发用) - * 格式: {topicBase}-{module}-{entity}-{action}-{clientCode} + * 格式: {topicBase}-{clientCode}(简化版,所有事件共用一个 Topic) + * 示例: databus-sync-branch-001 + * + * 注意:不再为每个事件创建独立 Topic,而是通过消息体中的 eventType 字段路由 */ public String getTopic(String topicBase, String clientCode) { - return String.format("%s-%s-%s-%s-%s", topicBase, module, entity, action, clientCode); + return String.format("%s-%s", topicBase, clientCode); } /** @@ -270,6 +273,31 @@ public enum DatabusEventType { return null; } + /** + * 根据事件类型字符串获取枚举(支持大写下划线格式) + * 例如:SYSTEM_POST_FULL → DatabusEventType.SYSTEM_POST_FULL + * + * @param eventType 事件类型字符串(格式: MODULE_ENTITY_ACTION) + * @return 枚举值,未找到返回null + */ + public static DatabusEventType getByEventType(String eventType) { + if (eventType == null) { + return null; + } + // 先尝试直接匹配枚举名称 + try { + return DatabusEventType.valueOf(eventType.toUpperCase()); + } catch (IllegalArgumentException e) { + // 如果失败,尝试转换格式后匹配(如 system-post-full → SYSTEM_POST_FULL) + String normalized = eventType.toUpperCase().replace("-", "_"); + try { + return DatabusEventType.valueOf(normalized); + } catch (IllegalArgumentException ex) { + return null; + } + } + } + /** * 根据模块、实体、操作获取枚举 * diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java index 7e83f105..5d974e23 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java @@ -1,6 +1,9 @@ package com.zt.plat.module.databus.framework.rpc.config; import com.zt.plat.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; +import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi; +import com.zt.plat.module.databus.api.provider.DatabusPostProviderApi; +import com.zt.plat.module.databus.api.provider.DatabusUserProviderApi; import com.zt.plat.module.system.api.user.AdminUserApi; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; @@ -9,6 +12,13 @@ import org.springframework.context.annotation.Configuration; * Databus 模块的 RPC 配置,开启所需的 Feign 客户端。 */ @Configuration(value = "databusRpcConfiguration", proxyBeanMethods = false) -@EnableFeignClients(clients = {AdminUserApi.class, OAuth2TokenCommonApi.class}) +@EnableFeignClients(clients = { + AdminUserApi.class, + OAuth2TokenCommonApi.class, + // DataBus 数据提供者 Feign 客户端 + DatabusDeptProviderApi.class, + DatabusUserProviderApi.class, + DatabusPostProviderApi.class +}) public class RpcConfiguration { } diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml index 30abeecd..e33d10c4 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml @@ -75,9 +75,16 @@ management: # 日志文件配置 logging: file: - name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 + name: D:/project/zhongtong/logs/${spring.application.name}.log # 日志文件名,全路径 +# RocketMQ 配置项 +rocketmq: + name-server: 172.16.240.64:9876 # RocketMQ Namesrv + producer: + group: databus-server-producer-group # 生产者组名 + send-message-timeout: 10000 # 发送消息超时时间,单位:毫秒 + justauth: enabled: true type: @@ -105,3 +112,23 @@ justauth: prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 +zt: + databus: + sync: + server: + enabled: true + clients: + - company-b # 配置订阅的客户端(与客户端的client-code一致) + mq: + enabled: true + name-server: 172.16.240.64:9876 # RocketMQ NameServer 地址 + topic-base: databus-sync + producer-group: databus-server-producer + send-msg-timeout: 10000 + retry: + max-attempts: 5 # 最大重试次数 + initial-delay: 1 # 初始重试延迟(秒) + multiplier: 2 # 重试延迟倍数 + batch: + default-size: 500 # 默认批量大小 + interval: 5 # 批量推送间隔(秒) \ No newline at end of file diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml index c45ce834..04b5c003 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml @@ -86,6 +86,13 @@ mybatis-plus: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +# RocketMQ 配置项 +rocketmq: + name-server: 172.16.46.63:9876 # RocketMQ Namesrv + producer: + group: databus-server-producer-group # 生产者组名 + send-message-timeout: 10000 # 发送消息超时时间,单位:毫秒 + # ZT配置项,设置当前项目所有自定义的配置 zt: env: # 多环境的配置项 diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml index c40ecc83..bcb4db90 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml @@ -50,7 +50,7 @@ spring: time-to-live: 1h # 设置过期时间为 1 小时 server: - port: 48100 + port: 48105 logging: file: @@ -130,6 +130,11 @@ zt: - /databus/api/portal/** ignore-tables: - databus_api_client_credential + # DataBus 数据同步服务端配置 + databus: + sync: + server: + enabled: true # 启用 DataBus 同步服务端 databus: gateway: diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java index 76c5b624..b8d4a995 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java @@ -8,6 +8,7 @@ import com.zt.plat.module.system.dal.dataobject.dept.PostDO; import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; @@ -16,6 +17,9 @@ import jakarta.annotation.Resource; * Databus 数据变更消息生产者 *

* 用于发送部门、用户、岗位变更消息到 MQ,供 databus-server 消费 + *

+ * 注意:客户端系统(分公司)应该禁用此功能,避免形成消息循环 + * 配置项:zt.databus.change.producer.enabled=false * * @author ZT */ @@ -26,6 +30,15 @@ public class DatabusChangeProducer { @Resource private RocketMQTemplate rocketMQTemplate; + /** + * 是否启用变更消息发送 + *

+ * 集团侧(数据源):设置为 true,发送变更消息 + * 分公司侧(客户端):设置为 false,禁用变更消息,避免循环 + */ + @Value("${zt.databus.change.producer.enabled:true}") + private boolean enabled; + // ==================== 部门变更消息 ==================== /** @@ -61,6 +74,11 @@ public class DatabusChangeProducer { } private void sendDeptChangeMessage(DatabusDeptChangeMessage message) { + if (!enabled) { + log.debug("[Databus] 变更消息发送已禁用, 跳过部门变更消息, action={}, deptId={}", + message.getAction(), message.getDeptId()); + return; + } try { rocketMQTemplate.asyncSend(DatabusDeptChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { @Override @@ -118,6 +136,11 @@ public class DatabusChangeProducer { } private void sendUserChangeMessage(DatabusUserChangeMessage message) { + if (!enabled) { + log.debug("[Databus] 变更消息发送已禁用, 跳过用户变更消息, action={}, userId={}", + message.getAction(), message.getUserId()); + return; + } try { rocketMQTemplate.asyncSend(DatabusUserChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { @Override @@ -171,6 +194,11 @@ public class DatabusChangeProducer { } private void sendPostChangeMessage(DatabusPostChangeMessage message) { + if (!enabled) { + log.debug("[Databus] 变更消息发送已禁用, 跳过岗位变更消息, action={}, postId={}", + message.getAction(), message.getPostId()); + return; + } try { rocketMQTemplate.asyncSend(DatabusPostChangeMessage.TOPIC, message, new org.apache.rocketmq.client.producer.SendCallback() { @Override diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml index 0d7755ef..9d9f0f8b 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml @@ -187,4 +187,10 @@ seata: undo: logTable: undo_log dataValidation: true - logSerialization: jackson \ No newline at end of file + logSerialization: jackson +zt: + databus: + # 变更消息生产者配置 + change: + producer: + enabled: false From e093157bb5d3296f74690011eede3a6771ada93f Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Wed, 3 Dec 2025 14:15:46 +0800 Subject: [PATCH 07/10] =?UTF-8?q?fix(databus):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E6=B6=88=E6=81=AF=E5=BC=80=E5=85=B3=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E4=B8=BA=20false=EF=BC=8C=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E4=BC=98=E5=85=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 DatabusChangeProducer.enabled 默认值从 true 改为 false - 避免未配置时导致消息循环,安全优先原则 - 集团侧(数据源)必须显式配置 enabled=true 才能发送变更消息 - 客户端(分公司)保持默认 false 或不配置,禁用变更消息 配置说明: - 默认值:false(安全) - 集团侧:application-dev.yaml 中显式设置 enabled=true - 客户端:不配置或设置 enabled=false Ref: 防止未配置导致项目异常 --- .../system/mq/producer/databus/DatabusChangeProducer.java | 7 ++++--- .../src/main/resources/application-dev.yaml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java index b8d4a995..9b00d400 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/databus/DatabusChangeProducer.java @@ -33,10 +33,11 @@ public class DatabusChangeProducer { /** * 是否启用变更消息发送 *

- * 集团侧(数据源):设置为 true,发送变更消息 - * 分公司侧(客户端):设置为 false,禁用变更消息,避免循环 + * 默认值:false(安全优先,避免未配置时导致消息循环) + * 集团侧(数据源):必须显式设置为 true,发送变更消息 + * 分公司侧(客户端):保持 false 或不配置,禁用变更消息,避免循环 */ - @Value("${zt.databus.change.producer.enabled:true}") + @Value("${zt.databus.change.producer.enabled:false}") private boolean enabled; // ==================== 部门变更消息 ==================== diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml index 9d9f0f8b..7ae49cb2 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml @@ -190,7 +190,7 @@ seata: logSerialization: jackson zt: databus: - # 变更消息生产者配置 + # 变更消息生产者配置(集团侧数据源必须启用) change: producer: - enabled: false + enabled: true # 集团侧启用变更消息发送 From ffc7d0247d7399cffbf08cea98f7ff57bd633541 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Wed, 3 Dec 2025 15:59:59 +0800 Subject: [PATCH 08/10] =?UTF-8?q?refactor(databus):=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E4=BF=AE=E5=A4=8D=E5=92=8CTODO=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复硬编码配置: - 修改日志路径为环境变量:${LOG_PATH:./logs}/${spring.application.name}.log - 修改 RocketMQ 地址为环境变量:${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} - 还原 Nacos namespace 从 hwc 到 dev 完成 TODO 功能: - TODO #1: 实现死信重试逻辑(重新投递消息到 MQ) - TODO #2: 实现日志重试逻辑(根据事件记录重新推送) - TODO #3: 实现全量同步触发(创建任务并异步执行) - TODO #4: 实现事件 ID 查询(通过 eventType 查询事件定义) 涉及文件: - pom.xml: 还原 Nacos namespace 到 dev - DatabusSyncDeadLetterServiceImpl: 实现死信重试 - DatabusSyncLogServiceImpl: 实现日志重试 - DatabusSyncSubscriptionServiceImpl: 实现全量同步触发 - DatabusEventPublisherImpl: 实现事件 ID 查询 - application-*.yml/yaml: 修复硬编码配置 Ref: 代码审查报告 --- pom.xml | 2 +- .../publisher/DatabusEventPublisherImpl.java | 19 +++++-- .../DatabusSyncDeadLetterServiceImpl.java | 41 +++++++++++--- .../impl/DatabusSyncLogServiceImpl.java | 56 ++++++++++++++++++- .../DatabusSyncSubscriptionServiceImpl.java | 39 ++++++++++++- .../src/main/resources/application-dev.yml | 6 +- .../src/main/resources/application-local.yml | 2 +- .../src/main/resources/application-dev.yaml | 4 +- .../src/main/resources/application-local.yaml | 2 +- 9 files changed, 146 insertions(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index 35973e7e..8c4d3911 100644 --- a/pom.xml +++ b/pom.xml @@ -235,7 +235,7 @@ dev 172.16.46.63:30848 - hwc + dev DEFAULT_GROUP nacos P@ssword25 diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java index 2ac2c33e..6ebc1be0 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/core/publisher/DatabusEventPublisherImpl.java @@ -2,7 +2,9 @@ package com.zt.plat.framework.databus.server.core.publisher; import cn.hutool.core.bean.BeanUtil; import com.zt.plat.framework.databus.server.core.event.DatabusEvent; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO; import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventMapper; import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional; public class DatabusEventPublisherImpl implements DatabusEventPublisher { private final DatabusSyncEventRecordMapper eventRecordMapper; + private final DatabusSyncEventMapper eventMapper; @Async @Override @@ -43,16 +46,24 @@ public class DatabusEventPublisherImpl implements DatabusEventPublisher { * 保存事件记录到流水表 */ private Long saveEventRecord(DatabusEvent event) { - // 查询事件定义ID(这里简化处理,实际应该注入EventMapper查询) - // TODO: 根据 eventType 查询 event_id + // 根据 eventType 查询事件定义 ID + DatabusSyncEventDO eventDO = eventMapper.selectByEventType(event.getEventType()); + Long eventId = (eventDO != null) ? eventDO.getId() : null; + if (eventId == null) { + log.warn("[Databus] 事件定义不存在, eventType={}, 仍然保存事件记录", event.getEventType()); + } + + // 构建事件记录对象 DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO(); BeanUtil.copyProperties(event, record); + record.setEventId(eventId); // 设置事件定义 ID + // 保存到流水表 eventRecordMapper.insert(record); - log.info("[Databus] 事件记录已保存, id={}, eventType={}, eventAction={}", - record.getId(), event.getEventType(), event.getEventAction()); + log.info("[Databus] 事件记录已保存, id={}, eventId={}, eventType={}, eventAction={}", + record.getId(), eventId, event.getEventType(), event.getEventAction()); return record.getId(); } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java index ee16880b..edb3e55f 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncDeadLetterServiceImpl.java @@ -1,7 +1,10 @@ package com.zt.plat.framework.databus.server.service.impl; +import com.alibaba.fastjson2.JSON; import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO; import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncDeadLetterMapper; import com.zt.plat.framework.databus.server.enums.DeadLetterStatusEnum; @@ -31,6 +34,9 @@ public class DatabusSyncDeadLetterServiceImpl implements DatabusSyncDeadLetterSe @Resource private DatabusSyncDeadLetterMapper deadLetterMapper; + @Resource + private MessagePusher messagePusher; + @Override public DatabusSyncDeadLetterDO getDeadLetter(Long id) { return deadLetterMapper.selectById(id); @@ -50,16 +56,33 @@ public class DatabusSyncDeadLetterServiceImpl implements DatabusSyncDeadLetterSe throw exception(DEAD_LETTER_NOT_EXISTS); } - // TODO: 实现重新投递逻辑,将消息重新发送到 MQ 或 HTTP - log.info("[reprocessDeadLetter] 重新投递死信消息,ID: {}, 同步ID: {}", id, deadLetter.getSyncId()); + // 重新投递逻辑:将消息重新发送到 MQ + log.info("[reprocessDeadLetter] 重新投递死信消息,ID: {}, 同步ID: {}, 客户端: {}", + id, deadLetter.getSyncId(), deadLetter.getClientCode()); - // 更新状态为已重新投递 - DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO(); - updateObj.setId(id); - updateObj.setStatus(DeadLetterStatusEnum.REDELIVERED.getStatus()); - updateObj.setHandled(1); - updateObj.setHandleTime(LocalDateTime.now()); - deadLetterMapper.updateById(updateObj); + try { + // 1. 解析消息内容 + SyncMessage message = JSON.parseObject(deadLetter.getMessageBody(), SyncMessage.class); + + // 2. 构建 Topic(格式:databus-sync-{clientCode}) + String topic = "databus-sync-" + deadLetter.getClientCode(); + + // 3. 重新推送到 MQ + String messageId = messagePusher.pushByMQ(topic, message); + log.info("[reprocessDeadLetter] 死信消息重新投递成功,ID: {}, MQ消息ID: {}", id, messageId); + + // 4. 更新状态为已重新投递 + DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO(); + updateObj.setId(id); + updateObj.setStatus(DeadLetterStatusEnum.REDELIVERED.getStatus()); + updateObj.setHandled(1); + updateObj.setHandleTime(LocalDateTime.now()); + deadLetterMapper.updateById(updateObj); + + } catch (Exception e) { + log.error("[reprocessDeadLetter] 死信消息重新投递失败,ID: {}", id, e); + throw new RuntimeException("死信消息重新投递失败: " + e.getMessage(), e); + } } @Override diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java index 64910c9a..a5e048f7 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncLogServiceImpl.java @@ -1,15 +1,23 @@ package com.zt.plat.framework.databus.server.service.impl; import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.date.LocalDateTimeUtils; import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO; +import com.zt.plat.framework.databus.server.core.message.SyncMessage; +import com.zt.plat.framework.databus.server.core.pusher.MessagePusher; +import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO; import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO; +import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper; import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncLogMapper; import com.zt.plat.framework.databus.server.service.DatabusSyncLogService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.time.LocalDateTime; + import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.PUSH_LOG_NOT_EXISTS; @@ -26,6 +34,12 @@ public class DatabusSyncLogServiceImpl implements DatabusSyncLogService { @Resource private DatabusSyncLogMapper pushLogMapper; + @Resource + private DatabusSyncEventRecordMapper eventRecordMapper; + + @Resource + private MessagePusher messagePusher; + @Override public DatabusSyncLogDO getPushLog(Long id) { return pushLogMapper.selectById(id); @@ -37,14 +51,52 @@ public class DatabusSyncLogServiceImpl implements DatabusSyncLogService { } @Override + @Transactional(rollbackFor = Exception.class) public void retryPush(Long id) { // 校验存在 DatabusSyncLogDO pushLog = pushLogMapper.selectById(id); if (pushLog == null) { throw exception(PUSH_LOG_NOT_EXISTS); } - // TODO: 实现重试逻辑,将日志重新入队或触发推送 - log.info("[retryPush] 重试推送,日志ID: {}, 同步ID: {}", id, pushLog.getSyncId()); + + // 重试推送逻辑:根据事件记录ID查询原始数据,重新推送 + log.info("[retryPush] 重试推送,日志ID: {}, 同步ID: {}, 客户端: {}", + id, pushLog.getSyncId(), pushLog.getClientCode()); + + try { + // 1. 查询原始事件记录 + DatabusSyncEventRecordDO eventRecord = eventRecordMapper.selectById(pushLog.getEventRecordId()); + if (eventRecord == null) { + log.error("[retryPush] 事件记录不存在,无法重试推送,eventRecordId: {}", pushLog.getEventRecordId()); + throw new RuntimeException("事件记录不存在,无法重试推送"); + } + + // 2. 构建同步消息 + SyncMessage message = new SyncMessage(); + message.setSyncId(pushLog.getSyncId()); + message.setEventRecordId(eventRecord.getId()); + message.setEventType(pushLog.getEventType()); + message.setEventAction(eventRecord.getEventAction()); + message.setDataSnapshot(eventRecord.getDataSnapshot()); + message.setDataVersion(eventRecord.getDataVersion()); + message.setTimestamp(System.currentTimeMillis()); + + // 3. 重新推送到 MQ + String messageId = messagePusher.pushByMQ(pushLog.getMqTopic(), message); + log.info("[retryPush] 日志重新推送成功,日志ID: {}, MQ消息ID: {}", id, messageId); + + // 4. 更新推送日志状态 + DatabusSyncLogDO updateObj = new DatabusSyncLogDO(); + updateObj.setId(id); + updateObj.setStatus(3); // 状态改为重试中 + updateObj.setRetryCount(pushLog.getRetryCount() + 1); + updateObj.setMqMsgId(messageId); + pushLogMapper.updateById(updateObj); + + } catch (Exception e) { + log.error("[retryPush] 日志重试推送失败,日志ID: {}", id, e); + throw new RuntimeException("日志重试推送失败: " + e.getMessage(), e); + } } } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java index 6feb607c..d19d2bba 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/service/impl/DatabusSyncSubscriptionServiceImpl.java @@ -4,11 +4,13 @@ import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO; import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO; import com.zt.plat.framework.databus.server.convert.DatabusSyncSubscriptionConvert; +import com.zt.plat.framework.databus.server.core.sync.DatabusFullSyncService; import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO; import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncSubscriptionMapper; import com.zt.plat.framework.databus.server.service.DatabusSyncSubscriptionService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -29,6 +31,9 @@ public class DatabusSyncSubscriptionServiceImpl implements DatabusSyncSubscripti @Resource private DatabusSyncSubscriptionMapper subscriptionMapper; + @Resource + private DatabusFullSyncService fullSyncService; + @Override @Transactional(rollbackFor = Exception.class) public Long createSubscription(DatabusSyncSubscriptionSaveReqVO createReqVO) { @@ -122,8 +127,38 @@ public class DatabusSyncSubscriptionServiceImpl implements DatabusSyncSubscripti public void triggerSync(Long id) { // 校验存在 validateSubscriptionExists(id); - // TODO: 发送消息到 MQ 或调用异步任务触发同步 - log.info("[triggerSync] 手动触发同步,订阅ID: {}", id); + + // 手动触发全量同步:创建全量同步任务并异步执行 + log.info("[triggerSync] 手动触发全量同步,订阅ID: {}", id); + + try { + // 1. 创建全量同步任务 + Long taskId = fullSyncService.createFullSyncTask(id, "手动触发全量同步"); + log.info("[triggerSync] 创建全量同步任务成功,订阅ID: {}, 任务ID: {}", id, taskId); + + // 2. 异步执行全量同步任务 + executeFullSyncAsync(taskId); + + } catch (Exception e) { + log.error("[triggerSync] 手动触发全量同步失败,订阅ID: {}", id, e); + throw new RuntimeException("触发全量同步失败: " + e.getMessage(), e); + } + } + + /** + * 异步执行全量同步任务 + * + * @param taskId 任务ID + */ + @Async + protected void executeFullSyncAsync(Long taskId) { + try { + log.info("[executeFullSyncAsync] 开始执行全量同步任务,任务ID: {}", taskId); + fullSyncService.executeFullSyncTask(taskId); + log.info("[executeFullSyncAsync] 全量同步任务执行完成,任务ID: {}", taskId); + } catch (Exception e) { + log.error("[executeFullSyncAsync] 全量同步任务执行失败,任务ID: {}", taskId, e); + } } @Override diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml index e33d10c4..892d171e 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml @@ -75,12 +75,12 @@ management: # 日志文件配置 logging: file: - name: D:/project/zhongtong/logs/${spring.application.name}.log # 日志文件名,全路径 + name: ${LOG_PATH:./logs}/${spring.application.name}.log # 日志文件名,使用环境变量或相对路径 # RocketMQ 配置项 rocketmq: - name-server: 172.16.240.64:9876 # RocketMQ Namesrv + name-server: ${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} # RocketMQ Namesrv,使用环境变量 producer: group: databus-server-producer-group # 生产者组名 send-message-timeout: 10000 # 发送消息超时时间,单位:毫秒 @@ -121,7 +121,7 @@ zt: - company-b # 配置订阅的客户端(与客户端的client-code一致) mq: enabled: true - name-server: 172.16.240.64:9876 # RocketMQ NameServer 地址 + name-server: ${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} # RocketMQ NameServer 地址,使用环境变量 topic-base: databus-sync producer-group: databus-server-producer send-msg-timeout: 10000 diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml index 04b5c003..cd47ac91 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml @@ -88,7 +88,7 @@ mybatis-plus: # RocketMQ 配置项 rocketmq: - name-server: 172.16.46.63:9876 # RocketMQ Namesrv + name-server: ${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} # RocketMQ Namesrv,使用环境变量 producer: group: databus-server-producer-group # 生产者组名 send-message-timeout: 10000 # 发送消息超时时间,单位:毫秒 diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml index 7ae49cb2..9634d5cf 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml @@ -59,7 +59,7 @@ spring: # rocketmq 配置项,对应 RocketMQProperties 配置类 rocketmq: - name-server: 172.16.46.63:30876 # RocketMQ Namesrv + name-server: ${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} # RocketMQ Namesrv,使用环境变量 spring: # RabbitMQ 配置项,对应 RabbitProperties 配置类 @@ -109,7 +109,7 @@ spring: # 日志文件配置 logging: file: - name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 + name: ${LOG_PATH:./logs}/${spring.application.name}.log # 日志文件名,使用环境变量或相对路径 --- #################### 微信公众号、小程序相关配置 #################### wx: diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml index 9bdd38e6..e22a45a1 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml @@ -60,7 +60,7 @@ spring: # rocketmq 配置项,对应 RocketMQProperties 配置类 rocketmq: - name-server: 172.16.46.63:30876 # RocketMQ Namesrv + name-server: ${ROCKETMQ_NAME_SERVER:172.16.46.63:30876} # RocketMQ Namesrv,使用环境变量 --- #################### 定时任务相关配置 #################### From 25978ad4b9532ec7a6193a346d4a66336c771271 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Thu, 4 Dec 2025 14:41:59 +0800 Subject: [PATCH 09/10] =?UTF-8?q?fix:=E8=BF=98=E5=8E=9Fdatabus=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zt-module-databus-server/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml b/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml index bcb4db90..5a113239 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml +++ b/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml @@ -50,7 +50,7 @@ spring: time-to-live: 1h # 设置过期时间为 1 小时 server: - port: 48105 + port: 48100 logging: file: From a0b51edd9b9c1e8663c149a513993a72865fd90c Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 4 Dec 2025 19:22:41 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E4=B8=8D=E6=A0=A1=E9=AA=8C=E9=95=BF=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/system/controller/admin/auth/vo/AuthLoginReqVO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index 9e0b014f..2e4e2813 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -28,7 +28,7 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO { @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") @NotEmpty(message = "密码不能为空") - @Length(min = 4, max = 16, message = "密码长度为 4-16 位") +// @Length(min = 4, max = 16, message = "密码长度为 4-16 位") private String password; // ========== 绑定社交登录时,需要传递如下参数 ==========