datai/datai-scenes/datai-scene-salesforce/docs/prompts/018-metadata-deploy-core.md
Kris 2e6f087732 docs: 完成REQ-010-17和REQ-010-2的文档创建
- 完成REQ-010-17(性能优化和限流处理)的所有6个阶段
  - 创建ADR文档:0026-performance-optimization.md
  - 创建Prompt文档:027-performance-optimization.md
  - 创建会话记录:20260119-performance-optimization.md
  - 创建变更记录:20260119-performance-optimization.md
  - 创建复盘报告:20260119-performance-optimization-retro.md
  - 更新index.md和CHANGELOG.md

- 完成REQ-010-2(基础实体类和Mapper创建)的前3个阶段
  - 更新ADR文档:0011-entity-mapper-create.md
  - 创建Prompt文档:002-entity-mapper-create.md
  - 更新index.md

所有文档均按照SSOT方法论创建,包括需求定义、架构决策、提示词资产化、执行会话、变更记录和闭环复盘。
2026-01-19 10:06:09 +08:00

20 KiB
Raw Blame History

Prompt - 元数据部署核心功能实现

输入引用

引用相关的 docs 文档链接:

Context Maps

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

目标

实现元数据部署核心功能,包括:

  1. 实现手动触发部署功能支持选择任务ID、组织配置和上传Zip文件
  2. 实现异步部署执行使用Spring的@Async注解和自定义线程池
  3. 实现状态监控使用状态机管理部署状态Pending/Processing/Success/Failed/Partial_Success/Cancelled
  4. 实现部署历史记录,支持分页查询和条件查询
  5. 实现部署进度查询,支持实时进度查询和进度百分比显示
  6. 实现部署取消功能使用Future.cancel()取消异步任务
  7. 实现部署结果解析,提取错误信息和代码覆盖率

输出格式

代码结构

datai-salesforce-metadata/
├── src/main/java/com/datai/salesforce/metadata/deploy/
│   ├── controller/
│   │   └── MetadataDeployController.java          # 元数据部署控制器
│   ├── service/
│   │   ├── IMetadataDeployService.java            # 元数据部署服务接口
│   │   └── MetadataDeployServiceImpl.java         # 元数据部署服务实现
│   ├── enums/
│   │   └── DeployStatus.java                    # 部署状态枚举
│   ├── dto/
│   │   ├── DeployTriggerRequest.java             # 部署触发请求
│   │   ├── DeployTriggerResponse.java            # 部署触发响应
│   │   ├── DeployProgressResponse.java            # 部署进度响应
│   │   ├── DeployHistoryResponse.java            # 部署历史响应
│   │   └── DeployResult.java                   # 部署结果
│   ├── config/
│   │   └── AsyncConfig.java                     # 异步配置
│   └── exception/
│       └── DeployException.java                  # 部署异常
├── src/main/resources/
│   ├── application.yml                          # 应用配置
│   └── logback-spring.xml                     # 日志配置
└── src/test/java/com/datai/salesforce/metadata/deploy/
    ├── service/
    │   └── MetadataDeployServiceTest.java       # 部署服务测试
    └── controller/
        └── MetadataDeployControllerTest.java    # 部署控制器测试

代码示例

1. DeployStatus.java

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

import lombok.Getter;

@Getter
public enum DeployStatus {
    
    PENDING(0, "待处理"),
    PROCESSING(1, "处理中"),
    SUCCESS(2, "成功"),
    FAILED(3, "失败"),
    PARTIAL_SUCCESS(4, "部分成功"),
    CANCELLED(5, "已取消");
    
    private final Integer code;
    private final String desc;
    
