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

16 KiB
Raw Permalink Blame History

Prompt - Quick Deploy功能实现

输入引用

引用相关的 docs 文档链接:

Context Maps

强制列出本次 Prompt 依赖的 Canvas 文件:

目标

实现 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 控制器

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 服务接口

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 服务实现

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

package com.datai.salesforce.metadata.deploy.dto;

import lombok.Data;

@Data
public class QuickDeployTriggerRequest {
    private Long orgConfigId;
    private String createdBy;
}

5. QuickDeployTriggerResponse DTO

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

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

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

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