datai/docs/archive/sessions/20260119-quick-deploy.md

15 KiB
Raw Permalink Blame History

会话记录 - Quick Deploy功能实现

现状

当前项目已经完成了元数据部署核心功能REQ-010-8的实现包括手动触发部署、异步部署执行、状态监控、部署历史记录、部署进度查询、部署取消功能和部署结果解析。现在需要实现 Quick Deploy 功能,支持基于最近一次成功的部署记录快速重新部署,无需重新运行测试。

目标

实现 Quick Deploy 功能,包括 Quick Deploy 触发、Quick Deploy 历史记录、Quick Deploy 状态监控、Quick Deploy 结果解析。基于最近一次成功的部署记录快速重新部署,无需重新运行测试,显著提高部署速度。

输入链接

Prompt 文件

Context Snapshot

记录本次会话参考了哪些 Canvas 节点:

执行过程

1. 创建 QuickDeployController 控制器

执行内容: 创建 QuickDeployController 控制器,提供 4 个 RESTful API 接口,支持 Quick Deploy 触发、进度查询、取消操作、历史记录查询。

关键代码:

@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 服务接口方法。

关键代码:

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 服务接口方法,支持异步执行和状态管理。

关键代码:

@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 类。

关键代码:

@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

  • 是否需要更新 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 历史记录查询是否正常