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

622 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 决策文档 | 完整的代码实现和测试用例 | 待使用 | 待改进 |