datai/docs/archive/prompts/019-quick-deploy.md

481 lines
16 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 - 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功能代码 | 待反馈 | 待改进 |