481 lines
16 KiB
Markdown
481 lines
16 KiB
Markdown
# Prompt - Quick Deploy功能实现
|
||
|
||
## 输入引用
|
||
|
||
引用相关的 docs 文档链接:
|
||
|
||
- [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 模块说明
|
||
|
||
## Context Maps
|
||
|
||
强制列出本次 Prompt 依赖的 Canvas 文件:
|
||
|
||
- [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示
|
||
- **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式
|
||
- **相关节点**: [IMetadataApiService](node_metadata_api_service) - 元数据API服务接口
|
||
- **相关节点**: [MetadataApiClient](node_metadata_api_client) - Metadata API客户端
|
||
|
||
## 目标
|
||
|
||
实现 Quick Deploy 功能,包括 Quick Deploy 触发、Quick Deploy 历史记录、Quick Deploy 状态监控、Quick Deploy 结果解析。基于最近一次成功的部署记录快速重新部署,无需重新运行测试,显著提高部署速度。
|
||
|
||
## 输出格式
|
||
|
||
### 代码结构
|
||
|
||
```
|
||
datai-salesforce-metadata/
|
||
├── deploy/
|
||
│ ├── controller/
|
||
│ │ ├── MetadataDeployController.java
|
||
│ │ └── QuickDeployController.java
|
||
│ ├── service/
|
||
│ │ ├── IMetadataDeployService.java
|
||
│ │ ├── MetadataDeployServiceImpl.java
|
||
│ │ ├── IQuickDeployService.java
|
||
│ │ └── QuickDeployServiceImpl.java
|
||
│ ├── enums/
|
||
│ │ └── DeployStatus.java
|
||
│ ├── dto/
|
||
│ │ ├── DeployTriggerRequest.java
|
||
│ │ ├── DeployTriggerResponse.java
|
||
│ │ ├── DeployProgressResponse.java
|
||
│ │ ├── DeployHistoryResponse.java
|
||
│ │ ├── DeployResult.java
|
||
│ │ ├── QuickDeployTriggerRequest.java
|
||
│ │ ├── QuickDeployTriggerResponse.java
|
||
│ │ ├── QuickDeployProgressResponse.java
|
||
│ │ ├── QuickDeployHistoryResponse.java
|
||
│ │ └── QuickDeployResult.java
|
||
│ ├── config/
|
||
│ │ └── AsyncConfig.java
|
||
│ └── exception/
|
||
│ └── DeployException.java
|
||
```
|
||
|
||
### 代码示例(语言:Java)
|
||
|
||
#### 1. QuickDeployController 控制器
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.controller;
|
||
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerRequest;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployProgressResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployHistoryResponse;
|
||
import com.datai.salesforce.metadata.deploy.service.IQuickDeployService;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.web.bind.annotation.*;
|
||
|
||
@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 服务接口
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.service;
|
||
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerRequest;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployProgressResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployHistoryResponse;
|
||
|
||
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 服务实现
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.service;
|
||
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerRequest;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployTriggerResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployProgressResponse;
|
||
import com.datai.salesforce.metadata.deploy.dto.QuickDeployHistoryResponse;
|
||
import com.datai.salesforce.metadata.deploy.enums.DeployStatus;
|
||
import com.datai.salesforce.metadata.deploy.exception.DeployException;
|
||
import com.datai.salesforce.metadata.entity.DataiMetaJobExecution;
|
||
import com.datai.salesforce.metadata.mapper.DataiMetaJobExecutionMapper;
|
||
import com.datai.salesforce.metadata.service.IMetadataApiService;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
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.time.LocalDateTime;
|
||
import java.util.Date;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import java.util.concurrent.Future;
|
||
|
||
@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. QuickDeployTriggerRequest DTO
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.dto;
|
||
|
||
import lombok.Data;
|
||
|
||
@Data
|
||
public class QuickDeployTriggerRequest {
|
||
private Long orgConfigId;
|
||
private String createdBy;
|
||
}
|
||
```
|
||
|
||
#### 5. QuickDeployTriggerResponse DTO
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.dto;
|
||
|
||
import lombok.Data;
|
||
|
||
@Data
|
||
public class QuickDeployTriggerResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
}
|
||
```
|
||
|
||
#### 6. QuickDeployProgressResponse DTO
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.dto;
|
||
|
||
import lombok.Data;
|
||
|
||
@Data
|
||
public class QuickDeployProgressResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private int progress;
|
||
private String message;
|
||
}
|
||
```
|
||
|
||
#### 7. QuickDeployHistoryResponse DTO
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.dto;
|
||
|
||
import com.datai.salesforce.metadata.entity.DataiMetaJobExecution;
|
||
import lombok.Data;
|
||
|
||
import java.util.List;
|
||
|
||
@Data
|
||
public class QuickDeployHistoryResponse {
|
||
private long total;
|
||
private int page;
|
||
private int size;
|
||
private List<DataiMetaJobExecution> records;
|
||
}
|
||
```
|
||
|
||
#### 8. QuickDeployResult DTO
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.deploy.dto;
|
||
|
||
import lombok.Data;
|
||
|
||
@Data
|
||
public class QuickDeployResult {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
private String errorMessage;
|
||
private int codeCoverage;
|
||
}
|
||
```
|
||
|
||
## 约束
|
||
|
||
### 技术栈限制
|
||
|
||
- 必须基于 Spring Boot 3 + MyBatis Plus + MySQL 技术栈
|
||
- 必须使用现有的 IMetadataApiService 接口进行 Salesforce API 调用
|
||
- 必须使用现有的 DataiMetaJobExecution 实体和 Mapper
|
||
|
||
### 架构约束
|
||
|
||
- 必须遵循 Authentication.canvas 中定义的架构和调用关系
|
||
- 必须在 datai-salesforce-metadata 模块下实现
|
||
- 必须使用 RESTful API 设计规范
|
||
|
||
### 性能要求
|
||
|
||
- Quick Deploy 触发响应时间 < 1s
|
||
- Quick Deploy 状态轮询任务执行时间 < 1s
|
||
- Quick Deploy 进度查询响应时间 < 500ms
|
||
- Quick Deploy 历史记录查询响应时间 < 1s
|
||
|
||
### 安全性要求
|
||
|
||
- Quick Deploy 操作需要认证
|
||
- 部署数据传输加密
|
||
- 敏感信息不记录到日志
|
||
|
||
## Rule Set
|
||
|
||
"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。"
|
||
|
||
**具体规则**:
|
||
- 必须使用 Canvas 中定义的类名和方法名
|
||
- 必须遵循 Canvas 中定义的调用关系
|
||
- 必须参考 Canvas 中的流程图逻辑
|
||
- 必须复用现有的 DeployStatus 枚举和状态管理逻辑
|
||
- 必须复用现有的 AsyncConfig 配置类和线程池
|
||
|
||
## 验收标准
|
||
|
||
### 功能完整性
|
||
|
||
- Quick Deploy 触发成功,支持选择最近一次成功的部署记录
|
||
- Quick Deploy 验证成功,检查部署记录是否在 30 天内
|
||
- Quick Deploy 历史记录成功,支持分页查询和条件查询
|
||
- Quick Deploy 状态监控正常工作,使用状态机管理 Quick Deploy 状态
|
||
- Quick Deploy 结果解析成功,错误信息提取正确
|
||
|
||
### 代码正确性
|
||
|
||
- 代码符合项目编码规范,有清晰的注释
|
||
- 单元测试覆盖率 > 80%
|
||
- 集成测试通过率 100%
|
||
- 无严重的代码质量问题
|
||
|
||
### 文档准确性
|
||
|
||
- API 文档完整,包含接口说明、参数说明、返回值说明
|
||
- 代码注释清晰,包含方法说明、参数说明、返回值说明
|
||
- 使用文档完整,包含使用示例和注意事项
|
||
|
||
### 性能指标
|
||
|
||
- Quick Deploy 触发响应时间 < 1s
|
||
- Quick Deploy 状态轮询任务执行时间 < 1s
|
||
- Quick Deploy 进度查询响应时间 < 500ms
|
||
- Quick Deploy 历史记录查询响应时间 < 1s
|
||
|
||
## 风险
|
||
|
||
### 输出质量风险
|
||
|
||
- Quick Deploy 验证逻辑复杂可能导致验证不准确
|
||
- 状态监控不准确可能导致 Quick Deploy 状态不一致
|
||
- 结果解析不完善可能导致错误信息不准确
|
||
|
||
### 技术实现风险
|
||
|
||
- Quick Deploy 可能在某些情况下不可用
|
||
- 部署历史记录过多可能影响查询性能
|
||
- 异步任务状态跟踪困难
|
||
|
||
### 时间成本风险
|
||
|
||
- Quick Deploy 功能实现可能需要较长时间
|
||
- 测试和验证可能需要较长时间
|
||
- 文档编写可能需要较长时间
|
||
|
||
## 使用记录
|
||
|
||
| 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 |
|
||
|------|---------|---------|---------|------|----------|
|
||
| 2026-01-19 | Quick Deploy功能实现 | REQ-010-9, ADR-0018 | Quick Deploy功能代码 | 待反馈 | 待改进 |
|