- 完成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方法论创建,包括需求定义、架构决策、提示词资产化、执行会话、变更记录和闭环复盘。
25 KiB
25 KiB
闭环复盘 - Metadata API客户端封装
复盘信息
- 复盘编号: Retro-20260118-002
- 复盘日期: 2026-01-18
- 相关需求: REQ-010-5 - Metadata API客户端封装
- 相关决策: ADR-0014 - Metadata API客户端封装架构决策
- 相关提示词: Prompt-015 - Metadata API客户端封装实现提示词
- 相关会话: Session-20260118-002 - Metadata API客户端封装执行会话
- 相关变更: Change-015 - Metadata API客户端封装变更记录
目标与实际产出对比
目标
- 封装 Salesforce Metadata API 客户端,提供 retrieve() 和 deploy() 方法的调用接口
- 支持异步执行
- 支持状态轮询
- 支持 Job ID 获取
- 支持 Zip 文件下载和解压处理
- 支持部署结果解析
- 实现错误处理机制
实际产出
-
✅ 完成了 MetadataApiClient 客户端类的创建,包括:
- getMetadataConnection: 获取 Metadata 连接
- retrieve: 检索元数据
- deploy: 部署元数据
- checkRetrieveStatus: 检查检索状态
- checkDeployStatus: 检查部署状态
-
✅ 完成了 MetadataApiService 服务层的创建,包括:
- retrieveAsync: 异步检索元数据
- deployAsync: 异步部署元数据
- cancelJob: 取消任务
- getJobStatus: 获取任务状态
-
✅ 完成了 AsyncConfig 配置类的创建,包括:
- 配置线程池(核心线程数 5,最大线程数 10,队列容量 100)
- 配置线程名称前缀
- 配置拒绝策略
-
✅ 完成了状态轮询机制的创建,包括:
- 定时轮询任务状态
- 支持超时处理(30 分钟)
- 支持取消轮询
- 更新任务状态
-
✅ 完成了 Zip 文件处理工具类的创建,包括:
- unzip: 解压 Zip 文件
- readZipFile: 读取 Zip 文件内容
- 使用流式处理避免内存溢出
-
✅ 完成了 MetadataApiUtil 工具类的创建,包括:
- parsePackageXml: 解析 package.xml
- parseDeployResult: 解析部署结果
- extractErrorMessages: 提取错误信息
- extractCodeCoverage: 提取代码覆盖率
-
✅ 完成了自定义异常类的创建,包括:
- MetadataApiException: 统一的异常处理机制
-
✅ 完成了结果类的创建,包括:
- RetrieveResult: 检索结果类
- DeployResult: 部署结果类
- JobStatus: 任务状态类
-
✅ 完成了 MetadataApiController 控制器的创建,包括:
- POST /metadata/retrieve - 检索元数据
- POST /metadata/deploy - 部署元数据
- GET /metadata/job/{jobId}/status - 获取任务状态
- DELETE /metadata/job/{jobId} - 取消任务
-
✅ 完成了 MetadataManager 前端组件的创建,包括:
- 元数据检索界面
- 元数据部署界面
- 任务状态查询界面
- 任务取消界面
对比分析
| 目标项 | 完成度 | 说明 |
|---|---|---|
| MetadataApiClient 客户端类 | 100% | 完全实现,使用 Salesforce WSC 库 |
| MetadataApiService 服务层 | 100% | 完全实现,支持异步执行 |
| AsyncConfig 配置类 | 100% | 完全实现,配置线程池 |
| 状态轮询机制 | 100% | 完全实现,支持超时处理和取消轮询 |
| Zip 文件处理工具类 | 100% | 完全实现,使用流式处理 |
| MetadataApiUtil 工具类 | 100% | 完全实现,提供多种工具方法 |
| 自定义异常类 | 100% | 完全实现,统一的异常处理机制 |
| 结果类 | 100% | 完全实现,封装任务执行结果 |
| MetadataApiController 控制器 | 100% | 完全实现,提供 4 个 RESTful API 接口 |
| MetadataManager 前端组件 | 100% | 完全实现,基于 Vue 3 和 Element Plus |
总体完成度: 100%
结论: 所有目标均已实现,功能完整,符合需求。
有效的 Prompt 技巧(3条)
技巧1:明确角色设定和技术栈
描述: 在提示词开头明确设定角色和技术栈,让 AI 清楚自己的定位和要使用的技术。
示例:
### 角色设定
你是一个经验丰富的 Spring Boot 全栈开发工程师,专注于 Salesforce 元数据管理系统的开发。你熟悉以下技术栈:
- **后端**: Spring Boot 3, MyBatis Plus, MySQL
- **前端**: Vue 3, Element Plus
- **Salesforce API**: Metadata API, Partner API
- **Salesforce WSC**: Web Service Connector 库
- **工具**: Maven, Git, Postman
### 技术栈限制
- **后端**: Spring Boot 3, MyBatis Plus, MySQL
- **Salesforce API**: Metadata API, Partner API
- **Salesforce WSC**: 必须使用 Salesforce WSC (Web Service Connector) 库
- **异步执行**: 必须使用 Spring 的 @Async 注解和线程池
- **状态轮询**: 必须使用定时任务实现状态轮询
效果: AI 能够快速理解角色定位和技术栈,生成符合要求的代码。
技巧2:提供详细的代码示例和实现方案
描述: 在提示词中提供详细的代码示例和实现方案,包括类结构、方法签名、参数类型等。
示例:
#### 2.1 MetadataApiClient 类
```java
package com.datai.metadata.client;
import com.datai.auth.service.ISessionManager;
import com.sforce.soap.metadata.*;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Metadata API 客户端类
* 封装 Salesforce Metadata API 调用
*/
@Slf4j
@Component
public class MetadataApiClient {
@Autowired
private ISessionManager sessionManager;
/**
* 获取 Metadata 连接
*
* @param orgConfigId 组织配置ID
* @return Metadata 连接
* @throws ConnectionException 连接异常
*/
private MetadataConnection getMetadataConnection(Long orgConfigId) throws ConnectionException {
// 使用 SessionManager 获取会话信息
SessionInfo sessionInfo = sessionManager.getSession(orgConfigId);
// 创建连接配置
ConnectorConfig config = new ConnectorConfig();
config.setSessionId(sessionInfo.getSessionId());
config.setServiceEndpoint(sessionInfo.getMetadataEndpoint());
// 创建 Metadata 连接
return new MetadataConnection(config);
}
/**
* 检索元数据
*
* @param orgConfigId 组织配置ID
* @param packageXml package.xml 内容
* @param apiVersion API 版本
* @return 检索结果
* @throws MetadataApiException Metadata API 异常
*/
public RetrieveResult retrieve(Long orgConfigId, String packageXml, String apiVersion)
throws MetadataApiException {
try {
// 获取 Metadata 连接
MetadataConnection connection = getMetadataConnection(orgConfigId);
// 创建 Retrieve 请求
com.sforce.soap.metadata.RetrieveRequest request = new com.sforce.soap.metadata.RetrieveRequest();
request.setApiVersion(Double.parseDouble(apiVersion));
request.setUnpackaged(readPackageXml(packageXml));
// 执行检索
AsyncResult asyncResult = connection.retrieve(request);
// 返回检索结果
return new RetrieveResult(asyncResult.getId(), asyncResult.getState().name());
} catch (ConnectionException e) {
log.error("检索元数据失败", e);
throw new MetadataApiException("检索元数据失败: " + e.getMessage(), e);
}
}
}
**效果**: AI 能够根据提供的代码示例生成符合项目规范的代码,减少修改和调整的工作量。
### 技巧3:明确输出格式和验收标准
**描述**: 在提示词中明确输出格式和验收标准,包括代码结构、单元测试、集成测试、API 文档等。
**示例**:
输出格式
1. 代码结构
datai-salesforce-metadata/
├── src/main/java/com/datai/metadata/
│ ├── client/
│ │ ├── MetadataApiClient.java - Metadata API 客户端类
│ │ ├── MetadataApiException.java - Metadata API 异常类
│ │ ├── RetrieveResult.java - 检索结果类
│ │ ├── DeployResult.java - 部署结果类
│ │ └── JobStatus.java - 任务状态类
│ ├── config/
│ │ └── AsyncConfig.java - 异步配置类
│ ├── service/
│ │ ├── IMetadataApiService.java - Metadata API 服务接口
│ │ └── impl/
│ │ └── MetadataApiServiceImpl.java - Metadata API 服务实现
│ ├── task/
│ │ └── MetadataPollingTask.java - Metadata 轮询任务
│ └── util/
│ ├── ZipUtil.java - Zip 文件处理工具类
│ └── MetadataApiUtil.java - Metadata API 工具类
3. 单元测试
必须为以下类编写单元测试:
- MetadataApiClient
- MetadataApiServiceImpl
- ZipUtil
- MetadataApiUtil
4. 集成测试
必须编写以下集成测试:
- Metadata API 客户端调用测试
- 异步执行测试
- 状态轮询测试
- Zip 文件处理测试
- 错误处理测试
5. API 文档
为以下接口编写 API 文档:
- POST /metadata/retrieve - 检索元数据
- POST /metadata/deploy - 部署元数据
- GET /metadata/job/{jobId}/status - 获取任务状态
- DELETE /metadata/job/{jobId} - 取消任务
**效果**: AI 能够按照要求的格式输出代码,并确保代码质量和完整性。
## 避免的坑(3个)
### 坑1:异步执行方案选择不当
**问题描述**: 在架构决策阶段,对于异步执行方案,最初考虑了多种方案(Spring @Async、消息队列、CompletableFuture 等),如果选择不当可能导致实现复杂或功能受限。
**避免方法**: 在 ADR-0014 中,我们详细分析了三种方案的优缺点,最终选择了使用 Spring 的 @Async 注解和线程池的方案,理由是:
1. Spring 的 @Async 注解简单易用,无需额外配置
2. Spring 提供了线程池管理,可以控制并发数和资源使用
3. 异步执行不影响系统响应,提高用户体验
4. 支持异步任务的状态跟踪和结果获取
5. 对于简单的异步任务,使用消息队列是过度设计
**经验教训**: 在做技术选型时,要充分考虑业务需求、技术约束、实现复杂度等因素,选择最合适的方案。
### 坑2:状态轮询方案选择不当
**问题描述**: 对于状态轮询方案,最初考虑了多种方案(定时任务、回调机制、WebSocket 等),如果选择不当可能导致实现复杂或功能受限。
**避免方法**: 我们选择了使用定时任务实现状态轮询,理由是:
1. Salesforce Metadata API 不支持回调机制
2. 定时任务简单易用,无需额外引入其他库
3. 可以控制轮询频率,避免 API 限流
4. 支持超时处理,防止无限轮询
5. 支持取消轮询,提高灵活性
**经验教训**: 在选择状态轮询方案时,要考虑 API 的限制、实现复杂度、资源消耗等因素,选择最合适的方案。
### 坑3:Zip 文件处理方案选择不当
**问题描述**: 对于 Zip 文件处理方案,最初考虑了多种方案(ZipInputStream、Apache Commons Compress、其他 Zip 库等),如果选择不当可能导致内存溢出或性能问题。
**避免方法**: 我们选择了使用 Java 的 ZipInputStream 处理 Zip 文件,理由是:
1. Java 的 ZipInputStream 是标准库,无需额外引入其他库
2. 流式处理可以避免内存溢出,支持大文件处理
3. 可以逐条读取 Zip 文件中的条目,提高处理效率
4. 支持解压到指定目录,便于后续处理
**经验教训**: 在选择 Zip 文件处理方案时,要考虑内存使用、性能、依赖复杂度等因素,选择最合适的方案。
## 模板迭代
### 模板适用性评估
本次使用的 Prompt-015 模板(Metadata API客户端封装实现提示词)整体适用性良好,能够指导 AI 完成开发任务。但仍有以下改进空间:
### 改进建议1:增加异步执行示例
**问题描述**: 当前模板没有提供详细的异步执行示例,可能导致 AI 生成的代码异步执行不够完善。
**改进建议**: 在模板中增加异步执行示例,包括:
- @Async 注解的使用
- CompletableFuture 的使用
- 异步任务的状态跟踪
- 异步任务的取消和超时处理
**示例**:
2.3 MetadataApiServiceImpl 实现类
package com.datai.metadata.service.impl;
import com.datai.metadata.client.*;
import com.datai.metadata.service.IMetadataApiService;
import com.sforce.soap.metadata.DeployOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
/**
* Metadata API 服务实现类
*/
@Slf4j
@Service
public class MetadataApiServiceImpl implements IMetadataApiService {
@Autowired
private MetadataApiClient metadataApiClient;
private final ConcurrentHashMap<String, CompletableFuture<?>> jobMap = new ConcurrentHashMap<>();
@Override
@Async("metadataTaskExecutor")
public CompletableFuture<RetrieveResult> retrieveAsync(Long orgConfigId, String packageXml, String apiVersion) {
String jobId = generateJobId();
CompletableFuture<RetrieveResult> future = new CompletableFuture<>();
jobMap.put(jobId, future);
try {
// 执行检索
RetrieveResult result = metadataApiClient.retrieve(orgConfigId, packageXml, apiVersion);
// 轮询状态
pollRetrieveStatus(orgConfigId, result.getJobId(), future);
return future;
} catch (Exception e) {
log.error("异步检索元数据失败", e);
future.completeExceptionally(e);
jobMap.remove(jobId);
return future;
}
}
@Override
@Async("metadataTaskExecutor")
public CompletableFuture<DeployResult> deployAsync(Long orgConfigId, byte[] zipFile, DeployOptions options) {
String jobId = generateJobId();
CompletableFuture<DeployResult> future = new CompletableFuture<>();
jobMap.put(jobId, future);
try {
// 执行部署
DeployResult result = metadataApiClient.deploy(orgConfigId, zipFile, options);
// 轮询状态
pollDeployStatus(orgConfigId, result.getJobId(), future);
return future;
} catch (Exception e) {
log.error("异步部署元数据失败", e);
future.completeExceptionally(e);
jobMap.remove(jobId);
return future;
}
}
@Override
public void cancelJob(String jobId) {
CompletableFuture<?> future = jobMap.get(jobId);
if (future != null && !future.isDone()) {
future.cancel(true);
jobMap.remove(jobId);
log.info("任务已取消: {}", jobId);
}
}
@Override
public JobStatus getJobStatus(String jobId) {
CompletableFuture<?> future = jobMap.get(jobId);
if (future == null) {
return JobStatus.NOT_FOUND;
}
if (future.isDone()) {
if (future.isCompletedExceptionally()) {
return JobStatus.FAILED;
} else {
return JobStatus.COMPLETED;
}
} else {
return JobStatus.IN_PROGRESS;
}
}
/**
* 轮询检索状态
*
* @param orgConfigId 组织配置ID
* @param jobId 任务ID
* @param future CompletableFuture
*/
private void pollRetrieveStatus(Long orgConfigId, String jobId, CompletableFuture<RetrieveResult> future) {
// 使用定时任务轮询状态
// 实现略
}
/**
* 轮询部署状态
*
* @param orgConfigId 组织配置ID
* @param jobId 任务ID
* @param future CompletableFuture
*/
private void pollDeployStatus(Long orgConfigId, String jobId, CompletableFuture<DeployResult> future) {
// 使用定时任务轮询状态
// 实现略
}
/**
* 生成任务ID
*
* @return 任务ID
*/
private String generateJobId() {
return System.currentTimeMillis() + "-" + (int)(Math.random() * 1000);
}
}
### 改进建议2:增加状态轮询示例
**问题描述**: 当前模板没有提供详细的状态轮询示例,可能导致 AI 生成的代码状态轮询不够完善。
**改进建议**: 在模板中增加状态轮询示例,包括:
- @Scheduled 注解的使用
- 轮询频率的配置
- 超时处理的实现
- 取消轮询的实现
**示例**:
2.4 MetadataPollingTask 轮询任务
package com.datai.metadata.task;
import com.datai.metadata.client.*;
import com.datai.metadata.service.IMetadataApiService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Metadata 轮询任务
*/
@Slf4j
@Component
public class MetadataPollingTask {
@Autowired
private IMetadataApiService metadataApiService;
private final ConcurrentHashMap<String, PollingJob> pollingJobs = new ConcurrentHashMap<>();
/**
* 轮询检索状态
*
* @param orgConfigId 组织配置ID
* @param jobId 任务ID
* @param future CompletableFuture
*/
public void pollRetrieveStatus(Long orgConfigId, String jobId, CompletableFuture<RetrieveResult> future) {
PollingJob pollingJob = new PollingJob(orgConfigId, jobId, future, System.currentTimeMillis());
pollingJobs.put(jobId, pollingJob);
}
/**
* 轮询部署状态
*
* @param orgConfigId 组织配置ID
* @param jobId 任务ID
* @param future CompletableFuture
*/
public void pollDeployStatus(Long orgConfigId, String jobId, CompletableFuture<DeployResult> future) {
PollingJob pollingJob = new PollingJob(orgConfigId, jobId, future, System.currentTimeMillis());
pollingJobs.put(jobId, pollingJob);
}
/**
* 定时轮询任务状态
* 每 5 秒轮询一次
*/
@Scheduled(fixedRate = 5000)
public void pollJobStatus() {
for (Map.Entry<String, PollingJob> entry : pollingJobs.entrySet()) {
String jobId = entry.getKey();
PollingJob pollingJob = entry.getValue();
// 检查是否超时
long elapsedTime = System.currentTimeMillis() - pollingJob.getStartTime();
if (elapsedTime > TimeUnit.MINUTES.toMillis(30)) {
log.warn("任务超时: {}", jobId);
pollingJob.getFuture().completeExceptionally(new TimeoutException("任务超时"));
pollingJobs.remove(jobId);
continue;
}
// 检查是否已取消
if (pollingJob.getFuture().isCancelled()) {
log.info("任务已取消: {}", jobId);
pollingJobs.remove(jobId);
continue;
}
// 轮询状态
try {
if (pollingJob.getFuture() instanceof CompletableFuture<?>) {
// 根据任务类型轮询状态
// 实现略
}
} catch (Exception e) {
log.error("轮询任务状态失败: {}", jobId, e);
pollingJob.getFuture().completeExceptionally(e);
pollingJobs.remove(jobId);
}
}
}
/**
* 轮询任务
*/
private static class PollingJob {
private final Long orgConfigId;
private final String jobId;
private final CompletableFuture<?> future;
private final long startTime;
public PollingJob(Long orgConfigId, String jobId, CompletableFuture<?> future, long startTime) {
this.orgConfigId = orgConfigId;
this.jobId = jobId;
this.future = future;
this.startTime = startTime;
}
public Long getOrgConfigId() {
return orgConfigId;
}
public String getJobId() {
return jobId;
}
public CompletableFuture<?> getFuture() {
return future;
}
public long getStartTime() {
return startTime;
}
}
}
### 改进建议3:增加错误处理示例
**问题描述**: 当前模板没有提供详细的错误处理示例,可能导致 AI 生成的代码错误处理不够完善。
**改进建议**: 在模板中增加错误处理示例,包括:
- 自定义异常类的使用
- 统一的异常处理机制
- 错误信息的返回
- 错误重试机制
**示例**:
2.5 MetadataApiException 异常类
package com.datai.metadata.client;
import lombok.Getter;
/**
* Metadata API 异常类
*/
@Getter
public class MetadataApiException extends RuntimeException {
private final String errorCode;
private final String errorMessage;
public MetadataApiException(String errorMessage) {
super(errorMessage);
this.errorCode = "METADATA_API_ERROR";
this.errorMessage = errorMessage;
}
public MetadataApiException(String errorMessage, Throwable cause) {
super(errorMessage, cause);
this.errorCode = "METADATA_API_ERROR";
this.errorMessage = errorMessage;
}
public MetadataApiException(String errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public MetadataApiException(String errorCode, String errorMessage, Throwable cause) {
super(errorMessage, cause);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
2.6 统一异常处理
package com.datai.metadata.exception;
import com.datai.common.core.domain.AjaxResult;
import com.datai.metadata.client.MetadataApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 统一异常处理
*/
@Slf4j
@RestControllerAdvice
public class MetadataExceptionHandler {
/**
* 处理 Metadata API 异常
*
* @param e Metadata API 异常
* @return 错误结果
*/
@ExceptionHandler(MetadataApiException.class)
public AjaxResult handleMetadataApiException(MetadataApiException e) {
log.error("Metadata API 异常: {}", e.getErrorMessage(), e);
return AjaxResult.error(e.getErrorCode(), e.getErrorMessage());
}
/**
* 处理其他异常
*
* @param e 异常
* @return 错误结果
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error("系统异常", e);
return AjaxResult.error("系统异常: " + e.getMessage());
}
}
## 总结
### 成功经验
1. **遵循项目规则**: 严格按照项目规则的 6 个阶段执行,确保文档的完整性和可追溯性。
2. **架构决策合理**: 在架构决策阶段详细分析了多种方案,选择了最合适的方案。
3. **提示词设计完善**: 提示词设计清晰、详细,能够指导 AI 完成开发任务。
4. **文档更新及时**: 及时更新 CHANGELOG.md 和 index.md,保持文档的同步。
### 改进方向
1. **增加异步执行示例**: 在提示词模板中增加详细的异步执行示例。
2. **增加状态轮询示例**: 在提示词模板中增加详细的状态轮询示例。
3. **增加错误处理示例**: 在提示词模板中增加详细的错误处理示例。
4. **增加性能优化建议**: 在提示词模板中增加性能优化建议。
### 下一步行动
1. **更新提示词模板**: 根据复盘结果,更新 Prompt-015 模板,增加异步执行、状态轮询、错误处理示例。
2. **应用到其他需求**: 将改进后的模板应用到其他 REQ-010 子需求的实现中。
3. **持续优化**: 在后续的开发中,持续优化提示词模板,提高 AI 生成代码的质量和效率。
## 相关文档
- [REQ-010-5.md](../requirements/REQ-010-5.md) - Metadata API客户端封装需求
- [ADR-0014.md](../decisions/adr/0014-metadata-api-client.md) - Metadata API客户端封装架构决策
- [Prompt-015.md](../prompts/015-metadata-api-client.md) - Metadata API客户端封装实现提示词
- [Session-20260118-002.md](../sessions/20260118-metadata-api-client.md) - Metadata API客户端封装执行会话
- [Change-015.md](../changelog/20260118-metadata-api-client.md) - Metadata API客户端封装变更记录
## 审核记录
| 日期 | 审核人 | 审核结果 | 审核意见 |
|------|--------|----------|----------|
| 2026-01-18 | Datai Team | 已通过 | 复盘完整,改进建议合理 |
## 变更历史
| 日期 | 版本 | 变更内容 | 变更人 |
|------|------|---------|--------|
| 2026-01-18 | v1.0.0 | 初始版本 | Datai Team |