datai/docs/archive/prompts/018-metadata-deploy-core.md

622 lines
20 KiB
Markdown
Raw Normal View History

# Prompt - 元数据部署核心功能实现
## 输入引用
引用相关的 docs 文档链接:
- [REQ-010-8.md](../requirements/REQ-010-8.md) - 元数据部署核心功能需求文档
- [0017-metadata-deploy-core.md](../decisions/adr/0017-metadata-deploy-core.md) - 元数据部署核心功能架构决策
- [REQ-010-6.md](../requirements/REQ-010-6.md) - 元数据拉取核心功能需求文档
- [REQ-010-7.md](../requirements/REQ-010-7.md) - 文件存储和解压处理需求文档
- [004-元数据部署网页资料链接地址](../reference-code/metadata/004-元数据部署网页资料链接地址) - 官方文档和开源项目参考
- [metadata-module.md](../reference-code/com/docs/metadata-module.md) - Salesforce Metadata API 模块说明(唯一真源)
## Context Maps
强制列出本次 Prompt 依赖的 Canvas 文件:
- [Authentication.canvas](../../Authentication.canvas) - 项目架构视觉化展示
- **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式
- **相关节点**: [SessionManager](node_session_manager_detail) - 会话管理,提供登录服务
## 目标
实现元数据部署核心功能,包括:
1. 实现手动触发部署功能支持选择任务ID、组织配置和上传Zip文件
2. 实现异步部署执行使用Spring的@Async注解和自定义线程池
3. 实现状态监控使用状态机管理部署状态Pending/Processing/Success/Failed/Partial_Success/Cancelled
4. 实现部署历史记录,支持分页查询和条件查询
5. 实现部署进度查询,支持实时进度查询和进度百分比显示
6. 实现部署取消功能使用Future.cancel()取消异步任务
7. 实现部署结果解析,提取错误信息和代码覆盖率
## 输出格式
### 代码结构
```
datai-salesforce-metadata/
├── src/main/java/com/datai/salesforce/metadata/deploy/
│ ├── controller/
│ │ └── MetadataDeployController.java # 元数据部署控制器
│ ├── service/
│ │ ├── IMetadataDeployService.java # 元数据部署服务接口
│ │ └── MetadataDeployServiceImpl.java # 元数据部署服务实现
│ ├── enums/
│ │ └── DeployStatus.java # 部署状态枚举
│ ├── dto/
│ │ ├── DeployTriggerRequest.java # 部署触发请求
│ │ ├── DeployTriggerResponse.java # 部署触发响应
│ │ ├── DeployProgressResponse.java # 部署进度响应
│ │ ├── DeployHistoryResponse.java # 部署历史响应
│ │ └── DeployResult.java # 部署结果
│ ├── config/
│ │ └── AsyncConfig.java # 异步配置
│ └── exception/
│ └── DeployException.java # 部署异常
├── src/main/resources/
│ ├── application.yml # 应用配置
│ └── logback-spring.xml # 日志配置
└── src/test/java/com/datai/salesforce/metadata/deploy/
├── service/
│ └── MetadataDeployServiceTest.java # 部署服务测试
└── controller/
└── MetadataDeployControllerTest.java # 部署控制器测试
```
### 代码示例
#### 1. DeployStatus.java
```java
package com.datai.salesforce.metadata.deploy.enums;
import lombok.Getter;
@Getter
public enum DeployStatus {
PENDING(0, "待处理"),
PROCESSING(1, "处理中"),
SUCCESS(2, "成功"),
FAILED(3, "失败"),
PARTIAL_SUCCESS(4, "部分成功"),
CANCELLED(5, "已取消");
private final Integer code;
private final String desc;
DeployStatus(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static DeployStatus fromCode(Integer code) {
for (DeployStatus status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("Invalid status code: " + code);
}
}
```
#### 2. DeployTriggerRequest.java
```java
package com.datai.salesforce.metadata.deploy.dto;
import lombok.Data;
@Data
public class DeployTriggerRequest {
private Long taskId;
private Long orgConfigId;
private String zipFilePath;
private String createdBy;
}
```
#### 3. DeployTriggerResponse.java
```java
package com.datai.salesforce.metadata.deploy.dto;
import lombok.Data;
@Data
public class DeployTriggerResponse {
private String jobId;
private Integer status;
private String message;
}
```
#### 4. DeployProgressResponse.java
```java
package com.datai.salesforce.metadata.deploy.dto;
import lombok.Data;
@Data
public class DeployProgressResponse {
private String jobId;
private Integer status;
private Integer progress;
private Integer processedComponents;
private Integer totalComponents;
private String message;
}
```
#### 5. DeployHistoryResponse.java
```java
package com.datai.salesforce.metadata.deploy.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class DeployHistoryResponse {
private Long id;
private Long taskId;
private Long orgConfigId;
private Integer status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private String createdBy;
private List<DeployResult> results;
}
```
#### 6. DeployResult.java
```java
package com.datai.salesforce.metadata.deploy.dto;
import lombok.Data;
import java.util.List;
@Data
public class DeployResult {
private String componentType;
private String fileName;
private Boolean success;
private String errorMessage;
private Integer lineNumber;
private Integer columnNumber;
}
```
#### 7. IMetadataDeployService.java
```java
package com.datai.salesforce.metadata.deploy.service;
import com.datai.salesforce.metadata.deploy.dto.*;
import java.util.List;
import java.util.concurrent.Future;
public interface IMetadataDeployService {
DeployTriggerResponse triggerDeploy(DeployTriggerRequest request);
Future<Void> deployAsync(Long jobExecutionId, Long taskId, Long orgConfigId, String zipFilePath);
DeployProgressResponse getProgress(String jobId);
List<DeployHistoryResponse> getHistory(Long taskId, Long orgConfigId, Integer pageNum, Integer pageSize);
boolean cancelDeploy(String jobId);
void parseDeployResult(Long jobExecutionId, String deployResult);
}
```
#### 8. MetadataDeployServiceImpl.java
```java
package com.datai.salesforce.metadata.deploy.service;
import com.datai.salesforce.metadata.deploy.dto.*;
import com.datai.salesforce.metadata.deploy.enums.DeployStatus;
import com.datai.salesforce.metadata.entity.DataiMetaJobExecution;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.mapper.DataiMetaJobExecutionMapper;
import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper;
import com.datai.salesforce.metadata.service.IMetadataApiService;
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.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
@Slf4j
@Service
public class MetadataDeployServiceImpl implements IMetadataDeployService {
@Autowired
private IMetadataApiService metadataApiService;
@Autowired
private DataiMetaJobExecutionMapper jobExecutionMapper;
@Autowired
private DataiMetaTaskMapper taskMapper;
private final Map<String, Future<?>> deployFutureMap = new ConcurrentHashMap<>();
private final Map<String, DeployProgressResponse> progressMap = new ConcurrentHashMap<>();
@Override
@Transactional
public DeployTriggerResponse triggerDeploy(DeployTriggerRequest request) {
DataiMetaTask task = taskMapper.selectById(request.getTaskId());
if (task == null) {
throw new RuntimeException("Task not found");
}
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
jobExecution.setTaskId(request.getTaskId());
jobExecution.setOrgConfigId(request.getOrgConfigId());
jobExecution.setJobType(task.getTaskType());
jobExecution.setStatus(DeployStatus.PENDING.getCode());
jobExecution.setCreatedBy(request.getCreatedBy());
jobExecutionMapper.insert(jobExecution);
deployAsync(jobExecution.getId(), request.getTaskId(), request.getOrgConfigId(), request.getZipFilePath());
DeployTriggerResponse response = new DeployTriggerResponse();
response.setJobId(jobExecution.getId().toString());
response.setStatus(DeployStatus.PENDING.getCode());
response.setMessage("Deploy task created");
return response;
}
@Override
@Async("deployExecutor")
public Future<Void> deployAsync(Long jobExecutionId, Long taskId, Long orgConfigId, String zipFilePath) {
String jobId = jobExecutionId.toString();
try {
updateJobStatus(jobExecutionId, DeployStatus.PROCESSING);
File zipFile = new File(zipFilePath);
String deployResult = metadataApiService.deploy(orgConfigId, zipFile);
updateJobStatus(jobExecutionId, DeployStatus.SUCCESS);
parseDeployResult(jobExecutionId, deployResult);
log.info("Deploy completed successfully: {}", jobId);
} catch (Exception e) {
log.error("Deploy failed: {}", jobId, e);
updateJobStatus(jobExecutionId, DeployStatus.FAILED);
throw new RuntimeException("Deploy failed", e);
} finally {
deployFutureMap.remove(jobId);
}
return null;
}
@Override
public DeployProgressResponse getProgress(String jobId) {
DeployProgressResponse progress = progressMap.get(jobId);
if (progress == null) {
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(Long.parseLong(jobId));
progress = new DeployProgressResponse();
progress.setJobId(jobId);
progress.setStatus(jobExecution.getStatus());
progress.setProgress(0);
progress.setMessage("Deploy task not found in progress map");
}
return progress;
}
@Override
public List<DeployHistoryResponse> getHistory(Long taskId, Long orgConfigId, Integer pageNum, Integer pageSize) {
return jobExecutionMapper.selectDeployHistory(taskId, orgConfigId, pageNum, pageSize);
}
@Override
public boolean cancelDeploy(String jobId) {
Future<?> future = deployFutureMap.get(jobId);
if (future != null) {
boolean cancelled = future.cancel(true);
if (cancelled) {
updateJobStatus(Long.parseLong(jobId), DeployStatus.CANCELLED);
progressMap.remove(jobId);
log.info("Deploy cancelled: {}", jobId);
}
return cancelled;
}
return false;
}
@Override
@Transactional
public void parseDeployResult(Long jobExecutionId, String deployResult) {
log.info("Parsing deploy result for job: {}", jobExecutionId);
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobExecutionId);
jobExecution.setDeployResult(deployResult);
jobExecutionMapper.updateById(jobExecution);
}
private void updateJobStatus(Long jobExecutionId, DeployStatus status) {
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
jobExecution.setId(jobExecutionId);
jobExecution.setStatus(status.getCode());
jobExecutionMapper.updateById(jobExecution);
}
}
```
#### 9. MetadataDeployController.java
```java
package com.datai.salesforce.metadata.deploy.controller;
import com.datai.salesforce.metadata.deploy.dto.*;
import com.datai.salesforce.metadata.deploy.service.IMetadataDeployService;
import com.datai.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/metadata/deploy")
public class MetadataDeployController {
@Autowired
private IMetadataDeployService metadataDeployService;
@PostMapping("/trigger")
public R<DeployTriggerResponse> triggerDeploy(@RequestBody DeployTriggerRequest request) {
log.info("Trigger deploy: taskId={}, orgConfigId={}", request.getTaskId(), request.getOrgConfigId());
DeployTriggerResponse response = metadataDeployService.triggerDeploy(request);
return R.ok(response);
}
@GetMapping("/progress/{jobId}")
public R<DeployProgressResponse> getProgress(@PathVariable String jobId) {
log.info("Get deploy progress: jobId={}", jobId);
DeployProgressResponse response = metadataDeployService.getProgress(jobId);
return R.ok(response);
}
@GetMapping("/history")
public R<List<DeployHistoryResponse>> getHistory(
@RequestParam(required = false) Long taskId,
@RequestParam(required = false) Long orgConfigId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
log.info("Get deploy history: taskId={}, orgConfigId={}, pageNum={}, pageSize={}",
taskId, orgConfigId, pageNum, pageSize);
List<DeployHistoryResponse> history = metadataDeployService.getHistory(taskId, orgConfigId, pageNum, pageSize);
return R.ok(history);
}
@PostMapping("/cancel/{jobId}")
public R<Boolean> cancelDeploy(@PathVariable String jobId) {
log.info("Cancel deploy: jobId={}", jobId);
boolean cancelled = metadataDeployService.cancelDeploy(jobId);
return R.ok(cancelled);
}
}
```
#### 10. AsyncConfig.java
```java
package com.datai.salesforce.metadata.deploy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("deployExecutor")
public Executor deployExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("deploy-async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
```
#### 11. application.yml
```yaml
spring:
task:
execution:
pool:
core-size: 5
max-size: 10
queue-capacity: 100
thread-name-prefix: async-
keep-alive: 60s
metadata:
deploy:
poll-interval: 5000 # 状态轮询间隔(毫秒)
max-retry: 3 # 最大重试次数
```
## 约束
### 技术栈约束
- 必须基于 Spring Boot 3
- 必须使用 Java 17+
- 必须使用 MyBatis Plus
- 必须使用 MySQL
### 性能约束
- 部署触发响应时间 < 1s
- 状态轮询任务执行时间 < 1s
- 进度查询响应时间 < 500ms
- 历史记录查询响应时间 < 1s
- 并发触发 10 个部署任务无性能下降
### 安全性约束
- 部署操作需要认证
- 必须使用 SessionManager 进行会话管理
- 自动重新登录功能正常
- 部署数据传输加密
- 敏感信息不记录到日志
- 部署历史记录权限控制
### 兼容性约束
- 必须使用现有的集成核心功能进行 Salesforce API 调用
- 必须使用现有的认证模块进行 OAuth 认证
- 必须遵循现有的异常处理机制
- 必须遵循现有的日志记录规范
## Rule Set
"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。"
**具体规则**
- 必须使用 Canvas 中定义的类名和方法名
- 必须遵循 Canvas 中定义的调用关系
- 必须参考 Canvas 中的流程图逻辑
- 必须使用 SessionManager 进行会话管理和自动重新登录
- 必须使用现有的认证模块进行 OAuth 认证
- 必须使用现有的集成核心功能进行 API 调用
- 必须遵循现有的异常处理机制
- 必须遵循现有的日志记录规范
- 必须使用状态机模式管理部署状态
- 必须使用 Spring 的 @Async 注解实现异步执行
- 必须使用自定义线程池管理异步任务
- 必须使用轮询机制检查部署状态
## 验收标准
### 功能验收标准
- [ ] 能够成功手动触发部署
- [ ] 支持选择任务ID
- [ ] 支持选择组织配置
- [ ] 支持上传Zip文件
- [ ] 触发成功返回Job ID
- [ ] API接口符合RESTful规范
- [ ] 异步部署执行正常工作
- [ ] 使用线程池管理异步任务
- [ ] 支持并发部署
- [ ] 部署任务不阻塞系统响应
- [ ] 状态监控正常工作
- [ ] 使用状态机管理部署状态
- [ ] 支持状态查询
- [ ] 状态更新及时
- [ ] 部署历史记录成功
- [ ] 支持分页查询
- [ ] 支持条件查询
- [ ] 历史记录完整
- [ ] 部署进度查询成功
- [ ] 支持实时进度查询
- [ ] 进度信息准确
- [ ] 支持进度百分比显示
- [ ] 部署取消功能正常工作
- [ ] 支持取消正在进行的部署任务
- [ ] 取消后资源正确释放
- [ ] 取消状态更新及时
- [ ] 部署结果解析成功
- [ ] 错误信息提取正确
- [ ] 代码覆盖率提取正确
- [ ] 支持多种部署状态
- [ ] 错误信息详细
### 性能验收标准
- [ ] 部署触发响应时间 < 1s
- [ ] 并发触发 10 个部署任务无性能下降
- [ ] 状态轮询任务执行时间 < 1s
- [ ] 不影响系统正常运行
- [ ] 进度查询响应时间 < 500ms
- [ ] 并发查询 100 次无性能下降
- [ ] 历史记录查询响应时间 < 1s
- [ ] 分页查询 100 条记录无性能下降
### 安全性验收标准
- [ ] 部署操作需要认证
- [ ] 使用 SessionManager 进行会话管理
- [ ] 自动重新登录功能正常
- [ ] 部署数据传输加密
- [ ] 敏感信息不记录到日志
- [ ] 部署历史记录权限控制
### 代码质量验收标准
- [ ] 代码符合项目编码规范
- [ ] 有清晰的注释
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试覆盖主要场景
- [ ] 通过代码审查
## 风险
### 异步执行风险
- **风险描述**: 异步执行机制复杂可能导致状态管理困难
- **缓解措施**: 使用状态机模式管理状态,使用 Future 对象跟踪异步任务
- **监控指标**: 监控异步任务执行时间和状态转换
### 状态轮询风险
- **风险描述**: 状态轮询频率不当可能导致 API 限流
- **缓解措施**: 设置合理的轮询间隔(如 5 秒),使用指数退避策略
- **监控指标**: 监控 API 调用次数和响应时间
### 部署取消风险
- **风险描述**: 部署取消功能复杂可能导致资源泄漏
- **缓解措施**: 使用 Future.cancel() 安全取消,确保资源正确释放
- **监控指标**: 监控取消操作的成功率和资源释放情况
### 历史记录风险
- **风险描述**: 部署历史记录过多可能影响查询性能
- **缓解措施**: 使用分页查询,定期清理过期历史记录
- **监控指标**: 监控历史记录查询性能
### 进度查询风险
- **风险描述**: 进度查询不准确可能导致用户体验差
- **缓解措施**: 使用缓存提高查询性能,优化轮询策略
- **监控指标**: 监控进度查询的准确性和响应时间
### 结果解析风险
- **风险描述**: 部署结果解析不完善可能导致错误信息不准确
- **缓解措施**: 使用 JSON 解析库和正则表达式双重验证,充分测试
- **监控指标**: 监控结果解析的成功率和准确性
## 使用记录
| 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 |
|------|---------|---------|---------|------|----------|
| 2026-01-19 | 实现元数据部署核心功能 | REQ-010-8 需求文档、ADR-0017 决策文档 | 完整的代码实现和测试用例 | 待使用 | 待改进 |