diff --git a/datai-scenes/datai-scene-salesforce/CHANGELOG.md b/datai-scenes/datai-scene-salesforce/CHANGELOG.md index 91a01172..b6ae9a15 100644 --- a/datai-scenes/datai-scene-salesforce/CHANGELOG.md +++ b/datai-scenes/datai-scene-salesforce/CHANGELOG.md @@ -3,6 +3,16 @@ ## [Unreleased] ### Added +- 元数据拉取核心功能(REQ-010-6) - 实现了完整的元数据拉取核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能 + - JobExecutionStatus 枚举类 - 作业执行状态枚举,包含 PENDING、PROCESSING、SUCCESS、FAILED、PARTIAL_SUCCESS、CANCELLED 六个值,使用 @EnumValue 注解映射数据库值 + - MetadataRetrieveController 控制器 - 元数据拉取控制器,提供 4 个 RESTful API 接口,支持手动触发拉取、查询拉取历史记录、查询拉取进度、取消拉取 + - RetrieveRequest DTO - 拉取请求,包含 taskId 和 orgConfigId 字段 + - RetrieveResponse DTO - 拉取响应,包含 jobId 和 message 字段 + - RetrieveProgressResponse DTO - 拉取进度响应,包含 jobId、status、progress、message 字段 + - IMetadataRetrieveService 服务接口 - 元数据拉取服务接口,定义 triggerRetrieve()、getRetrieveHistory()、getRetrieveProgress()、cancelRetrieve() 方法 + - MetadataRetrieveServiceImpl 服务实现 - 元数据拉取服务实现,使用 MetadataApiClient 调用 retrieve() 方法,使用 @Async 注解实现异步执行,使用 ConcurrentHashMap 存储正在运行的作业 + - RetrieveException 异常类 - 拉取异常,封装拉取异常 + - 单元测试 - MetadataRetrieveServiceImplTest - Metadata API客户端封装(REQ-010-5) - 实现了完整的Metadata API客户端封装功能,包括MetadataApiClient客户端类、retrieve()方法、deploy()方法、状态轮询机制、Zip文件处理、部署结果解析、错误处理机制、异步服务接口和实现、单元测试 - MetadataApiClient 客户端类 - Metadata API 客户端,使用 Salesforce WSC (Web Service Connector) 库,封装 Salesforce Metadata API 调用 - RetrieveRequest 类 - Retrieve 请求模型,包含 orgConfigId、packageNames、singlePackage、specificFiles、unpackaged、timeout 等字段 diff --git a/datai-scenes/datai-scene-salesforce/docs/changelog/20260119-metadata-retrieve-core.md b/datai-scenes/datai-scene-salesforce/docs/changelog/20260119-metadata-retrieve-core.md new file mode 100644 index 00000000..e09460fc --- /dev/null +++ b/datai-scenes/datai-scene-salesforce/docs/changelog/20260119-metadata-retrieve-core.md @@ -0,0 +1,217 @@ +# 变更记录 - 元数据拉取核心功能 + +## 变更概述 + +本次变更为元数据拉取核心功能提供了完整的实现,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能等。 + +## 变更时间 + +2026-01-19 + +## 变更类型 + +功能增强 + +## 影响范围 + +### 代码文件 + +#### 枚举类 + +- com.datai.salesforce.metadata.enums.JobExecutionStatus - 作业执行状态枚举 + +#### 控制器 + +- com.datai.salesforce.metadata.controller.MetadataRetrieveController - 元数据拉取控制器 + +#### DTO + +- com.datai.salesforce.metadata.dto.RetrieveRequest - 拉取请求 +- com.datai.salesforce.metadata.dto.RetrieveResponse - 拉取响应 +- com.datai.salesforce.metadata.dto.RetrieveProgressResponse - 拉取进度响应 + +#### 服务接口 + +- com.datai.salesforce.metadata.service.IMetadataRetrieveService - 元数据拉取服务接口 + +#### 服务实现 + +- com.datai.salesforce.metadata.service.impl.MetadataRetrieveServiceImpl - 元数据拉取服务实现 + +#### 异常类 + +- com.datai.salesforce.metadata.exception.RetrieveException - 拉取异常 + +#### 单元测试 + +- com.datai.salesforce.metadata.service.impl.MetadataRetrieveServiceImplTest - 元数据拉取服务单元测试 + +### 文档文件 + +- docs/requirements/REQ-010-6.md - 元数据拉取核心功能需求文档 +- docs/decisions/adr/0015-metadata-retrieve-core.md - 元数据拉取核心功能架构决策 +- docs/prompts/006-metadata-retrieve-core.md - 元数据拉取核心功能实现提示词 +- docs/sessions/20260119-metadata-retrieve-core.md - 元数据拉取核心功能会话记录 +- docs/changelog/20260119-metadata-retrieve-core.md - 元数据拉取核心功能变更记录 +- docs/retros/20260119-metadata-retrieve-core-retro.md - 元数据拉取核心功能复盘报告 + +## 变更内容 + +### 新增功能 + +1. 手动触发拉取功能 + - 使用 MetadataApiClient 调用 retrieve() 方法 + - 使用异步线程池执行拉取任务 + - 使用 RESTful API 设计接口 + - 支持选择任务 ID 和组织配置 + - 触发成功返回 Job ID + +2. 异步拉取执行 + - 使用 Spring 的 @Async 注解实现异步执行 + - 使用 ThreadPoolTaskExecutor 配置线程池 + - 使用 CompletableFuture 支持异步结果 + - 支持并发拉取 + - 拉取任务不阻塞系统响应 + +3. 状态监控 + - 使用状态机管理拉取状态 + - 使用轮询机制检查拉取状态 + - 使用枚举类定义拉取状态(Pending/Processing/Success/Failed/Partial_Success/Cancelled) + - 支持状态查询 + - 状态更新及时 + +4. 拉取历史记录 + - 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询 + - 使用分页插件实现分页查询 + - 使用条件查询支持多条件查询 + - 历史记录完整 + - 支持按任务 ID 和组织配置 ID 查询 + +5. 拉取进度查询 + - 使用轮询机制获取进度 + - 使用缓存提高查询性能 + - 使用百分比显示进度 + - 支持实时进度查询 + - 进度信息准确 + +6. 拉取取消功能 + - 使用 Future.cancel() 取消异步任务 + - 使用状态机管理取消状态 + - 使用异常处理机制处理取消异常 + - 支持取消正在进行的拉取任务 + - 取消后资源正确释放 + +### 功能改进 + +- 无 + +### Bug 修复 + +- 无 + +### 性能优化 + +- 无 + +### 安全改进 + +- 无 + +### 代码重构 + +- 无 + +## 兼容性说明 + +### 向后兼容性 + +本次变更为新增功能,不影响现有功能,完全向后兼容。 + +### 数据库变更 + +本次变更不涉及数据库结构变更,不需要数据库迁移。 + +### API 变更 + +本次变更新增了以下 API 接口: +- POST /api/metadata/retrieve - 手动触发拉取 +- GET /api/metadata/retrieve/history - 查询拉取历史记录 +- GET /api/metadata/retrieve/progress/{jobId} - 查询拉取进度 +- POST /api/metadata/retrieve/cancel/{jobId} - 取消拉取 + +### 配置变更 + +本次变更需要添加以下配置: +- 异步线程池配置 +- Redis 缓存配置 + +## 依赖变更 + +### 新增依赖 + +- 无 + +### 依赖升级 + +- 无 + +### 依赖移除 + +- 无 + +## 测试情况 + +### 单元测试 + +- MetadataRetrieveServiceImplTest - 通过 + +### 集成测试 + +- 待测试 + +### 性能测试 + +- 待测试 + +### 安全测试 + +- 待测试 + +## 已知问题 + +无 + +## 后续计划 + +1. 进行集成测试,确保功能正常 +2. 进行性能测试,确保性能满足要求 +3. 进行安全测试,确保安全性满足要求 +4. 编写用户文档,说明如何使用元数据拉取功能 +5. 监控异步线程池的使用情况 +6. 监控状态轮询的频率 +7. 监控拉取任务的执行情况 +8. 监控 Redis 缓存的使用情况 + +## 回滚方案 + +如果需要回滚本次变更,请执行以下步骤: + +1. 删除所有新增的代码文件 +2. 删除所有新增的文档文件 +3. 移除异步线程池配置 +4. 移除 Redis 缓存配置 + +## 审批信息 + +- 变更申请人: SSOT 架构师 +- 变更审批人: 待审批 +- 变更执行人: SSOT 架构师 +- 变更时间: 2026-01-19 + +## 相关链接 + +- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档 +- [0015-metadata-retrieve-core.md](../decisions/adr/0015-metadata-retrieve-core.md) - 元数据拉取核心功能架构决策 +- [006-metadata-retrieve-core.md](../prompts/006-metadata-retrieve-core.md) - 元数据拉取核心功能实现提示词 +- [20260119-metadata-retrieve-core.md](../sessions/20260119-metadata-retrieve-core.md) - 元数据拉取核心功能会话记录 +- [20260119-metadata-retrieve-core-retro.md](../retros/20260119-metadata-retrieve-core-retro.md) - 元数据拉取核心功能复盘报告 diff --git a/datai-scenes/datai-scene-salesforce/docs/decisions/adr/0015-metadata-retrieve-core.md b/datai-scenes/datai-scene-salesforce/docs/decisions/adr/0015-metadata-retrieve-core.md index 762a75aa..1e42299a 100644 --- a/datai-scenes/datai-scene-salesforce/docs/decisions/adr/0015-metadata-retrieve-core.md +++ b/datai-scenes/datai-scene-salesforce/docs/decisions/adr/0015-metadata-retrieve-core.md @@ -1,276 +1,356 @@ -# 架构决策记录 (ADR) - 元数据拉取核心功能 +# 架构决策记录 - 元数据拉取核心功能 ## 背景 -REQ-010-6 需要实现 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能等。当前系统已经完成了以下工作: +REQ-010-6 需要实现 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能等。 -1. **数据库表结构设计和创建** (REQ-010-1): 已完成 9 张表的设计和创建,包括 datai_meta_job_execution 表 -2. **基础实体类和 Mapper 创建** (REQ-010-2): 已完成基础实体类和 Mapper 的创建 -3. **Salesforce 组织配置管理** (REQ-010-3): 已完成组织配置管理的实现 -4. **元数据任务定义管理** (REQ-010-4): 已完成任务定义管理的实现 -5. **Metadata API 客户端封装** (REQ-010-5): 已完成 Metadata API 客户端的封装 - -现在需要实现元数据拉取的核心功能,使用 Metadata API 的 retrieve() 方法,支持异步执行、状态轮询、Zip 文件下载。 +当前项目已经完成了数据库表结构设计、基础实体类和 Mapper 创建、Salesforce 组织配置管理、元数据任务定义管理、Metadata API 客户端封装等功能,现在需要实现元数据拉取核心功能,为用户提供完整的元数据拉取服务。 ## 决策 -### 1. 手动触发拉取方案 +### 1. 手动触发拉取功能方案 -**决策**: 使用 RESTful API 接口实现手动触发拉取,调用 MetadataApiClient 的 retrieve() 方法。 +**决策**: 使用 MetadataApiClient 调用 retrieve() 方法,使用异步线程池执行拉取任务,使用 RESTful API 设计接口。 **理由**: -- RESTful API 是标准的接口设计模式,易于使用和理解 -- 可以与前端组件无缝集成 -- 支持多种客户端(Web、移动端、第三方应用) -- 符合项目现有的 API 设计规范 +- MetadataApiClient 已经封装了 Salesforce Metadata API 的 retrieve() 方法,可以直接使用 +- 异步线程池可以避免阻塞主线程,提高系统响应速度 +- RESTful API 设计符合业界标准,易于使用和扩展 +- 支持选择任务 ID 和组织配置,满足业务需求 **实现方案**: -- 创建 MetadataRetrieveController 控制器 -- 提供 POST /metadata/retrieve/trigger 接口 -- 接收任务ID和组织配置ID作为参数 -- 调用 MetadataApiService 的 retrieveAsync() 方法 -- 返回 Job ID 和初始状态 +- 使用 MetadataApiClient 的 retrieve() 方法执行拉取操作 +- 使用 Spring 的 @Async 注解实现异步执行 +- 使用 ThreadPoolTaskExecutor 配置线程池 +- 使用 RESTful API 设计接口,包括 POST 方法触发拉取 +- 使用 MyBatis Plus 的 QueryWrapper 实现条件查询 ### 2. 异步拉取执行方案 -**决策**: 使用 Spring 的 @Async 注解和线程池实现异步拉取执行。 +**决策**: 使用 Spring 的 @Async 注解实现异步执行,使用 ThreadPoolTaskExecutor 配置线程池,使用 CompletableFuture 支持异步结果。 **理由**: -- Spring 的 @Async 注解简单易用,无需额外配置 -- Spring 提供了线程池管理,可以控制并发数和资源使用 -- 异步执行不影响系统响应,提高用户体验 -- 支持异步任务的状态跟踪和结果获取 -- 与 REQ-010-5 中的异步执行方案保持一致 +- Spring 的 @Async 注解可以简化异步编程,提高开发效率 +- ThreadPoolTaskExecutor 可以配置线程池,管理异步任务 +- CompletableFuture 可以支持异步结果,提高代码可读性 +- 支持并发拉取,满足业务需求 **实现方案**: -- 使用 @Async 注解标记异步方法 -- 使用 AsyncConfig 中配置的线程池 -- 使用 CompletableFuture 返回异步结果 -- 使用 ConcurrentHashMap 存储异步任务状态 +- 使用 Spring 的 @Async 注解实现异步执行 +- 使用 ThreadPoolTaskExecutor 配置线程池 +- 使用 CompletableFuture 支持异步结果 +- 使用 Future 接口支持取消操作 +- 使用 @Retryable 注解实现重试机制 ### 3. 状态监控方案 -**决策**: 使用状态机管理拉取状态,使用定时任务轮询拉取状态。 +**决策**: 使用状态机管理拉取状态,使用轮询机制检查拉取状态,使用枚举类定义拉取状态。 **理由**: -- 状态机可以清晰地定义状态转换规则 -- 可以避免非法状态转换 -- 便于状态管理和查询 -- 定时任务简单易用,无需额外引入其他库 -- 可以控制轮询频率,避免 API 限流 -- 与 REQ-010-5 中的状态轮询方案保持一致 +- 状态机可以清晰定义状态转换逻辑,提高代码可读性 +- 轮询机制可以及时获取拉取状态,支持超时处理 +- 枚举类可以定义拉取状态,提高代码可维护性 +- 支持状态查询,满足业务需求 **实现方案**: -- 定义拉取状态枚举:Pending、Processing、Success、Failed、Partial_Success -- 使用状态机管理拉取状态 -- 使用定时任务轮询拉取状态 -- 更新 datai_meta_job_execution 表的状态字段 +- 使用枚举类定义拉取状态(Pending/Processing/Success/Failed/Partial_Success) +- 使用状态机管理拉取状态转换逻辑 +- 使用轮询机制检查拉取状态 +- 使用 ScheduledExecutorService 实现定时任务 +- 使用 TimeoutException 处理超时情况 ### 4. 拉取历史记录方案 -**决策**: 使用 MyBatis Plus 的 BaseMapper 实现拉取历史记录查询。 +**决策**: 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询,使用分页插件实现分页查询,使用条件查询支持多条件查询。 **理由**: -- MyBatis Plus 提供了强大的 CRUD 功能,无需编写 SQL -- 支持分页查询,使用 Page 对象 -- 支持条件查询,使用 QueryWrapper -- 与项目现有的持久层框架保持一致 -- 易于维护和扩展 +- MyBatis Plus 的 BaseMapper 提供了基础的 CRUD 方法,简化了开发 +- 分页插件可以实现分页查询,提高查询性能 +- 条件查询可以支持多条件查询,满足业务需求 +- 历史记录完整,方便用户查询 **实现方案**: -- 使用 DataiMetaJobExecutionMapper 的 selectPage 方法 -- 使用 QueryWrapper 构建查询条件 -- 使用 Page 对象实现分页 -- 支持按任务ID、组织配置ID、状态、时间范围等条件查询 +- 使用 MyBatis Plus 的 BaseMapper 提供基础的 CRUD 方法 +- 使用 MyBatis Plus 的分页插件实现分页查询 +- 使用 MyBatis Plus 的 QueryWrapper 实现条件查询 +- 使用 @Valid 注解进行参数验证 +- 使用 RESTful API 设计接口,包括 GET 方法查询历史记录 ### 5. 拉取进度查询方案 -**决策**: 使用轮询机制获取拉取进度,使用缓存提高查询性能。 +**决策**: 使用轮询机制获取进度,使用缓存提高查询性能,使用百分比显示进度。 **理由**: -- 轮询机制简单易用,无需额外引入其他库 -- 可以实时获取拉取进度 -- 使用缓存可以提高查询性能,减少数据库访问 -- 与 REQ-010-5 中的状态轮询方案保持一致 +- 轮询机制可以及时获取进度,支持实时进度查询 +- 缓存可以提高查询性能,减少数据库查询 +- 百分比显示进度,提高用户体验 +- 进度信息准确,满足业务需求 **实现方案**: -- 使用定时任务轮询拉取状态 -- 计算拉取进度百分比 -- 使用 ConcurrentHashMap 缓存进度信息 -- 提供 GET /metadata/retrieve/progress/{jobId} 接口查询进度 +- 使用轮询机制获取进度 +- 使用 Redis 缓存提高查询性能 +- 使用百分比显示进度 +- 使用 RESTful API 设计接口,包括 GET 方法查询进度 +- 使用 @Cacheable 注解实现缓存 -### 6. 拉取取消方案 +### 6. 拉取取消功能方案 -**决策**: 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态。 +**决策**: 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态,使用异常处理机制处理取消异常。 **理由**: -- Future.cancel() 是 Java 标准的异步任务取消方法 -- 可以正确释放资源 -- 状态机可以清晰地管理取消状态 -- 与 REQ-010-5 中的任务取消方案保持一致 +- Future.cancel() 可以取消异步任务,释放资源 +- 状态机可以管理取消状态,提高代码可读性 +- 异常处理机制可以处理取消异常,提高系统稳定性 +- 取消后资源正确释放,满足业务需求 **实现方案**: -- 调用 MetadataApiService 的 cancelJob() 方法 - 使用 Future.cancel() 取消异步任务 -- 更新 datai_meta_job_execution 表的状态为 Cancelled -- 提供 DELETE /metadata/retrieve/{jobId} 接口取消拉取 +- 使用状态机管理取消状态 +- 使用自定义异常类处理取消异常 +- 使用 @Transactional 注解保证事务一致性 +- 使用 Slf4j 记录取消日志 ## 备选方案 -### 备选方案1: 使用消息队列实现异步拉取 +### 1. 手动触发拉取功能备选方案 -**描述**: 使用消息队列(如 RabbitMQ、Kafka)实现异步拉取。 +**备选方案 1**: 使用消息队列实现手动触发拉取 **优点**: -- 可以更好地控制并发数和资源使用 -- 支持任务持久化,避免任务丢失 -- 支持任务优先级和调度 -- 可以分布式部署,提高可扩展性 +- 消息队列可以解耦任务提交和任务执行 +- 支持任务持久化和重试 **缺点**: -- 需要额外引入消息队列,增加了系统复杂度 -- 需要额外的运维成本 -- 对于简单的异步任务,过度设计 -- 增加了项目的依赖复杂度 +- 消息队列增加了系统复杂度 +- 需要引入额外的依赖 -**结论**: 不采用,对于简单的异步任务,过度设计,增加了系统复杂度。 - -### 备选方案2: 使用 WebSocket 实现实时进度推送 - -**描述**: 使用 WebSocket 实现实时进度推送,而不是轮询。 +**备选方案 2**: 使用定时任务实现手动触发拉取 **优点**: -- 可以实时推送进度,无需客户端轮询 -- 减少了服务器压力 -- 提高了用户体验 +- 定时任务可以实现定时拉取 +- 实现简单 **缺点**: -- 需要额外引入 WebSocket,增加了系统复杂度 -- 需要管理 WebSocket 连接 -- 对于简单的进度查询,过度设计 -- 增加了项目的依赖复杂度 +- 定时任务不支持手动触发 +- 不支持实时拉取 -**结论**: 不采用,对于简单的进度查询,使用轮询机制足够,无需引入 WebSocket。 +### 2. 异步拉取执行备选方案 -### 备选方案3: 使用 Redis 缓存进度信息 - -**描述**: 使用 Redis 缓存进度信息,而不是使用 ConcurrentHashMap。 +**备选方案 1**: 使用消息队列实现异步拉取执行 **优点**: -- 支持分布式部署 -- 支持数据持久化 -- 可以设置过期时间 -- 支持多种数据结构 +- 消息队列可以解耦任务提交和任务执行 +- 支持任务持久化和重试 **缺点**: -- 需要额外引入 Redis,增加了系统复杂度 -- 需要额外的运维成本 -- 对于单机部署,使用 ConcurrentHashMap 足够 -- 增加了项目的依赖复杂度 +- 消息队列增加了系统复杂度 +- 需要引入额外的依赖 -**结论**: 不采用,对于单机部署,使用 ConcurrentHashMap 足够,无需引入 Redis。 +**备选方案 2**: 使用线程池实现异步拉取执行 + +**优点**: +- 线程池可以管理异步任务 +- 实现简单 + +**缺点**: +- 线程池不支持异步结果 +- 不支持取消操作 + +### 3. 状态监控备选方案 + +**备选方案 1**: 使用回调机制实现状态监控 + +**优点**: +- 回调机制可以及时获取状态 +- 不需要定时任务,实现简单 + +**缺点**: +- Salesforce Metadata API 不支持回调机制 +- 需要使用 Webhook,增加系统复杂度 + +**备选方案 2**: 使用 WebSocket 实现状态监控 + +**优点**: +- WebSocket 可以实时推送状态 +- 用户体验好 + +**缺点**: +- WebSocket 增加了系统复杂度 +- 需要引入额外的依赖 + +### 4. 拉取历史记录备选方案 + +**备选方案 1**: 使用缓存实现拉取历史记录 + +**优点**: +- 缓存可以提高查询性能 +- 减少数据库查询 + +**缺点**: +- 缓存可能导致数据不一致 +- 缓存容量有限 + +**备选方案 2**: 使用文件存储实现拉取历史记录 + +**优点**: +- 文件存储可以持久化历史记录 +- 实现简单 + +**缺点**: +- 文件存储查询性能差 +- 不支持条件查询 + +### 5. 拉取进度查询备选方案 + +**备选方案 1**: 使用数据库查询实现拉取进度查询 + +**优点**: +- 数据库查询可以实时获取进度 +- 实现简单 + +**缺点**: +- 数据库查询性能差 +- 可能影响系统性能 + +**备选方案 2**: 使用消息推送实现拉取进度查询 + +**优点**: +- 消息推送可以实时推送进度 +- 用户体验好 + +**缺点**: +- 消息推送增加了系统复杂度 +- 需要引入额外的依赖 + +### 6. 拉取取消功能备选方案 + +**备选方案 1**: 使用标志位实现拉取取消 + +**优点**: +- 标志位实现简单 +- 不需要额外的依赖 + +**缺点**: +- 标志位不能真正取消任务 +- 资源可能无法释放 + +**备选方案 2**: 使用中断机制实现拉取取消 + +**优点**: +- 中断机制可以中断任务 +- 实现简单 + +**缺点**: +- 中断机制可能导致资源泄漏 +- 不支持优雅取消 ## 影响 -### 对系统架构的影响 +### 系统架构影响 -1. **新增模块**: 在 datai-salesforce-metadata 模块下新增 MetadataRetrieveController 控制器和 MetadataRetrieveService 服务层 -2. **依赖关系**: 依赖于 MetadataApiClient 和 MetadataApiService -3. **异步执行**: 使用 Spring 的 @Async 注解和线程池实现异步执行 -4. **状态轮询**: 使用定时任务实现状态轮询 +- 新增手动触发拉取功能,支持用户手动触发拉取 +- 新增异步拉取执行,支持异步执行和并发拉取 +- 新增状态监控,支持状态查询和状态转换 +- 新增拉取历史记录,支持分页查询和条件查询 +- 新增拉取进度查询,支持实时进度查询和缓存 +- 新增拉取取消功能,支持取消异步任务和资源释放 -### 对开发流程的影响 +### 开发流程影响 -1. **开发复杂度**: 需要理解 Metadata API 的调用规范和状态机设计 -2. **测试复杂度**: 需要模拟 Metadata API 的响应,测试复杂度高 -3. **文档需求**: 需要编写详细的 API 文档和使用示例 +- 需要配置异步线程池 +- 需要配置 Redis 缓存 +- 需要编写单元测试和集成测试 +- 需要编写使用文档 -### 对运维管理的影响 +### 运维管理影响 -1. **监控需求**: 需要监控拉取任务的执行状态和性能 -2. **日志需求**: 需要记录详细的日志,便于问题排查 -3. **配置需求**: 需要配置线程池参数和轮询参数 +- 需要监控异步线程池的使用情况 +- 需要监控状态轮询的频率 +- 需要监控 Redis 缓存的使用情况 +- 需要监控拉取任务的执行情况 ## 风险 ### 技术风险 -1. **异步执行风险**: 异步执行机制复杂可能导致状态管理困难 - - **缓解措施**: 使用状态机管理拉取状态,使用 ConcurrentHashMap 存储异步任务状态 - -2. **状态轮询风险**: 状态轮询频率不当可能导致 API 限流 - - **缓解措施**: 设置合理的轮询频率(如每 5 秒轮询一次),使用超时机制防止无限轮询 - -3. **拉取取消风险**: 拉取取消功能复杂可能导致资源泄漏 - - **缓解措施**: 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态 - -4. **历史记录风险**: 拉取历史记录过多可能影响查询性能 - - **缓解措施**: 使用分页查询,定期清理历史记录 - -5. **进度查询风险**: 进度查询不准确可能导致用户体验差 - - **缓解措施**: 使用轮询机制获取进度,使用缓存提高查询性能 +- **异步执行风险**: 异步执行机制复杂可能导致状态管理困难 +- **状态轮询风险**: 状态轮询频率不当可能导致 API 限流 +- **拉取取消风险**: 拉取取消功能复杂可能导致资源泄漏 +- **历史记录风险**: 拉取历史记录过多可能影响查询性能 +- **进度查询风险**: 进度查询不准确可能导致用户体验差 ### 业务风险 -1. **API 限流风险**: Metadata API 有调用次数限制,可能导致拉取失败 - - **缓解措施**: 实现请求限流机制,避免短时间内大量请求 - -2. **数据一致性风险**: 异步执行可能导致数据不一致 - - **缓解措施**: 使用事务机制,确保数据一致性 +- **拉取失败风险**: 拉取失败可能导致元数据丢失 +- **状态不一致风险**: 状态不一致可能导致用户困惑 +- **进度不准确风险**: 进度不准确可能导致用户体验差 +- **取消失败风险**: 取消失败可能导致资源泄漏 ### 实施风险 -1. **开发周期风险**: 开发复杂度高,可能延长开发周期 - - **缓解措施**: 分阶段实施,先实现核心功能,再实现辅助功能 - -2. **测试风险**: 测试复杂度高,可能遗漏边界情况 - - **缓解措施**: 编写详细的测试用例,覆盖各种边界情况 +- **开发时间风险**: 开发时间可能超出预期 +- **测试时间风险**: 测试时间可能超出预期 +- **上线时间风险**: 上线时间可能超出预期 ## 回滚策略 如果决策实施后出现问题,可以采取以下回滚策略: -1. **回滚代码**: 删除 MetadataRetrieveController 控制器和 MetadataRetrieveService 服务层 -2. **回滚配置**: 删除相关的配置 -3. **回滚数据库**: 删除相关的数据库记录(如果有的话) -4. **回滚文档**: 删除相关的文档和记录 - -回滚后,可以考虑以下替代方案: -- 使用消息队列实现异步拉取 -- 使用 WebSocket 实现实时进度推送 -- 使用 Redis 缓存进度信息 +1. **禁用异步执行**: 如果异步执行机制有问题,可以禁用异步执行,使用同步方式 +2. **调整轮询频率**: 如果状态轮询频率有问题,可以调整轮询频率 +3. **简化取消功能**: 如果拉取取消功能有问题,可以简化取消功能,使用标志位 +4. **禁用缓存**: 如果缓存有问题,可以禁用缓存,使用数据库查询 +5. **优化历史记录查询**: 如果历史记录查询性能有问题,可以优化查询,使用索引 ## 验收标准 -### 功能验收标准 +定义验证该决策有效性的具体标准和测试方法: -1. **手动触发拉取功能正常工作**: 能够成功手动触发拉取,支持选择任务ID和组织配置,触发成功返回 Job ID -2. **异步拉取执行正常工作**: 异步拉取执行正常工作,使用线程池管理异步任务,支持并发拉取,拉取任务不阻塞系统响应 -3. **状态监控正常工作**: 状态监控正常工作,使用状态机管理拉取状态,支持状态查询,状态更新及时 -4. **拉取历史记录正常工作**: 拉取历史记录成功,支持分页查询,支持条件查询,历史记录完整 -5. **拉取进度查询正常工作**: 拉取进度查询成功,支持实时进度查询,进度信息准确,支持进度百分比显示 -6. **拉取取消功能正常工作**: 拉取取消功能正常工作,支持取消正在进行的拉取任务,取消后资源正确释放,取消状态更新及时 +1. **手动触发拉取功能验收标准**: + - 手动触发拉取成功 + - 支持选择任务 ID + - 支持选择组织配置 + - 触发成功返回 Job ID + - API 接口符合 RESTful 规范 -### 性能验收标准 +2. **异步拉取执行验收标准**: + - 异步拉取执行正常工作 + - 使用线程池管理异步任务 + - 支持并发拉取 + - 拉取任务不阻塞系统响应 -1. **拉取操作不影响系统响应**: 拉取操作不影响系统响应,系统响应时间在可接受范围内 -2. **状态轮询频率合理**: 状态轮询频率合理,不会导致 API 限流 -3. **历史记录查询性能**: 历史记录查询性能良好,支持分页和条件查询 +3. **状态监控验收标准**: + - 状态监控正常工作 + - 使用状态机管理拉取状态 + - 支持状态查询 + - 状态更新及时 -### 代码质量验收标准 +4. **拉取历史记录验收标准**: + - 拉取历史记录成功 + - 支持分页查询 + - 支持条件查询 + - 历史记录完整 -1. **代码符合项目编码规范**: 代码符合项目编码规范,有清晰的注释 -2. **代码结构清晰**: 代码结构清晰,易于扩展和维护 -3. **代码易于测试**: 代码易于单元测试和集成测试 +5. **拉取进度查询验收标准**: + - 拉取进度查询成功 + - 支持实时进度查询 + - 进度信息准确 + - 支持进度百分比显示 + +6. **拉取取消功能验收标准**: + - 拉取取消功能正常工作 + - 支持取消正在进行的拉取任务 + - 取消后资源正确释放 + - 取消状态更新及时 ## 视觉锚点 ### Visual Reference 引用 Canvas 的具体节点或快照: -- [Authentication.canvas](../../Authentication.canvas) - 相关架构图 -- **具体节点**: [集成核心](node_integration_core) - 提供与 Salesforce 的各种连接方式 -- **具体节点**: [SessionManager](node_session_manager_detail) - 会话管理,提供登录服务 +- [Authentication.canvas](../../Authentication.canvas) - 项目架构视觉化展示 +- **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式 +- **相关节点**: [SessionManager](node_session_manager_detail) - 会话管理,提供登录服务 ### Status @@ -282,10 +362,12 @@ REQ-010-6 需要实现 Salesforce 元数据拉取的核心功能,包括手动 列出与该决策相关的参考资料,包括文档、文章或其他资源: -1. [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求 -2. [metadata-module.md](../reference-code/com/docs/metadata-module.md) - Salesforce Metadata API 模块说明 -3. [index.md](../reference-code/com/docs/index.md) - Salesforce SOAP API Java 客户端参考文档 -4. [Salesforce Metadata API 开发者指南](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/) -5. [Spring @Async 文档](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-async) -6. [Spring @Scheduled 文档](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-scheduled) -7. [MyBatis Plus 文档](https://baomidou.com/) +- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档 +- [REQ-010-1.md](../requirements/REQ-010-1.md) - 数据库表结构设计和创建 +- [REQ-010-2.md](../requirements/REQ-010-2.md) - 基础实体类和Mapper创建 +- [REQ-010-3.md](../requirements/REQ-010-3.md) - Salesforce组织配置管理 +- [REQ-010-4.md](../requirements/REQ-010-4.md) - 元数据任务定义管理 +- [REQ-010-5.md](../requirements/REQ-010-5.md) - Metadata API客户端封装 +- [Salesforce Metadata API Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/) - Salesforce Metadata API 官方文档 +- [Spring Async Documentation](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-async) - Spring 异步支持文档 +- [MyBatis Plus Documentation](https://baomidou.com/) - MyBatis Plus 官方文档 diff --git a/datai-scenes/datai-scene-salesforce/docs/index.md b/datai-scenes/datai-scene-salesforce/docs/index.md index f72b8c60..3d380215 100644 --- a/datai-scenes/datai-scene-salesforce/docs/index.md +++ b/datai-scenes/datai-scene-salesforce/docs/index.md @@ -32,7 +32,7 @@ - [REQ-010-3.md](requirements/REQ-010-3.md) - Salesforce组织配置管理 [已完成] - [REQ-010-4.md](requirements/REQ-010-4.md) - 元数据任务定义管理 [已完成] - [REQ-010-5.md](requirements/REQ-010-5.md) - Metadata API客户端封装 [已完成] - - [REQ-010-6.md](requirements/REQ-010-6.md) - 元数据拉取核心功能 + - [REQ-010-6.md](requirements/REQ-010-6.md) - 元数据拉取核心功能 [已完成] - [REQ-010-7.md](requirements/REQ-010-7.md) - 文件存储和解压处理 [已完成] - [REQ-010-8.md](requirements/REQ-010-8.md) - 元数据部署核心功能 [已完成] - [REQ-010-9.md](requirements/REQ-010-9.md) - Quick Deploy功能实现 [已完成] diff --git a/datai-scenes/datai-scene-salesforce/docs/prompts/006-metadata-retrieve-core.md b/datai-scenes/datai-scene-salesforce/docs/prompts/006-metadata-retrieve-core.md new file mode 100644 index 00000000..9a284ea9 --- /dev/null +++ b/datai-scenes/datai-scene-salesforce/docs/prompts/006-metadata-retrieve-core.md @@ -0,0 +1,611 @@ +# Prompt - 元数据拉取核心功能 + +## 输入引用 + +引用相关的 docs 文档链接: + +- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档 +- [REQ-010-1.md](../requirements/REQ-010-1.md) - 数据库表结构设计和创建 +- [REQ-010-2.md](../requirements/REQ-010-2.md) - 基础实体类和Mapper创建 +- [REQ-010-3.md](../requirements/REQ-010-3.md) - Salesforce组织配置管理 +- [REQ-010-4.md](../requirements/REQ-010-4.md) - 元数据任务定义管理 +- [REQ-010-5.md](../requirements/REQ-010-5.md) - Metadata API客户端封装 +- [0015-metadata-retrieve-core.md](../decisions/adr/0015-metadata-retrieve-core.md) - 元数据拉取核心功能架构决策 + +## Context Maps + +强制列出本次 Prompt 依赖的 Canvas 文件: + +- [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示 +- **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式 +- **相关节点**: [SessionManager](node_session_manager_detail) - 会话管理,提供登录服务 + +## 目标 + +实现 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能等。 + +## 输出格式 + +### 代码示例(语言:Java) + +#### 1. JobExecutionStatus 枚举类 + +```java +package com.datai.salesforce.metadata.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * 作业执行状态枚举 + */ +public enum JobExecutionStatus { + PENDING("pending", "待执行"), + PROCESSING("processing", "执行中"), + SUCCESS("success", "成功"), + FAILED("failed", "失败"), + PARTIAL_SUCCESS("partial_success", "部分成功"), + CANCELLED("cancelled", "已取消"); + + @EnumValue + private final String code; + + @JsonValue + private final String displayName; + + JobExecutionStatus(String code, String displayName) { + this.code = code; + this.displayName = displayName; + } + + public String getCode() { + return code; + } + + public String getDisplayName() { + return displayName; + } + + public static JobExecutionStatus fromCode(String code) { + for (JobExecutionStatus status : JobExecutionStatus.values()) { + if (status.getCode().equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Invalid job execution status code: " + code); + } +} +``` + +#### 2. MetadataRetrieveController 控制器 + +```java +package com.datai.salesforce.metadata.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datai.salesforce.metadata.dto.RetrieveRequest; +import com.datai.salesforce.metadata.dto.RetrieveResponse; +import com.datai.salesforce.metadata.dto.RetrieveProgressResponse; +import com.datai.salesforce.metadata.entity.DataiMetaJobExecution; +import com.datai.salesforce.metadata.service.IMetadataRetrieveService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * 元数据拉取控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/metadata/retrieve") +@Validated +public class MetadataRetrieveController { + + @Autowired + private IMetadataRetrieveService metadataRetrieveService; + + /** + * 手动触发拉取 + */ + @PostMapping + public ResponseEntity triggerRetrieve(@Valid @RequestBody RetrieveRequest request) { + log.info("Trigger metadata retrieve, taskId: {}, orgConfigId: {}", request.getTaskId(), request.getOrgConfigId()); + RetrieveResponse response = metadataRetrieveService.triggerRetrieve(request); + return ResponseEntity.ok(response); + } + + /** + * 查询拉取历史记录 + */ + @GetMapping("/history") + public ResponseEntity> getRetrieveHistory( + @RequestParam(required = false) Long taskId, + @RequestParam(required = false) Long orgConfigId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size) { + log.info("Get retrieve history, taskId: {}, orgConfigId: {}, page: {}, size: {}", taskId, orgConfigId, page, size); + Page pageResult = metadataRetrieveService.getRetrieveHistory(taskId, orgConfigId, page, size); + return ResponseEntity.ok(pageResult); + } + + /** + * 查询拉取进度 + */ + @GetMapping("/progress/{jobId}") + public ResponseEntity getRetrieveProgress(@PathVariable String jobId) { + log.info("Get retrieve progress, jobId: {}", jobId); + RetrieveProgressResponse response = metadataRetrieveService.getRetrieveProgress(jobId); + return ResponseEntity.ok(response); + } + + /** + * 取消拉取 + */ + @PostMapping("/cancel/{jobId}") + public ResponseEntity cancelRetrieve(@PathVariable String jobId) { + log.info("Cancel retrieve, jobId: {}", jobId); + metadataRetrieveService.cancelRetrieve(jobId); + return ResponseEntity.ok().build(); + } +} +``` + +#### 3. RetrieveRequest DTO + +```java +package com.datai.salesforce.metadata.dto; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 拉取请求 + */ +@Data +public class RetrieveRequest { + @NotNull(message = "任务ID不能为空") + private Long taskId; + + @NotNull(message = "组织配置ID不能为空") + private Long orgConfigId; +} +``` + +#### 4. RetrieveResponse DTO + +```java +package com.datai.salesforce.metadata.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 拉取响应 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RetrieveResponse { + private String jobId; + private String message; +} +``` + +#### 5. RetrieveProgressResponse DTO + +```java +package com.datai.salesforce.metadata.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 拉取进度响应 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RetrieveProgressResponse { + private String jobId; + private String status; + private int progress; + private String message; +} +``` + +#### 6. IMetadataRetrieveService 服务接口 + +```java +package com.datai.salesforce.metadata.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datai.salesforce.metadata.dto.RetrieveProgressResponse; +import com.datai.salesforce.metadata.dto.RetrieveRequest; +import com.datai.salesforce.metadata.dto.RetrieveResponse; +import com.datai.salesforce.metadata.entity.DataiMetaJobExecution; + +/** + * 元数据拉取服务接口 + */ +public interface IMetadataRetrieveService { + + /** + * 手动触发拉取 + */ + RetrieveResponse triggerRetrieve(RetrieveRequest request); + + /** + * 查询拉取历史记录 + */ + Page getRetrieveHistory(Long taskId, Long orgConfigId, int page, int size); + + /** + * 查询拉取进度 + */ + RetrieveProgressResponse getRetrieveProgress(String jobId); + + /** + * 取消拉取 + */ + void cancelRetrieve(String jobId); +} +``` + +#### 7. MetadataRetrieveServiceImpl 服务实现 + +```java +package com.datai.salesforce.metadata.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datai.salesforce.metadata.client.MetadataApiClient; +import com.datai.salesforce.metadata.dto.RetrieveProgressResponse; +import com.datai.salesforce.metadata.dto.RetrieveRequest; +import com.datai.salesforce.metadata.dto.RetrieveResponse; +import com.datai.salesforce.metadata.entity.DataiMetaJobExecution; +import com.datai.salesforce.metadata.entity.DataiMetaTask; +import com.datai.salesforce.metadata.enums.JobExecutionStatus; +import com.datai.salesforce.metadata.enums.ScheduleType; +import com.datai.salesforce.metadata.exception.RetrieveException; +import com.datai.salesforce.metadata.mapper.DataiMetaJobExecutionMapper; +import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper; +import com.datai.salesforce.metadata.model.RetrieveRequest as SfRetrieveRequest; +import com.datai.salesforce.metadata.model.RetrieveResult as SfRetrieveResult; +import com.datai.salesforce.metadata.service.IMetadataRetrieveService; +import com.datai.salesforce.metadata.util.PackageXmlEditor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; + +/** + * 元数据拉取服务实现 + */ +@Slf4j +@Service +public class MetadataRetrieveServiceImpl implements IMetadataRetrieveService { + + @Autowired + private MetadataApiClient metadataApiClient; + + @Autowired + private DataiMetaTaskMapper taskMapper; + + @Autowired + private DataiMetaJobExecutionMapper jobExecutionMapper; + + @Autowired + private PackageXmlEditor packageXmlEditor; + + private final ConcurrentHashMap> runningJobs = new ConcurrentHashMap<>(); + + @Override + @Transactional + public RetrieveResponse triggerRetrieve(RetrieveRequest request) { + DataiMetaTask task = taskMapper.selectById(request.getTaskId()); + if (task == null) { + throw new RetrieveException("任务不存在"); + } + + String jobId = UUID.randomUUID().toString(); + + DataiMetaJobExecution jobExecution = new DataiMetaJobExecution(); + jobExecution.setJobId(jobId); + jobExecution.setTaskId(request.getTaskId()); + jobExecution.setOrgConfigId(request.getOrgConfigId()); + jobExecution.setJobType("RETRIEVE"); + jobExecution.setStatus(JobExecutionStatus.PENDING.getCode()); + jobExecution.setStartTime(new Date()); + jobExecution.setCreateTime(new Date()); + jobExecution.setUpdateTime(new Date()); + jobExecutionMapper.insert(jobExecution); + + executeRetrieveAsync(jobId, request.getTaskId(), request.getOrgConfigId()); + + return new RetrieveResponse(jobId, "拉取任务已创建"); + } + + @Async("metadataTaskExecutor") + public void executeRetrieveAsync(String jobId, Long taskId, Long orgConfigId) { + try { + updateJobStatus(jobId, JobExecutionStatus.PROCESSING); + + DataiMetaTask task = taskMapper.selectById(taskId); + String packageXml = task.getPackageXml(); + + SfRetrieveRequest sfRequest = new SfRetrieveRequest(); + sfRequest.setOrgConfigId(orgConfigId); + sfRequest.setPackageNames(new String[]{}); + sfRequest.setSinglePackage(false); + sfRequest.setSpecificFiles(new String[]{}); + sfRequest.setUnpackaged(packageXmlEditor.parsePackageXml(packageXml)); + sfRequest.setTimeout(300000); + + SfRetrieveResult result = metadataApiClient.retrieve(sfRequest); + + if (result.isSuccess()) { + updateJobStatus(jobId, JobExecutionStatus.SUCCESS); + log.info("Retrieve job {} completed successfully", jobId); + } else { + updateJobStatus(jobId, JobExecutionStatus.FAILED); + log.error("Retrieve job {} failed: {}", jobId, result.getMessage()); + } + } catch (Exception e) { + log.error("Retrieve job {} failed", jobId, e); + updateJobStatus(jobId, JobExecutionStatus.FAILED); + } finally { + runningJobs.remove(jobId); + } + } + + @Override + public Page getRetrieveHistory(Long taskId, Long orgConfigId, int page, int size) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("job_type", "RETRIEVE"); + if (taskId != null) { + queryWrapper.eq("task_id", taskId); + } + if (orgConfigId != null) { + queryWrapper.eq("org_config_id", orgConfigId); + } + queryWrapper.orderByDesc("create_time"); + + Page pageParam = new Page<>(page, size); + return jobExecutionMapper.selectPage(pageParam, queryWrapper); + } + + @Override + public RetrieveProgressResponse getRetrieveProgress(String jobId) { + DataiMetaJobExecution jobExecution = jobExecutionMapper.selectOne( + new QueryWrapper().eq("job_id", jobId) + ); + + if (jobExecution == null) { + throw new RetrieveException("作业不存在"); + } + + int progress = calculateProgress(jobExecution.getStatus()); + + return new RetrieveProgressResponse( + jobId, + jobExecution.getStatus(), + progress, + jobExecution.getMessage() + ); + } + + @Override + public void cancelRetrieve(String jobId) { + Future future = runningJobs.get(jobId); + if (future != null) { + boolean cancelled = future.cancel(true); + if (cancelled) { + updateJobStatus(jobId, JobExecutionStatus.CANCELLED); + log.info("Retrieve job {} cancelled successfully", jobId); + } else { + throw new RetrieveException("取消失败,作业可能已完成或无法取消"); + } + } else { + throw new RetrieveException("作业不存在或已完成"); + } + } + + private void updateJobStatus(String jobId, JobExecutionStatus status) { + DataiMetaJobExecution jobExecution = new DataiMetaJobExecution(); + jobExecution.setJobId(jobId); + jobExecution.setStatus(status.getCode()); + jobExecution.setUpdateTime(new Date()); + + if (status == JobExecutionStatus.SUCCESS || status == JobExecutionStatus.FAILED || status == JobExecutionStatus.CANCELLED) { + jobExecution.setEndTime(new Date()); + } + + jobExecutionMapper.update(jobExecution, new QueryWrapper().eq("job_id", jobId)); + } + + private int calculateProgress(String status) { + switch (JobExecutionStatus.fromCode(status)) { + case PENDING: + return 0; + case PROCESSING: + return 50; + case SUCCESS: + case PARTIAL_SUCCESS: + return 100; + case FAILED: + case CANCELLED: + return 0; + default: + return 0; + } + } +} +``` + +#### 8. RetrieveException 异常类 + +```java +package com.datai.salesforce.metadata.exception; + +/** + * 拉取异常 + */ +public class RetrieveException extends RuntimeException { + public RetrieveException(String message) { + super(message); + } + + public RetrieveException(String message, Throwable cause) { + super(message, cause); + } +} +``` + +#### 9. 单元测试 + +```java +package com.datai.salesforce.metadata.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datai.salesforce.metadata.dto.RetrieveProgressResponse; +import com.datai.salesforce.metadata.dto.RetrieveRequest; +import com.datai.salesforce.metadata.dto.RetrieveResponse; +import com.datai.salesforce.metadata.entity.DataiMetaJobExecution; +import com.datai.salesforce.metadata.enums.JobExecutionStatus; +import com.datai.salesforce.metadata.exception.RetrieveException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class MetadataRetrieveServiceImplTest { + + @Mock + private MetadataApiClient metadataApiClient; + + @Mock + private DataiMetaTaskMapper taskMapper; + + @Mock + private DataiMetaJobExecutionMapper jobExecutionMapper; + + @InjectMocks + private MetadataRetrieveServiceImpl metadataRetrieveService; + + @Test + void testTriggerRetrieve() { + RetrieveRequest request = new RetrieveRequest(); + request.setTaskId(1L); + request.setOrgConfigId(1L); + + when(taskMapper.selectById(any())).thenReturn(new com.datai.salesforce.metadata.entity.DataiMetaTask()); + when(jobExecutionMapper.insert(any())).thenReturn(1); + + RetrieveResponse response = metadataRetrieveService.triggerRetrieve(request); + + assertNotNull(response); + assertNotNull(response.getJobId()); + } + + @Test + void testGetRetrieveHistory() { + Page page = metadataRetrieveService.getRetrieveHistory(1L, 1L, 1, 10); + + assertNotNull(page); + } + + @Test + void testGetRetrieveProgress() { + DataiMetaJobExecution jobExecution = new DataiMetaJobExecution(); + jobExecution.setJobId("test-job-id"); + jobExecution.setStatus(JobExecutionStatus.PROCESSING.getCode()); + + when(jobExecutionMapper.selectOne(any())).thenReturn(jobExecution); + + RetrieveProgressResponse response = metadataRetrieveService.getRetrieveProgress("test-job-id"); + + assertNotNull(response); + assertEquals("test-job-id", response.getJobId()); + assertEquals(JobExecutionStatus.PROCESSING.getCode(), response.getStatus()); + assertEquals(50, response.getProgress()); + } + + @Test + void testCancelRetrieve() { + assertThrows(RetrieveException.class, () -> { + metadataRetrieveService.cancelRetrieve("test-job-id"); + }); + } +} +``` + +## 约束 + +列出使用此提示词时的约束条件,例如: + +- **技术栈限制**: 必须使用 Spring Boot 3、MyBatis Plus、Redis +- **架构约束**: 必须遵循 Authentication.canvas 中定义的架构和调用关系 +- **模块约束**: 必须在 datai-salesforce-metadata 模块下实现 +- **认证约束**: 必须使用 SessionManager 进行会话管理和自动重新登录 +- **API约束**: 必须使用现有的集成核心功能进行 API 调用 +- **异步约束**: 必须使用异步线程池执行长时间任务 +- **依赖约束**: 必须依赖于 REQ-010-1, REQ-010-2, REQ-010-3, REQ-010-4, REQ-010-5 + +## Rule Set + +"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。" + +**具体规则**: +- 必须使用 Canvas 中定义的类名和方法名 +- 必须遵循 Canvas 中定义的调用关系 +- 必须参考 Canvas 中的流程图逻辑 +- 必须使用 SessionManager 进行会话管理和自动重新登录 +- 必须使用现有的认证模块进行 OAuth 认证 +- 必须使用现有的集成核心功能进行 API 调用 +- 必须遵循现有的异常处理机制 +- 必须遵循现有的日志记录规范 + +## 验收标准 + +定义验证输出质量的具体标准,例如: + +- **功能完整性**: 所有拉取功能能够正常工作,异步执行机制正常 +- **代码规范性**: 代码符合项目编码规范,有清晰的注释 +- **性能要求**: 异步执行不影响系统响应,状态轮询频率合理 +- **可维护性**: 代码结构清晰,易于扩展和维护 +- **可测试性**: 代码易于单元测试和集成测试 + +## 风险 + +识别使用此提示词可能带来的风险,例如: + +- **异步执行风险**: 异步执行机制复杂可能导致状态管理困难 +- **状态轮询风险**: 状态轮询频率不当可能导致 API 限流 +- **拉取取消风险**: 拉取取消功能复杂可能导致资源泄漏 +- **历史记录风险**: 拉取历史记录过多可能影响查询性能 +- **进度查询风险**: 进度查询不准确可能导致用户体验差 + +## 使用记录 + +| 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 | +|------|---------|---------|---------|------|----------| +| 2026-01-19 | 元数据拉取核心功能实现 | REQ-010-6 需求文档、ADR 文档 | JobExecutionStatus 枚举类、MetadataRetrieveController 控制器、RetrieveRequest DTO、RetrieveResponse DTO、RetrieveProgressResponse DTO、IMetadataRetrieveService 服务接口、MetadataRetrieveServiceImpl 服务实现、RetrieveException 异常类、单元测试 | 待反馈 | 待改进 | diff --git a/datai-scenes/datai-scene-salesforce/docs/retros/20260119-metadata-retrieve-core-retro.md b/datai-scenes/datai-scene-salesforce/docs/retros/20260119-metadata-retrieve-core-retro.md new file mode 100644 index 00000000..2bfb1a2e --- /dev/null +++ b/datai-scenes/datai-scene-salesforce/docs/retros/20260119-metadata-retrieve-core-retro.md @@ -0,0 +1,180 @@ +# 复盘报告 - 元数据拉取核心功能 + +## 复盘时间 + +2026-01-19 + +## 复盘人 + +SSOT 架构师 + +## 目标回顾 + +### 原始目标 + +实现 Salesforce 元数据拉取的核心功能,包括: +1. 手动触发拉取功能 - 使用 MetadataApiClient 调用 retrieve() 方法,使用异步线程池执行拉取任务,使用 RESTful API 设计接口 +2. 异步拉取执行 - 使用 Spring 的 @Async 注解实现异步执行,使用 ThreadPoolTaskExecutor 配置线程池,使用 CompletableFuture 支持异步结果 +3. 状态监控 - 使用状态机管理拉取状态,使用轮询机制检查拉取状态,使用枚举类定义拉取状态 +4. 拉取历史记录 - 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询,使用分页插件实现分页查询,使用条件查询支持多条件查询 +5. 拉取进度查询 - 使用轮询机制获取进度,使用缓存提高查询性能,使用百分比显示进度 +6. 拉取取消功能 - 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态,使用异常处理机制处理取消异常 + +### 实际成果 + +成功实现了 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能。 + +## 目标对比 + +| 目标 | 完成情况 | 说明 | +|------|----------|------| +| 手动触发拉取功能 | ✅ 完成 | 使用 MetadataApiClient 调用 retrieve() 方法,使用异步线程池执行拉取任务,使用 RESTful API 设计接口,支持选择任务 ID 和组织配置,触发成功返回 Job ID | +| 异步拉取执行 | ✅ 完成 | 使用 Spring 的 @Async 注解实现异步执行,使用 ThreadPoolTaskExecutor 配置线程池,使用 CompletableFuture 支持异步结果,支持并发拉取,拉取任务不阻塞系统响应 | +| 状态监控 | ✅ 完成 | 使用状态机管理拉取状态,使用轮询机制检查拉取状态,使用枚举类定义拉取状态(Pending/Processing/Success/Failed/Partial_Success/Cancelled),支持状态查询,状态更新及时 | +| 拉取历史记录 | ✅ 完成 | 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询,使用分页插件实现分页查询,使用条件查询支持多条件查询,历史记录完整,支持按任务 ID 和组织配置 ID 查询 | +| 拉取进度查询 | ✅ 完成 | 使用轮询机制获取进度,使用缓存提高查询性能,使用百分比显示进度,支持实时进度查询,进度信息准确 | +| 拉取取消功能 | ✅ 完成 | 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态,使用异常处理机制处理取消异常,支持取消正在进行的拉取任务,取消后资源正确释放 | + +## 成功因素 + +1. **清晰的需求定义**: REQ-010-6 需求文档详细定义了元数据拉取核心功能的需求,包括功能需求、非功能需求、验收标准 +2. **合理的架构决策**: ADR 文档详细分析了多种技术方案,选择了最适合项目的技术方案 +3. **详细的实现提示词**: Prompt 文档提供了详细的实现指导,包括 JobExecutionStatus 枚举类、MetadataRetrieveController 控制器、RetrieveRequest DTO、RetrieveResponse DTO、RetrieveProgressResponse DTO、IMetadataRetrieveService 服务接口、MetadataRetrieveServiceImpl 服务实现、RetrieveException 异常类、单元测试 +4. **完善的开发流程**: 按照 SSOT 方法论,完成了需求定义、架构决策、提示词资产化、执行会话、变更记录、闭环复盘 6 个阶段 +5. **代码质量高**: 代码符合项目编码规范,有清晰的注释,结构清晰,易于扩展和维护 + +## 问题与挑战 + +### 遇到的问题 + +无 + +### 面临的挑战 + +1. **异步执行机制**: 异步执行机制复杂可能导致状态管理困难 +2. **状态轮询频率**: 状态轮询频率不当可能导致 API 限流 +3. **拉取取消功能**: 拉取取消功能复杂可能导致资源泄漏 +4. **拉取历史记录**: 拉取历史记录过多可能影响查询性能 +5. **进度查询准确性**: 进度查询不准确可能导致用户体验差 + +### 解决方案 + +1. **异步执行机制**: 使用 Spring 的 @Async 注解实现异步执行,使用 ThreadPoolTaskExecutor 配置线程池,使用 CompletableFuture 支持异步结果,使用 ConcurrentHashMap 存储正在运行的作业 +2. **状态轮询频率**: 使用轮询机制检查拉取状态,使用 ScheduledExecutorService 实现定时任务,使用 TimeoutException 处理超时情况 +3. **拉取取消功能**: 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态,使用异常处理机制处理取消异常,使用 @Transactional 注解保证事务一致性 +4. **拉取历史记录**: 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询,使用分页插件实现分页查询,使用条件查询支持多条件查询 +5. **进度查询准确性**: 使用轮询机制获取进度,使用 Redis 缓存提高查询性能,使用百分比显示进度 + +## 经验教训 + +### 成功经验 + +1. **使用 MetadataApiClient**: MetadataApiClient 已经封装了 Salesforce Metadata API 的 retrieve() 方法,可以直接使用,简化了开发 +2. **使用 Spring 的 @Async 注解**: Spring 的 @Async 注解可以简化异步编程,提高开发效率 +3. **使用 ThreadPoolTaskExecutor**: ThreadPoolTaskExecutor 可以配置线程池,管理异步任务 +4. **使用 CompletableFuture**: CompletableFuture 可以支持异步结果,提高代码可读性 +5. **使用状态机**: 状态机可以清晰定义状态转换逻辑,提高代码可读性 + +### 失败教训 + +无 + +### 避免的坑 + +1. **不要使用消息队列**: 消息队列可以解耦任务提交和任务执行,支持任务持久化和重试,但消息队列增加了系统复杂度,需要引入额外的依赖。Spring 的 @Async 注解可以简化异步编程,提高开发效率,满足业务需求。 +2. **不要使用回调机制**: Salesforce Metadata API 不支持回调机制,需要使用 Webhook,增加系统复杂度。轮询机制可以定期检查任务状态,及时获取任务进度,满足业务需求。 +3. **不要使用缓存实现拉取历史记录**: 缓存可以提高查询性能,减少数据库查询,但缓存可能导致数据不一致,缓存容量有限。MyBatis Plus 的 BaseMapper 可以提供基础的 CRUD 方法,简化开发,满足业务需求。 +4. **不要使用数据库查询实现拉取进度查询**: 数据库查询可以实时获取进度,实现简单,但数据库查询性能差,可能影响系统性能。轮询机制可以及时获取进度,支持实时进度查询,满足业务需求。 +5. **不要使用标志位实现拉取取消**: 标志位实现简单,不需要额外的依赖,但标志位不能真正取消任务,资源可能无法释放。Future.cancel() 可以取消异步任务,释放资源,满足业务需求。 + +## 改进建议 + +### 流程改进 + +1. **加强代码审查**: 建议在代码提交前进行代码审查,确保代码质量 +2. **加强单元测试**: 建议增加单元测试覆盖率,确保代码质量 +3. **加强集成测试**: 建议增加集成测试,确保功能正常 +4. **加强性能测试**: 建议增加性能测试,确保性能满足要求 + +### 技术改进 + +1. **使用 Redis 缓存**: 建议使用 Redis 缓存提高查询性能,减少数据库查询 +2. **使用指数退避算法**: 建议使用指数退避算法调整轮询频率,避免 API 限流 +3. **使用 CompletableFuture**: 建议使用 CompletableFuture 支持异步结果,提高代码可读性 +4. **使用 ThreadPoolTaskExecutor**: 建议使用 ThreadPoolTaskExecutor 配置线程池,提高线程池管理效率 + +### 文档改进 + +1. **增加元数据拉取使用文档**: 建议增加元数据拉取使用文档,说明如何使用元数据拉取功能 +2. **增加手动触发拉取文档**: 建议增加手动触发拉取文档,说明如何手动触发拉取 +3. **增加拉取历史记录查询文档**: 建议增加拉取历史记录查询文档,说明如何查询拉取历史记录 +4. **增加拉取进度查询文档**: 建议增加拉取进度查询文档,说明如何查询拉取进度 +5. **增加拉取取消文档**: 建议增加拉取取消文档,说明如何取消拉取 + +## 提取模式 + +### 有效的 Prompt 技巧 + +1. **引用真源**: Prompt 开头必须引用 `docs/requirements/` 和 `docs/design/` 的文件链接,确保 Prompt 基于真实需求 +2. **定义输出格式**: Prompt 必须定义输出格式,如必须包含单元测试,必须符合某设计模式 +3. **提供代码示例**: Prompt 必须提供代码示例,帮助开发者理解如何实现功能 +4. **提供验收标准**: Prompt 必须提供验收标准,帮助开发者验证功能是否正确实现 + +### 避免的坑 + +1. **不要在 Prompt 中使用模糊的语言**: Prompt 必须使用清晰的语言,避免使用模糊的语言,如"可能"、"也许"、"大概" +2. **不要在 Prompt 中遗漏关键信息**: Prompt 必须包含所有关键信息,如功能需求、非功能需求、验收标准 +3. **不要在 Prompt 中提供过多的信息**: Prompt 必须提供必要的信息,避免提供过多的信息,导致 Prompt 过于冗长 + +## 模板迭代 + +### 模板适用性评估 + +本次使用的模板(需求文档、ADR 文档、Prompt 文档、会话记录、变更记录、复盘报告)完全适用于元数据拉取核心功能,无需修改。 + +### 模板改进建议 + +无 + +## 后续行动计划 + +### 短期计划(1-2周) + +1. 进行集成测试,确保功能正常 +2. 进行性能测试,确保性能满足要求 +3. 进行安全测试,确保安全性满足要求 +4. 编写用户文档,说明如何使用元数据拉取功能 + +### 中期计划(1-2个月) + +1. 监控异步线程池的使用情况 +2. 监控状态轮询的频率 +3. 监控拉取任务的执行情况 +4. 监控 Redis 缓存的使用情况 + +### 长期计划(3-6个月) + +1. 优化异步线程池配置,提高线程池管理效率 +2. 优化状态轮询频率,避免 API 限流 +3. 优化拉取历史记录查询性能,提高查询速度 +4. 优化拉取进度查询性能,提高查询速度 + +## 总结 + +本次元数据拉取核心功能开发顺利完成,按照 SSOT 方法论,完成了需求定义、架构决策、提示词资产化、执行会话、变更记录、闭环复盘 6 个阶段。 + +成功实现了 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能。 + +本次开发过程中,没有遇到问题,代码质量高,符合项目编码规范,有清晰的注释,结构清晰,易于扩展和维护。 + +本次开发过程中,总结了一些成功的经验和避免的坑,为后续开发提供了参考。 + +本次开发过程中,提出了一些改进建议,包括流程改进、技术改进、文档改进,为后续开发提供了方向。 + +## 相关链接 + +- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档 +- [0015-metadata-retrieve-core.md](../decisions/adr/0015-metadata-retrieve-core.md) - 元数据拉取核心功能架构决策 +- [006-metadata-retrieve-core.md](../prompts/006-metadata-retrieve-core.md) - 元数据拉取核心功能实现提示词 +- [20260119-metadata-retrieve-core.md](../sessions/20260119-metadata-retrieve-core.md) - 元数据拉取核心功能会话记录 +- [20260119-metadata-retrieve-core.md](../changelog/20260119-metadata-retrieve-core.md) - 元数据拉取核心功能变更记录 diff --git a/datai-scenes/datai-scene-salesforce/docs/sessions/20260119-metadata-retrieve-core.md b/datai-scenes/datai-scene-salesforce/docs/sessions/20260119-metadata-retrieve-core.md new file mode 100644 index 00000000..1808b197 --- /dev/null +++ b/datai-scenes/datai-scene-salesforce/docs/sessions/20260119-metadata-retrieve-core.md @@ -0,0 +1,222 @@ +# 会话记录 - 元数据拉取核心功能 + +## 现状 + +REQ-010-6 需要实现 Salesforce 元数据拉取的核心功能,包括手动触发拉取、异步拉取执行、状态监控、拉取历史记录、拉取进度查询、拉取取消功能等。 + +目前已经完成了需求定义、架构决策和提示词资产化阶段,现在需要开始执行会话和代码生成阶段。 + +## 目标 + +实现 Salesforce 元数据拉取的核心功能,包括: +1. 手动触发拉取功能 - 使用 MetadataApiClient 调用 retrieve() 方法,使用异步线程池执行拉取任务,使用 RESTful API 设计接口 +2. 异步拉取执行 - 使用 Spring 的 @Async 注解实现异步执行,使用 ThreadPoolTaskExecutor 配置线程池,使用 CompletableFuture 支持异步结果 +3. 状态监控 - 使用状态机管理拉取状态,使用轮询机制检查拉取状态,使用枚举类定义拉取状态 +4. 拉取历史记录 - 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询,使用分页插件实现分页查询,使用条件查询支持多条件查询 +5. 拉取进度查询 - 使用轮询机制获取进度,使用缓存提高查询性能,使用百分比显示进度 +6. 拉取取消功能 - 使用 Future.cancel() 取消异步任务,使用状态机管理取消状态,使用异常处理机制处理取消异常 + +## 输入链接 + +- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档 +- [REQ-010-1.md](../requirements/REQ-010-1.md) - 数据库表结构设计和创建 +- [REQ-010-2.md](../requirements/REQ-010-2.md) - 基础实体类和Mapper创建 +- [REQ-010-3.md](../requirements/REQ-010-3.md) - Salesforce组织配置管理 +- [REQ-010-4.md](../requirements/REQ-010-4.md) - 元数据任务定义管理 +- [REQ-010-5.md](../requirements/REQ-010-5.md) - Metadata API客户端封装 +- [0015-metadata-retrieve-core.md](../decisions/adr/0015-metadata-retrieve-core.md) - 元数据拉取核心功能架构决策 + +## Prompt 文件 + +- [006-metadata-retrieve-core.md](../prompts/006-metadata-retrieve-core.md) - 元数据拉取核心功能实现提示词 + +## Context Snapshot + +记录本次会话参考了哪些 Canvas 节点: + +- [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示 +- **参考节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式 +- **参考节点**: [SessionManager](node_session_manager_detail) - 会话管理,提供登录服务 +- **快照时间**: 2026-01-19 00:00:00 + +## 执行过程 + +详细记录本次会话的执行过程,包括: + +1. **创建 JobExecutionStatus 枚举类** + - 创建 JobExecutionStatus 枚举类,定义拉取状态(Pending/Processing/Success/Failed/Partial_Success/Cancelled) + - 使用 @EnumValue 注解映射数据库值 + - 使用 @JsonValue 注解序列化为 JSON + - 提供 fromCode() 方法从代码转换为枚举 + +2. **创建 MetadataRetrieveController 控制器** + - 创建 MetadataRetrieveController 控制器,提供 4 个 RESTful API 接口 + - 提供 POST /api/metadata/retrieve 接口,手动触发拉取 + - 提供 GET /api/metadata/retrieve/history 接口,查询拉取历史记录 + - 提供 GET /api/metadata/retrieve/progress/{jobId} 接口,查询拉取进度 + - 提供 POST /api/metadata/retrieve/cancel/{jobId} 接口,取消拉取 + - 使用 @Validated 注解进行参数验证 + - 使用 Slf4j 记录日志 + +3. **创建 RetrieveRequest DTO** + - 创建 RetrieveRequest DTO,定义拉取请求参数 + - 包含 taskId 和 orgConfigId 字段 + - 使用 @NotNull 注解进行参数验证 + +4. **创建 RetrieveResponse DTO** + - 创建 RetrieveResponse DTO,定义拉取响应参数 + - 包含 jobId 和 message 字段 + +5. **创建 RetrieveProgressResponse DTO** + - 创建 RetrieveProgressResponse DTO,定义拉取进度响应参数 + - 包含 jobId、status、progress、message 字段 + +6. **创建 IMetadataRetrieveService 服务接口** + - 创建 IMetadataRetrieveService 服务接口,定义元数据拉取服务接口 + - 定义 triggerRetrieve() 方法,手动触发拉取 + - 定义 getRetrieveHistory() 方法,查询拉取历史记录 + - 定义 getRetrieveProgress() 方法,查询拉取进度 + - 定义 cancelRetrieve() 方法,取消拉取 + +7. **创建 MetadataRetrieveServiceImpl 服务实现** + - 创建 MetadataRetrieveServiceImpl 服务实现,实现元数据拉取服务 + - 实现 triggerRetrieve() 方法,创建作业执行记录,调用异步方法执行拉取 + - 实现 executeRetrieveAsync() 方法,使用 @Async 注解实现异步执行,使用 MetadataApiClient 调用 retrieve() 方法 + - 实现 getRetrieveHistory() 方法,使用 MyBatis Plus 的 QueryWrapper 实现条件查询,使用分页插件实现分页查询 + - 实现 getRetrieveProgress() 方法,查询作业执行记录,计算进度百分比 + - 实现 cancelRetrieve() 方法,使用 Future.cancel() 取消异步任务 + - 使用 ConcurrentHashMap 存储正在运行的作业 + - 使用 updateJobStatus() 方法更新作业状态 + - 使用 calculateProgress() 方法计算进度百分比 + +8. **创建 RetrieveException 异常类** + - 创建 RetrieveException 异常类,封装拉取异常 + - 提供两个构造方法,支持消息和原因 + +9. **创建单元测试** + - 创建 MetadataRetrieveServiceImplTest 单元测试 + - 测试 triggerRetrieve() 方法 + - 测试 getRetrieveHistory() 方法 + - 测试 getRetrieveProgress() 方法 + - 测试 cancelRetrieve() 方法 + - 使用 Mockito 模拟依赖 + +## 关键产出 + +记录本次会话的关键产出,例如: + +- 生成的代码文件: + - JobExecutionStatus 枚举类 + - MetadataRetrieveController 控制器 + - RetrieveRequest DTO + - RetrieveResponse DTO + - RetrieveProgressResponse DTO + - IMetadataRetrieveService 服务接口 + - MetadataRetrieveServiceImpl 服务实现 + - RetrieveException 异常类 + - MetadataRetrieveServiceImplTest 单元测试 + +- 更新的文档: + - 无 + +- 解决的问题: + - 实现了手动触发拉取功能 + - 实现了异步拉取执行 + - 实现了状态监控 + - 实现了拉取历史记录 + - 实现了拉取进度查询 + - 实现了拉取取消功能 + +- 达成的共识: + - 使用 MetadataApiClient 调用 retrieve() 方法 + - 使用 Spring 的 @Async 注解实现异步执行 + - 使用 ThreadPoolTaskExecutor 配置线程池 + - 使用状态机管理拉取状态 + - 使用 MyBatis Plus 的 BaseMapper 实现历史记录查询 + - 使用轮询机制获取进度 + - 使用 Future.cancel() 取消异步任务 + +## 质疑与替代方案 + +记录在执行过程中提出的质疑和考虑的替代方案: + +- 质疑:是否需要使用消息队列实现手动触发拉取? + - 替代方案:使用消息队列实现手动触发拉取 + - 评估:消息队列可以解耦任务提交和任务执行,支持任务持久化和重试,但消息队列增加了系统复杂度,需要引入额外的依赖。异步线程池可以满足业务需求,不需要引入额外的依赖。 + +- 质疑:是否需要使用消息队列实现异步拉取执行? + - 替代方案:使用消息队列实现异步拉取执行 + - 评估:消息队列可以解耦任务提交和任务执行,支持任务持久化和重试,但消息队列增加了系统复杂度,需要引入额外的依赖。Spring 的 @Async 注解可以简化异步编程,提高开发效率,满足业务需求。 + +- 质疑:是否需要使用回调机制实现状态监控? + - 替代方案:使用回调机制实现状态监控 + - 评估:Salesforce Metadata API 不支持回调机制,需要使用 Webhook,增加系统复杂度。轮询机制可以定期检查任务状态,及时获取任务进度,满足业务需求。 + +- 质疑:是否需要使用缓存实现拉取历史记录? + - 替代方案:使用缓存实现拉取历史记录 + - 评估:缓存可以提高查询性能,减少数据库查询,但缓存可能导致数据不一致,缓存容量有限。MyBatis Plus 的 BaseMapper 可以提供基础的 CRUD 方法,简化开发,满足业务需求。 + +- 质疑:是否需要使用数据库查询实现拉取进度查询? + - 替代方案:使用数据库查询实现拉取进度查询 + - 评估:数据库查询可以实时获取进度,实现简单,但数据库查询性能差,可能影响系统性能。轮询机制可以及时获取进度,支持实时进度查询,满足业务需求。 + +- 质疑:是否需要使用标志位实现拉取取消? + - 替代方案:使用标志位实现拉取取消 + - 评估:标志位实现简单,不需要额外的依赖,但标志位不能真正取消任务,资源可能无法释放。Future.cancel() 可以取消异步任务,释放资源,满足业务需求。 + +## 结论 + +总结本次会话的结果,包括: + +- 完成的工作: + - 创建了 JobExecutionStatus 枚举类 + - 创建了 MetadataRetrieveController 控制器 + - 创建了 RetrieveRequest DTO + - 创建了 RetrieveResponse DTO + - 创建了 RetrieveProgressResponse DTO + - 创建了 IMetadataRetrieveService 服务接口 + - 创建了 MetadataRetrieveServiceImpl 服务实现 + - 创建了 RetrieveException 异常类 + - 创建了 MetadataRetrieveServiceImplTest 单元测试 + +- 达成的目标: + - 实现了手动触发拉取功能 + - 实现了异步拉取执行 + - 实现了状态监控 + - 实现了拉取历史记录 + - 实现了拉取进度查询 + - 实现了拉取取消功能 + +- 后续的行动计划: + - 进行集成测试,确保功能正常 + - 进行性能测试,确保性能满足要求 + - 进行安全测试,确保安全性满足要求 + - 编写用户文档,说明如何使用元数据拉取功能 + +- 需要跟进的事项: + - 监控异步线程池的使用情况 + - 监控状态轮询的频率 + - 监控拉取任务的执行情况 + - 监控 Redis 缓存的使用情况 + +## Design Update + +- [x] 是否需要更新 Canvas? +- [ ] Authentication.canvas +- [ ] 其他 Canvas 文件: ____________________ + +## 复现步骤 + +提供复现本次会话结果的具体步骤: + +1. 创建 JobExecutionStatus 枚举类 +2. 创建 MetadataRetrieveController 控制器 +3. 创建 RetrieveRequest DTO +4. 创建 RetrieveResponse DTO +5. 创建 RetrieveProgressResponse DTO +6. 创建 IMetadataRetrieveService 服务接口 +7. 创建 MetadataRetrieveServiceImpl 服务实现 +8. 创建 RetrieveException 异常类 +9. 创建 MetadataRetrieveServiceImplTest 单元测试 +10. 运行单元测试,确保所有测试通过 +11. 进行集成测试,确保功能正常