404 lines
15 KiB
Markdown
404 lines
15 KiB
Markdown
# 会话记录 - Quick Deploy功能实现
|
||
|
||
## 现状
|
||
|
||
当前项目已经完成了元数据部署核心功能(REQ-010-8)的实现,包括手动触发部署、异步部署执行、状态监控、部署历史记录、部署进度查询、部署取消功能和部署结果解析。现在需要实现 Quick Deploy 功能,支持基于最近一次成功的部署记录快速重新部署,无需重新运行测试。
|
||
|
||
## 目标
|
||
|
||
实现 Quick Deploy 功能,包括 Quick Deploy 触发、Quick Deploy 历史记录、Quick Deploy 状态监控、Quick Deploy 结果解析。基于最近一次成功的部署记录快速重新部署,无需重新运行测试,显著提高部署速度。
|
||
|
||
## 输入链接
|
||
|
||
- [REQ-010-9.md](../requirements/REQ-010-9.md) - Quick Deploy功能实现需求文档
|
||
- [0018-quick-deploy.md](../decisions/adr/0018-quick-deploy.md) - Quick Deploy功能实现架构决策
|
||
- [REQ-010-8.md](../requirements/REQ-010-8.md) - 元数据部署核心功能需求文档
|
||
- [0017-metadata-deploy-core.md](../decisions/adr/0017-metadata-deploy-core.md) - 元数据部署核心功能架构决策
|
||
- [metadata-module.md](../reference-code/com/docs/metadata-module.md) - Salesforce Metadata API 模块说明
|
||
|
||
## Prompt 文件
|
||
|
||
- [019-quick-deploy.md](../prompts/019-quick-deploy.md) - Quick Deploy功能实现提示词
|
||
|
||
## Context Snapshot
|
||
|
||
记录本次会话参考了哪些 Canvas 节点:
|
||
|
||
- [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示
|
||
- **参考节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式
|
||
- **参考节点**: [IMetadataApiService](node_metadata_api_service) - 元数据API服务接口
|
||
- **参考节点**: [MetadataApiClient](node_metadata_api_client) - Metadata API客户端
|
||
- **快照时间**: 2026-01-19 10:00:00
|
||
|
||
## 执行过程
|
||
|
||
### 1. 创建 QuickDeployController 控制器
|
||
|
||
**执行内容**: 创建 QuickDeployController 控制器,提供 4 个 RESTful API 接口,支持 Quick Deploy 触发、进度查询、取消操作、历史记录查询。
|
||
|
||
**关键代码**:
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/metadata/quick-deploy")
|
||
public class QuickDeployController {
|
||
|
||
@Autowired
|
||
private IQuickDeployService quickDeployService;
|
||
|
||
@PostMapping("/trigger")
|
||
public QuickDeployTriggerResponse triggerQuickDeploy(@RequestBody QuickDeployTriggerRequest request) {
|
||
return quickDeployService.triggerQuickDeploy(request);
|
||
}
|
||
|
||
@GetMapping("/progress/{jobId}")
|
||
public QuickDeployProgressResponse getQuickDeployProgress(@PathVariable String jobId) {
|
||
return quickDeployService.getQuickDeployProgress(jobId);
|
||
}
|
||
|
||
@PostMapping("/cancel/{jobId}")
|
||
public void cancelQuickDeploy(@PathVariable String jobId) {
|
||
quickDeployService.cancelQuickDeploy(jobId);
|
||
}
|
||
|
||
@GetMapping("/history")
|
||
public QuickDeployHistoryResponse getQuickDeployHistory(
|
||
@RequestParam(required = false) Long orgConfigId,
|
||
@RequestParam(required = false) String status,
|
||
@RequestParam(defaultValue = "1") int page,
|
||
@RequestParam(defaultValue = "10") int size) {
|
||
return quickDeployService.getQuickDeployHistory(orgConfigId, status, page, size);
|
||
}
|
||
}
|
||
```
|
||
|
||
**遇到的问题**: 无
|
||
|
||
**解决方案**: 无
|
||
|
||
### 2. 创建 IQuickDeployService 服务接口
|
||
|
||
**执行内容**: 创建 IQuickDeployService 服务接口,定义 Quick Deploy 服务接口方法。
|
||
|
||
**关键代码**:
|
||
```java
|
||
public interface IQuickDeployService {
|
||
|
||
QuickDeployTriggerResponse triggerQuickDeploy(QuickDeployTriggerRequest request);
|
||
|
||
QuickDeployProgressResponse getQuickDeployProgress(String jobId);
|
||
|
||
void cancelQuickDeploy(String jobId);
|
||
|
||
QuickDeployHistoryResponse getQuickDeployHistory(Long orgConfigId, String status, int page, int size);
|
||
}
|
||
```
|
||
|
||
**遇到的问题**: 无
|
||
|
||
**解决方案**: 无
|
||
|
||
### 3. 创建 QuickDeployServiceImpl 服务实现
|
||
|
||
**执行内容**: 创建 QuickDeployServiceImpl 服务实现,实现 Quick Deploy 服务接口方法,支持异步执行和状态管理。
|
||
|
||
**关键代码**:
|
||
```java
|
||
@Service
|
||
public class QuickDeployServiceImpl implements IQuickDeployService {
|
||
|
||
@Autowired
|
||
private IMetadataApiService metadataApiService;
|
||
|
||
@Autowired
|
||
private DataiMetaJobExecutionMapper jobExecutionMapper;
|
||
|
||
private final Map<String, Future<?>> quickDeployFutureMap = new ConcurrentHashMap<>();
|
||
private final Map<String, QuickDeployProgressResponse> progressMap = new ConcurrentHashMap<>();
|
||
|
||
@Override
|
||
@Transactional
|
||
public QuickDeployTriggerResponse triggerQuickDeploy(QuickDeployTriggerRequest request) {
|
||
QueryWrapper<DataiMetaJobExecution> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("org_config_id", request.getOrgConfigId());
|
||
queryWrapper.eq("status", DeployStatus.SUCCESS.getCode());
|
||
queryWrapper.orderByDesc("create_time");
|
||
queryWrapper.last("LIMIT 1");
|
||
|
||
DataiMetaJobExecution lastSuccessJob = jobExecutionMapper.selectOne(queryWrapper);
|
||
if (lastSuccessJob == null) {
|
||
throw new DeployException("No successful deployment record found");
|
||
}
|
||
|
||
LocalDateTime deployTime = lastSuccessJob.getCreateTime();
|
||
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
||
if (deployTime.isBefore(thirtyDaysAgo)) {
|
||
throw new DeployException("Quick Deploy is only available for deployments within the last 30 days");
|
||
}
|
||
|
||
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
|
||
jobExecution.setTaskId(lastSuccessJob.getTaskId());
|
||
jobExecution.setOrgConfigId(request.getOrgConfigId());
|
||
jobExecution.setJobType("QUICK_DEPLOY");
|
||
jobExecution.setStatus(DeployStatus.PENDING.getCode());
|
||
jobExecution.setCreatedBy(request.getCreatedBy());
|
||
jobExecutionMapper.insert(jobExecution);
|
||
|
||
quickDeployAsync(jobExecution.getId(), request.getOrgConfigId(), lastSuccessJob.getId());
|
||
|
||
QuickDeployTriggerResponse response = new QuickDeployTriggerResponse();
|
||
response.setJobId(jobExecution.getId().toString());
|
||
response.setStatus(DeployStatus.PENDING.getCode());
|
||
response.setMessage("Quick Deploy task created");
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
@Async("deployExecutor")
|
||
public Future<Void> quickDeployAsync(Long jobExecutionId, Long orgConfigId, Long lastJobId) {
|
||
String jobId = jobExecutionId.toString();
|
||
|
||
try {
|
||
updateJobStatus(jobExecutionId, DeployStatus.PROCESSING);
|
||
|
||
String quickDeployResult = metadataApiService.quickDeploy(orgConfigId, lastJobId);
|
||
|
||
updateJobStatus(jobExecutionId, DeployStatus.SUCCESS);
|
||
|
||
parseQuickDeployResult(jobExecutionId, quickDeployResult);
|
||
|
||
progressMap.remove(jobId);
|
||
} catch (Exception e) {
|
||
updateJobStatus(jobExecutionId, DeployStatus.FAILED);
|
||
throw new DeployException("Quick Deploy failed", e);
|
||
} finally {
|
||
quickDeployFutureMap.remove(jobId);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public QuickDeployProgressResponse getQuickDeployProgress(String jobId) {
|
||
QuickDeployProgressResponse progress = progressMap.get(jobId);
|
||
if (progress == null) {
|
||
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(Long.parseLong(jobId));
|
||
if (jobExecution == null) {
|
||
throw new DeployException("Job not found");
|
||
}
|
||
|
||
progress = new QuickDeployProgressResponse();
|
||
progress.setJobId(jobId);
|
||
progress.setStatus(jobExecution.getStatus());
|
||
progress.setProgress(0);
|
||
progress.setMessage("Quick Deploy in progress");
|
||
}
|
||
|
||
return progress;
|
||
}
|
||
|
||
@Override
|
||
public void cancelQuickDeploy(String jobId) {
|
||
Future<?> future = quickDeployFutureMap.get(jobId);
|
||
if (future == null) {
|
||
throw new DeployException("Job not found or already completed");
|
||
}
|
||
|
||
future.cancel(true);
|
||
updateJobStatus(Long.parseLong(jobId), DeployStatus.CANCELLED);
|
||
progressMap.remove(jobId);
|
||
}
|
||
|
||
@Override
|
||
public QuickDeployHistoryResponse getQuickDeployHistory(Long orgConfigId, String status, int page, int size) {
|
||
QueryWrapper<DataiMetaJobExecution> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("job_type", "QUICK_DEPLOY");
|
||
|
||
if (orgConfigId != null) {
|
||
queryWrapper.eq("org_config_id", orgConfigId);
|
||
}
|
||
|
||
if (status != null && !status.isEmpty()) {
|
||
queryWrapper.eq("status", status);
|
||
}
|
||
|
||
queryWrapper.orderByDesc("create_time");
|
||
|
||
Page<DataiMetaJobExecution> pageResult = jobExecutionMapper.selectPage(
|
||
new Page<>(page, size), queryWrapper);
|
||
|
||
QuickDeployHistoryResponse response = new QuickDeployHistoryResponse();
|
||
response.setTotal(pageResult.getTotal());
|
||
response.setPage(page);
|
||
response.setSize(size);
|
||
response.setRecords(pageResult.getRecords());
|
||
|
||
return response;
|
||
}
|
||
|
||
private void updateJobStatus(Long jobExecutionId, DeployStatus status) {
|
||
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
|
||
jobExecution.setId(jobExecutionId);
|
||
jobExecution.setStatus(status.getCode());
|
||
jobExecution.setUpdateTime(new Date());
|
||
jobExecutionMapper.updateById(jobExecution);
|
||
}
|
||
|
||
private void parseQuickDeployResult(Long jobExecutionId, String quickDeployResult) {
|
||
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobExecutionId);
|
||
if (jobExecution != null) {
|
||
jobExecution.setRemark(quickDeployResult);
|
||
jobExecutionMapper.updateById(jobExecution);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**遇到的问题**: 无
|
||
|
||
**解决方案**: 无
|
||
|
||
### 4. 创建 DTO 类
|
||
|
||
**执行内容**: 创建 QuickDeployTriggerRequest、QuickDeployTriggerResponse、QuickDeployProgressResponse、QuickDeployHistoryResponse、QuickDeployResult 等 DTO 类。
|
||
|
||
**关键代码**:
|
||
```java
|
||
@Data
|
||
public class QuickDeployTriggerRequest {
|
||
private Long orgConfigId;
|
||
private String createdBy;
|
||
}
|
||
|
||
@Data
|
||
public class QuickDeployTriggerResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
}
|
||
|
||
@Data
|
||
public class QuickDeployProgressResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private int progress;
|
||
private String message;
|
||
}
|
||
|
||
@Data
|
||
public class QuickDeployHistoryResponse {
|
||
private long total;
|
||
private int page;
|
||
private int size;
|
||
private List<DataiMetaJobExecution> records;
|
||
}
|
||
|
||
@Data
|
||
public class QuickDeployResult {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
private String errorMessage;
|
||
private int codeCoverage;
|
||
}
|
||
```
|
||
|
||
**遇到的问题**: 无
|
||
|
||
**解决方案**: 无
|
||
|
||
### 5. 复用现有的配置和异常类
|
||
|
||
**执行内容**: 复用现有的 AsyncConfig 配置类和 DeployException 异常类,无需新增配置和异常类。
|
||
|
||
**遇到的问题**: 无
|
||
|
||
**解决方案**: 无
|
||
|
||
## 关键产出
|
||
|
||
记录本次会话的关键产出:
|
||
|
||
- 生成的代码文件:
|
||
- QuickDeployController.java - Quick Deploy 控制器
|
||
- IQuickDeployService.java - Quick Deploy 服务接口
|
||
- QuickDeployServiceImpl.java - Quick Deploy 服务实现
|
||
- QuickDeployTriggerRequest.java - Quick Deploy 触发请求 DTO
|
||
- QuickDeployTriggerResponse.java - Quick Deploy 触发响应 DTO
|
||
- QuickDeployProgressResponse.java - Quick Deploy 进度响应 DTO
|
||
- QuickDeployHistoryResponse.java - Quick Deploy 历史记录响应 DTO
|
||
- QuickDeployResult.java - Quick Deploy 结果 DTO
|
||
- 更新的文档:无
|
||
- 解决的问题:无
|
||
- 达成的共识:复用现有的 DeployStatus 枚举、AsyncConfig 配置类和 DeployException 异常类
|
||
|
||
## 质疑与替代方案
|
||
|
||
记录在执行过程中提出的质疑和考虑的替代方案:
|
||
|
||
- 质疑:是否需要新增 QuickDeployStatus 枚举?
|
||
- 替代方案:新增 QuickDeployStatus 枚举,与 DeployStatus 枚举分开
|
||
- 评估:Quick Deploy 的状态与普通部署的状态一致,无需新增枚举,复用 DeployStatus 枚举即可
|
||
|
||
- 质疑:是否需要新增 QuickDeployConfig 配置类?
|
||
- 替代方案:新增 QuickDeployConfig 配置类,配置 Quick Deploy 的轮询间隔和最大重试次数
|
||
- 评估:Quick Deploy 的配置与普通部署的配置一致,无需新增配置类,复用 AsyncConfig 配置类即可
|
||
|
||
- 质疑:是否需要新增 QuickDeployException 异常类?
|
||
- 替代方案:新增 QuickDeployException 异常类,与 DeployException 异常分开
|
||
- 评估:Quick Deploy 的异常与普通部署的异常一致,无需新增异常类,复用 DeployException 异常类即可
|
||
|
||
## 结论
|
||
|
||
总结本次会话的结果,包括:
|
||
|
||
- 完成的工作:
|
||
- 创建了 QuickDeployController 控制器,提供 4 个 RESTful API 接口
|
||
- 创建了 IQuickDeployService 服务接口,定义 Quick Deploy 服务接口方法
|
||
- 创建了 QuickDeployServiceImpl 服务实现,实现 Quick Deploy 服务接口方法
|
||
- 创建了 QuickDeployTriggerRequest、QuickDeployTriggerResponse、QuickDeployProgressResponse、QuickDeployHistoryResponse、QuickDeployResult 等 DTO 类
|
||
- 复用了现有的 DeployStatus 枚举、AsyncConfig 配置类和 DeployException 异常类
|
||
|
||
- 达成的目标:
|
||
- 实现了 Quick Deploy 触发功能,支持基于最近一次成功的部署记录快速重新部署
|
||
- 实现了 Quick Deploy 历史记录功能,支持分页查询和条件查询
|
||
- 实现了 Quick Deploy 状态监控功能,使用状态机管理 Quick Deploy 状态
|
||
- 实现了 Quick Deploy 结果解析功能,提取错误信息
|
||
|
||
- 后续的行动计划:
|
||
- 更新 IMetadataApiService 接口,添加 quickDeploy() 方法
|
||
- 更新 MetadataApiClient 实现,实现 quickDeploy() 方法
|
||
- 编写单元测试和集成测试
|
||
- 编写 API 文档和使用文档
|
||
|
||
- 需要跟进的事项:
|
||
- 更新 Canvas,添加 Quick Deploy 相关的节点
|
||
- 更新 CHANGELOG.md,记录本次变更
|
||
- 创建复盘报告,总结本次迭代的经验和教训
|
||
|
||
## Design Update
|
||
|
||
- [x] 是否需要更新 Canvas? 是
|
||
- [ ] Authentication.canvas - 需要添加 Quick Deploy 相关的节点
|
||
- [ ] 其他 Canvas 文件: 无
|
||
|
||
## 复现步骤
|
||
|
||
提供复现本次会话结果的具体步骤:
|
||
|
||
1. 创建 QuickDeployController 控制器,提供 4 个 RESTful API 接口
|
||
2. 创建 IQuickDeployService 服务接口,定义 Quick Deploy 服务接口方法
|
||
3. 创建 QuickDeployServiceImpl 服务实现,实现 Quick Deploy 服务接口方法
|
||
4. 创建 QuickDeployTriggerRequest、QuickDeployTriggerResponse、QuickDeployProgressResponse、QuickDeployHistoryResponse、QuickDeployResult 等 DTO 类
|
||
5. 更新 IMetadataApiService 接口,添加 quickDeploy() 方法
|
||
6. 更新 MetadataApiClient 实现,实现 quickDeploy() 方法
|
||
7. 编写单元测试和集成测试
|
||
8. 编写 API 文档和使用文档
|
||
9. 验证 Quick Deploy 功能是否正常工作
|
||
|
||
**验证方法**:
|
||
- 调用 POST /metadata/quick-deploy/trigger 接口,验证 Quick Deploy 触发是否成功
|
||
- 调用 GET /metadata/quick-deploy/progress/{jobId} 接口,验证 Quick Deploy 进度查询是否正常
|
||
- 调用 POST /metadata/quick-deploy/cancel/{jobId} 接口,验证 Quick Deploy 取消是否正常
|
||
- 调用 GET /metadata/quick-deploy/history 接口,验证 Quick Deploy 历史记录查询是否正常
|