- 完成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方法论创建,包括需求定义、架构决策、提示词资产化、执行会话、变更记录和闭环复盘。
20 KiB
20 KiB
Prompt - 元数据部署核心功能实现
输入引用
引用相关的 docs 文档链接:
- REQ-010-8.md - 元数据部署核心功能需求文档
- 0017-metadata-deploy-core.md - 元数据部署核心功能架构决策
- REQ-010-6.md - 元数据拉取核心功能需求文档
- REQ-010-7.md - 文件存储和解压处理需求文档
- 004-元数据部署网页资料链接地址 - 官方文档和开源项目参考
- metadata-module.md - Salesforce Metadata API 模块说明(唯一真源)
Context Maps
强制列出本次 Prompt 依赖的 Canvas 文件:
- Authentication.canvas - 项目架构视觉化展示
- 相关节点: 集成核心 - 提供与Salesforce的各种连接方式
- 相关节点: SessionManager - 会话管理,提供登录服务
目标
实现元数据部署核心功能,包括:
- 实现手动触发部署功能,支持选择任务ID、组织配置和上传Zip文件
- 实现异步部署执行,使用Spring的@Async注解和自定义线程池
- 实现状态监控,使用状态机管理部署状态(Pending/Processing/Success/Failed/Partial_Success/Cancelled)
- 实现部署历史记录,支持分页查询和条件查询
- 实现部署进度查询,支持实时进度查询和进度百分比显示
- 实现部署取消功能,使用Future.cancel()取消异步任务
- 实现部署结果解析,提取错误信息和代码覆盖率
输出格式
代码结构
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 决策文档 | 完整的代码实现和测试用例 | 待使用 | 待改进 |