    DeployStatus(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    public static DeployStatus fromCode(Integer code) {
        for (DeployStatus status : values()) {
            if (status.getCode().equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("Invalid status code: " + code);
    }
}

2. DeployTriggerRequest.java

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

import lombok.Data;

@Data
public class DeployTriggerRequest {
    
    private Long taskId;
    private Long orgConfigId;
    private String zipFilePath;
    private String createdBy;
}

3. DeployTriggerResponse.java

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

import lombok.Data;

@Data
public class DeployTriggerResponse {
    
    private String jobId;
    private Integer status;
    private String message;
}

4. DeployProgressResponse.java

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

import lombok.Data;

@Data
public class DeployProgressResponse {
    
    private String jobId;
    private Integer status;
    private Integer progress;
    private Integer processedComponents;
    private Integer totalComponents;
    private String message;
}

5. DeployHistoryResponse.java

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

import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;

@Data
public class DeployHistoryResponse {
    
    private Long id;
    private Long taskId;
    private Long orgConfigId;
    private Integer status;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private String createdBy;
    private List<DeployResult> results;
}

6. DeployResult.java

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

import lombok.Data;
import java.util.List;

@Data
public class DeployResult {
    
    private String componentType;
    private String fileName;
    private Boolean success;
    private String errorMessage;
    private Integer lineNumber;
    private Integer columnNumber;
}

7. IMetadataDeployService.java

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

import com.datai.salesforce.metadata.deploy.dto.*;
import java.util.List;
import java.util.concurrent.Future;

public interface IMetadataDeployService {
    
    DeployTriggerResponse triggerDeploy(DeployTriggerRequest request);
    
    Future<Void> deployAsync(Long jobExecutionId, Long taskId, Long orgConfigId, String zipFilePath);
    
    DeployProgressResponse getProgress(String jobId);
    
    List<DeployHistoryResponse> getHistory(Long taskId, Long orgConfigId, Integer pageNum, Integer pageSize);
    
    boolean cancelDeploy(String jobId);
    
    void parseDeployResult(Long jobExecutionId, String deployResult);
}

8. MetadataDeployServiceImpl.java

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

import com.datai.salesforce.metadata.deploy.dto.*;
import com.datai.salesforce.metadata.deploy.enums.DeployStatus;
import com.datai.salesforce.metadata.entity.DataiMetaJobExecution;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.mapper.DataiMetaJobExecutionMapper;
import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper;
import com.datai.salesforce.metadata.service.IMetadataApiService;
import lombok.extern.slf4j.Slf4j;
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.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;

@Slf4j
@Service
public class MetadataDeployServiceImpl implements IMetadataDeployService {
    
    @Autowired
    private IMetadataApiService metadataApiService;
    
    @Autowired
    private DataiMetaJobExecutionMapper jobExecutionMapper;
    
    @Autowired
    private DataiMetaTaskMapper taskMapper;
    
    private final Map<String, Future<?>> deployFutureMap = new ConcurrentHashMap<>();
    private final Map<String, DeployProgressResponse> progressMap = new ConcurrentHashMap<>();
    
    @Override
    @Transactional
    public DeployTriggerResponse triggerDeploy(DeployTriggerRequest request) {
        DataiMetaTask task = taskMapper.selectById(request.getTaskId());
        if (task == null) {
            throw new RuntimeException("Task not found");
        }
        
        DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
        jobExecution.setTaskId(request.getTaskId());
        jobExecution.setOrgConfigId(request.getOrgConfigId());
        jobExecution.setJobType(task.getTaskType());
        jobExecution.setStatus(DeployStatus.PENDING.getCode());
        jobExecution.setCreatedBy(request.getCreatedBy());
        jobExecutionMapper.insert(jobExecution);
        
        deployAsync(jobExecution.getId(), request.getTaskId(), request.getOrgConfigId(), request.getZipFilePath());
        
        DeployTriggerResponse response = new DeployTriggerResponse();
        response.setJobId(jobExecution.getId().toString());
        response.setStatus(DeployStatus.PENDING.getCode());
        response.setMessage("Deploy task created");
        
        return response;
    }
    
    @Override
    @Async("deployExecutor")
    public Future<Void> deployAsync(Long jobExecutionId, Long taskId, Long orgConfigId, String zipFilePath) {
        String jobId = jobExecutionId.toString();
        
        try {
            updateJobStatus(jobExecutionId, DeployStatus.PROCESSING);
            
            File zipFile = new File(zipFilePath);
            String deployResult = metadataApiService.deploy(orgConfigId, zipFile);
            
            updateJobStatus(jobExecutionId, DeployStatus.SUCCESS);
            
            parseDeployResult(jobExecutionId, deployResult);
            
            log.info("Deploy completed successfully: {}", jobId);
        } catch (Exception e) {
            log.error("Deploy failed: {}", jobId, e);
            updateJobStatus(jobExecutionId, DeployStatus.FAILED);
            throw new RuntimeException("Deploy failed", e);
        } finally {
            deployFutureMap.remove(jobId);
        }
        
        return null;
    }
    
    @Override
    public DeployProgressResponse getProgress(String jobId) {
        DeployProgressResponse progress = progressMap.get(jobId);
        if (progress == null) {
            DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(Long.parseLong(jobId));
            progress = new DeployProgressResponse();
            progress.setJobId(jobId);
            progress.setStatus(jobExecution.getStatus());
            progress.setProgress(0);
            progress.setMessage("Deploy task not found in progress map");
        }
        return progress;
    }
    
    @Override
    public List<DeployHistoryResponse> getHistory(Long taskId, Long orgConfigId, Integer pageNum, Integer pageSize) {
        return jobExecutionMapper.selectDeployHistory(taskId, orgConfigId, pageNum, pageSize);
    }
    
    @Override
    public boolean cancelDeploy(String jobId) {
        Future<?> future = deployFutureMap.get(jobId);
        if (future != null) {
            boolean cancelled = future.cancel(true);
            if (cancelled) {
                updateJobStatus(Long.parseLong(jobId), DeployStatus.CANCELLED);
                progressMap.remove(jobId);
                log.info("Deploy cancelled: {}", jobId);
            }
            return cancelled;
        }
        return false;
    }
    
    @Override
    @Transactional
    public void parseDeployResult(Long jobExecutionId, String deployResult) {
        log.info("Parsing deploy result for job: {}", jobExecutionId);
        
        DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobExecutionId);
        jobExecution.setDeployResult(deployResult);
        jobExecutionMapper.updateById(jobExecution);
    }
    
    private void updateJobStatus(Long jobExecutionId, DeployStatus status) {
        DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
        jobExecution.setId(jobExecutionId);
        jobExecution.setStatus(status.getCode());
        jobExecutionMapper.updateById(jobExecution);
    }
}

9. MetadataDeployController.java

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

import com.datai.salesforce.metadata.deploy.dto.*;
import com.datai.salesforce.metadata.deploy.service.IMetadataDeployService;
import com.datai.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/metadata/deploy")
public class MetadataDeployController {
    
    @Autowired
    private IMetadataDeployService metadataDeployService;
    
    @PostMapping("/trigger")
    public R<DeployTriggerResponse> triggerDeploy(@RequestBody DeployTriggerRequest request) {
        log.info("Trigger deploy: taskId={}, orgConfigId={}", request.getTaskId(), request.getOrgConfigId());
        DeployTriggerResponse response = metadataDeployService.triggerDeploy(request);
        return R.ok(response);
    }
    
    @GetMapping("/progress/{jobId}")
    public R<DeployProgressResponse> getProgress(@PathVariable String jobId) {
        log.info("Get deploy progress: jobId={}", jobId);
        DeployProgressResponse response = metadataDeployService.getProgress(jobId);
        return R.ok(response);
    }
    
    @GetMapping("/history")
    public R<List<DeployHistoryResponse>> getHistory(
            @RequestParam(required = false) Long taskId,
            @RequestParam(required = false) Long orgConfigId,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        log.info("Get deploy history: taskId={}, orgConfigId={}, pageNum={}, pageSize={}", 
                taskId, orgConfigId, pageNum, pageSize);
        List<DeployHistoryResponse> history = metadataDeployService.getHistory(taskId, orgConfigId, pageNum, pageSize);
        return R.ok(history);
    }
    
    @PostMapping("/cancel/{jobId}")
    public R<Boolean> cancelDeploy(@PathVariable String jobId) {
        log.info("Cancel deploy: jobId={}", jobId);
        boolean cancelled = metadataDeployService.cancelDeploy(jobId);
        return R.ok(cancelled);
    }
}

10. AsyncConfig.java

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean("deployExecutor")
    public Executor deployExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("deploy-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

11. application.yml

spring:
  task:
    execution:
      pool:
        core-size: 5
        max-size: 10
        queue-capacity: 100
        thread-name-prefix: async-
        keep-alive: 60s

metadata:
  deploy:
    poll-interval: 5000  # 状态轮询间隔(毫秒)
    max-retry: 3  # 最大重试次数

约束

技术栈约束

  • 必须基于 Spring Boot 3
  • 必须使用 Java 17+
  • 必须使用 MyBatis Plus
  • 必须使用 MySQL

性能约束

  • 部署触发响应时间 < 1s
  • 状态轮询任务执行时间 < 1s
  • 进度查询响应时间 < 500ms
  • 历史记录查询响应时间 < 1s
  • 并发触发 10 个部署任务无性能下降

安全性约束

  • 部署操作需要认证
  • 必须使用 SessionManager 进行会话管理
  • 自动重新登录功能正常
  • 部署数据传输加密
  • 敏感信息不记录到日志
  • 部署历史记录权限控制

兼容性约束

  • 必须使用现有的集成核心功能进行 Salesforce API 调用
  • 必须使用现有的认证模块进行 OAuth 认证
  • 必须遵循现有的异常处理机制
  • 必须遵循现有的日志记录规范

Rule Set

"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。"

具体规则

  • 必须使用 Canvas 中定义的类名和方法名
  • 必须遵循 Canvas 中定义的调用关系
  • 必须参考 Canvas 中的流程图逻辑
  • 必须使用 SessionManager 进行会话管理和自动重新登录
  • 必须使用现有的认证模块进行 OAuth 认证
  • 必须使用现有的集成核心功能进行 API 调用
  • 必须遵循现有的异常处理机制
  • 必须遵循现有的日志记录规范
  • 必须使用状态机模式管理部署状态
  • 必须使用 Spring 的 @Async 注解实现异步执行
  • 必须使用自定义线程池管理异步任务
  • 必须使用轮询机制检查部署状态

验收标准

功能验收标准

  • 能够成功手动触发部署
  • 支持选择任务ID
  • 支持选择组织配置
  • 支持上传Zip文件
  • 触发成功返回Job ID
  • API接口符合RESTful规范
  • 异步部署执行正常工作
  • 使用线程池管理异步任务
  • 支持并发部署
  • 部署任务不阻塞系统响应
  • 状态监控正常工作
  • 使用状态机管理部署状态
  • 支持状态查询
  • 状态更新及时
  • 部署历史记录成功
  • 支持分页查询
  • 支持条件查询
  • 历史记录完整
  • 部署进度查询成功
  • 支持实时进度查询
  • 进度信息准确
  • 支持进度百分比显示
  • 部署取消功能正常工作
  • 支持取消正在进行的部署任务
  • 取消后资源正确释放
  • 取消状态更新及时
  • 部署结果解析成功
  • 错误信息提取正确
  • 代码覆盖率提取正确
  • 支持多种部署状态
  • 错误信息详细

性能验收标准

  • 部署触发响应时间 < 1s
  • 并发触发 10 个部署任务无性能下降
  • 状态轮询任务执行时间 < 1s
  • 不影响系统正常运行
  • 进度查询响应时间 < 500ms
  • 并发查询 100 次无性能下降
  • 历史记录查询响应时间 < 1s
  • 分页查询 100 条记录无性能下降

安全性验收标准

  • 部署操作需要认证
  • 使用 SessionManager 进行会话管理
  • 自动重新登录功能正常
  • 部署数据传输加密
  • 敏感信息不记录到日志
  • 部署历史记录权限控制

代码质量验收标准

  • 代码符合项目编码规范
  • 有清晰的注释
  • 单元测试覆盖率 > 80%
  • 集成测试覆盖主要场景
  • 通过代码审查

风险

异步执行风险

  • 风险描述: 异步执行机制复杂可能导致状态管理困难
  • 缓解措施: 使用状态机模式管理状态,使用 Future 对象跟踪异步任务
  • 监控指标: 监控异步任务执行时间和状态转换

状态轮询风险

  • 风险描述: 状态轮询频率不当可能导致 API 限流
  • 缓解措施: 设置合理的轮询间隔(如 5 秒),使用指数退避策略
  • 监控指标: 监控 API 调用次数和响应时间

部署取消风险

  • 风险描述: 部署取消功能复杂可能导致资源泄漏
  • 缓解措施: 使用 Future.cancel() 安全取消,确保资源正确释放
  • 监控指标: 监控取消操作的成功率和资源释放情况

历史记录风险

  • 风险描述: 部署历史记录过多可能影响查询性能
  • 缓解措施: 使用分页查询,定期清理过期历史记录
  • 监控指标: 监控历史记录查询性能

进度查询风险

  • 风险描述: 进度查询不准确可能导致用户体验差
  • 缓解措施: 使用缓存提高查询性能,优化轮询策略
  • 监控指标: 监控进度查询的准确性和响应时间

结果解析风险

  • 风险描述: 部署结果解析不完善可能导致错误信息不准确
  • 缓解措施: 使用 JSON 解析库和正则表达式双重验证,充分测试
  • 监控指标: 监控结果解析的成功率和准确性

使用记录

日期 使用场景 输入参数 输出结果 反馈 改进措施
2026-01-19 实现元数据部署核心功能 REQ-010-8 需求文档、ADR-0017 决策文档 完整的代码实现和测试用例 待使用 待改进