- 完成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方法论创建,包括需求定义、架构决策、提示词资产化、执行会话、变更记录和闭环复盘。
804 lines
27 KiB
Markdown
804 lines
27 KiB
Markdown
# Prompt - Destructive Changes功能实现
|
||
|
||
## 输入引用
|
||
|
||
引用相关的 docs 文档链接:
|
||
|
||
- [REQ-010-10.md](../requirements/REQ-010-10.md) - Destructive Changes功能实现需求文档
|
||
- [0019-destructive-changes.md](../decisions/adr/0019-destructive-changes.md) - Destructive Changes功能实现架构决策
|
||
- [REQ-010-8.md](../requirements/REQ-010-8.md) - 元数据部署核心功能需求文档
|
||
- [0017-metadata-deploy-core.md](../decisions/adr/0017-metadata-deploy-core.md) - 元数据部署核心功能架构决策
|
||
- [metadata-module.md](../reference-code/com/docs/metadata-module.md) - Salesforce Metadata API 模块说明
|
||
|
||
## Context Maps
|
||
|
||
强制列出本次 Prompt 依赖的 Canvas 文件:
|
||
|
||
- [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示
|
||
- **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式
|
||
- **相关节点**: [IMetadataApiService](node_metadata_api_service) - 元数据API服务接口
|
||
- **相关节点**: [MetadataApiClient](node_metadata_api_client) - Metadata API客户端
|
||
|
||
## 目标
|
||
|
||
实现 Destructive Changes 功能,包括 destructiveChanges.xml 配置、Destructive Changes 部署、Destructive Changes 验证、Destructive Changes 历史记录、Destructive Changes 回滚。支持部署时删除指定的元数据组件,使用 destructiveChanges.xml 配置删除规则。
|
||
|
||
## 输出格式
|
||
|
||
### 代码结构
|
||
|
||
```
|
||
datai-salesforce-metadata/
|
||
├── destructive-changes/
|
||
│ ├── controller/
|
||
│ │ └── DestructiveChangesController.java
|
||
│ ├── service/
|
||
│ │ ├── IDestructiveChangesService.java
|
||
│ │ ├── DestructiveChangesServiceImpl.java
|
||
│ │ ├── IDestructiveChangesConfigService.java
|
||
│ │ └── DestructiveChangesConfigServiceImpl.java
|
||
│ ├── dto/
|
||
│ │ ├── DestructiveChangesConfigRequest.java
|
||
│ │ ├── DestructiveChangesConfigResponse.java
|
||
│ │ ├── DestructiveChangesDeployRequest.java
|
||
│ │ ├── DestructiveChangesDeployResponse.java
|
||
│ │ ├── DestructiveChangesValidateRequest.java
|
||
│ │ ├── DestructiveChangesValidateResponse.java
|
||
│ │ ├── DestructiveChangesHistoryResponse.java
|
||
│ │ ├── DestructiveChangesRollbackRequest.java
|
||
│ │ └── DestructiveChangesRollbackResponse.java
|
||
│ ├── entity/
|
||
│ │ └── DestructiveChangeRule.java
|
||
│ ├── mapper/
|
||
│ │ └── DestructiveChangeRuleMapper.java
|
||
│ ├── enums/
|
||
│ │ └── MetadataType.java
|
||
│ ├── util/
|
||
│ │ ├── DestructiveChangesXmlParser.java
|
||
│ │ └── DestructiveChangesXmlBuilder.java
|
||
│ └── exception/
|
||
│ └── DestructiveChangesException.java
|
||
```
|
||
|
||
### 代码示例(语言:Java)
|
||
|
||
#### 1. DestructiveChangesController 控制器
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.controller;
|
||
|
||
import com.datai.salesforce.metadata.destructive.dto.*;
|
||
import com.datai.salesforce.metadata.destructive.service.IDestructiveChangesService;
|
||
import com.datai.salesforce.metadata.destructive.service.IDestructiveChangesConfigService;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.web.bind.annotation.*;
|
||
|
||
@RestController
|
||
@RequestMapping("/metadata/destructive-changes")
|
||
public class DestructiveChangesController {
|
||
|
||
@Autowired
|
||
private IDestructiveChangesService destructiveChangesService;
|
||
|
||
@Autowired
|
||
private IDestructiveChangesConfigService destructiveChangesConfigService;
|
||
|
||
@PostMapping("/config")
|
||
public DestructiveChangesConfigResponse saveConfig(@RequestBody DestructiveChangesConfigRequest request) {
|
||
return destructiveChangesConfigService.saveConfig(request);
|
||
}
|
||
|
||
@GetMapping("/config/{taskId}")
|
||
public DestructiveChangesConfigResponse getConfig(@PathVariable Long taskId) {
|
||
return destructiveChangesConfigService.getConfig(taskId);
|
||
}
|
||
|
||
@PostMapping("/validate")
|
||
public DestructiveChangesValidateResponse validateConfig(@RequestBody DestructiveChangesValidateRequest request) {
|
||
return destructiveChangesConfigService.validateConfig(request);
|
||
}
|
||
|
||
@PostMapping("/deploy")
|
||
public DestructiveChangesDeployResponse deploy(@RequestBody DestructiveChangesDeployRequest request) {
|
||
return destructiveChangesService.deploy(request);
|
||
}
|
||
|
||
@GetMapping("/history")
|
||
public DestructiveChangesHistoryResponse getHistory(
|
||
@RequestParam(required = false) Long orgConfigId,
|
||
@RequestParam(required = false) String status,
|
||
@RequestParam(defaultValue = "1") int page,
|
||
@RequestParam(defaultValue = "10") int size) {
|
||
return destructiveChangesService.getHistory(orgConfigId, status, page, size);
|
||
}
|
||
|
||
@PostMapping("/rollback/{jobId}")
|
||
public DestructiveChangesRollbackResponse rollback(@PathVariable Long jobId) {
|
||
return destructiveChangesService.rollback(jobId);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. IDestructiveChangesService 服务接口
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.service;
|
||
|
||
import com.datai.salesforce.metadata.destructive.dto.*;
|
||
|
||
public interface IDestructiveChangesService {
|
||
|
||
DestructiveChangesDeployResponse deploy(DestructiveChangesDeployRequest request);
|
||
|
||
DestructiveChangesHistoryResponse getHistory(Long orgConfigId, String status, int page, int size);
|
||
|
||
DestructiveChangesRollbackResponse rollback(Long jobId);
|
||
}
|
||
```
|
||
|
||
#### 3. DestructiveChangesServiceImpl 服务实现
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.service;
|
||
|
||
import com.datai.salesforce.metadata.destructive.dto.*;
|
||
import com.datai.salesforce.metadata.destructive.entity.DestructiveChangeRule;
|
||
import com.datai.salesforce.metadata.destructive.mapper.DestructiveChangeRuleMapper;
|
||
import com.datai.salesforce.metadata.destructive.util.DestructiveChangesXmlBuilder;
|
||
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.io.File;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.concurrent.Future;
|
||
|
||
@Service
|
||
public class DestructiveChangesServiceImpl implements IDestructiveChangesService {
|
||
|
||
@Autowired
|
||
private IMetadataApiService metadataApiService;
|
||
|
||
@Autowired
|
||
private DataiMetaJobExecutionMapper jobExecutionMapper;
|
||
|
||
@Autowired
|
||
private DestructiveChangeRuleMapper destructiveChangeRuleMapper;
|
||
|
||
@Autowired
|
||
private DestructiveChangesXmlBuilder xmlBuilder;
|
||
|
||
@Override
|
||
@Transactional
|
||
public DestructiveChangesDeployResponse deploy(DestructiveChangesDeployRequest request) {
|
||
QueryWrapper<DestructiveChangeRule> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("task_id", request.getTaskId());
|
||
List<DestructiveChangeRule> rules = destructiveChangeRuleMapper.selectList(queryWrapper);
|
||
|
||
if (rules.isEmpty()) {
|
||
throw new RuntimeException("No destructive change rules found");
|
||
}
|
||
|
||
String destructiveChangesXml = xmlBuilder.buildXml(rules);
|
||
File destructiveChangesFile = xmlBuilder.saveXmlToFile(request.getTaskId(), destructiveChangesXml);
|
||
|
||
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
|
||
jobExecution.setTaskId(request.getTaskId());
|
||
jobExecution.setOrgConfigId(request.getOrgConfigId());
|
||
jobExecution.setJobType("DESTRUCTIVE_CHANGES");
|
||
jobExecution.setStatus("PENDING");
|
||
jobExecution.setCreatedBy(request.getCreatedBy());
|
||
jobExecutionMapper.insert(jobExecution);
|
||
|
||
deployAsync(jobExecution.getId(), request.getTaskId(), request.getOrgConfigId(), destructiveChangesFile);
|
||
|
||
DestructiveChangesDeployResponse response = new DestructiveChangesDeployResponse();
|
||
response.setJobId(jobExecution.getId().toString());
|
||
response.setStatus("PENDING");
|
||
response.setMessage("Destructive Changes deploy task created");
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
@Async("deployExecutor")
|
||
public Future<Void> deployAsync(Long jobExecutionId, Long taskId, Long orgConfigId, File destructiveChangesFile) {
|
||
String jobId = jobExecutionId.toString();
|
||
|
||
try {
|
||
updateJobStatus(jobExecutionId, "PROCESSING");
|
||
|
||
String deployResult = metadataApiService.deploy(orgConfigId, null, destructiveChangesFile);
|
||
|
||
updateJobStatus(jobExecutionId, "SUCCESS");
|
||
|
||
parseDeployResult(jobExecutionId, deployResult);
|
||
|
||
} catch (Exception e) {
|
||
updateJobStatus(jobExecutionId, "FAILED");
|
||
throw new RuntimeException("Destructive Changes deploy failed", e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public DestructiveChangesHistoryResponse getHistory(Long orgConfigId, String status, int page, int size) {
|
||
QueryWrapper<DataiMetaJobExecution> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("job_type", "DESTRUCTIVE_CHANGES");
|
||
|
||
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);
|
||
|
||
DestructiveChangesHistoryResponse response = new DestructiveChangesHistoryResponse();
|
||
response.setTotal(pageResult.getTotal());
|
||
response.setPage(page);
|
||
response.setSize(size);
|
||
response.setRecords(pageResult.getRecords());
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
@Transactional
|
||
public DestructiveChangesRollbackResponse rollback(Long jobId) {
|
||
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobId);
|
||
if (jobExecution == null) {
|
||
throw new RuntimeException("Job not found");
|
||
}
|
||
|
||
if (!"SUCCESS".equals(jobExecution.getStatus())) {
|
||
throw new RuntimeException("Only successful deployments can be rolled back");
|
||
}
|
||
|
||
String backupData = jobExecution.getBackupData();
|
||
if (backupData == null || backupData.isEmpty()) {
|
||
throw new RuntimeException("No backup data found");
|
||
}
|
||
|
||
rollbackAsync(jobId, jobExecution.getOrgConfigId(), backupData);
|
||
|
||
DestructiveChangesRollbackResponse response = new DestructiveChangesRollbackResponse();
|
||
response.setJobId(jobId.toString());
|
||
response.setStatus("ROLLBACK_IN_PROGRESS");
|
||
response.setMessage("Rollback task created");
|
||
|
||
return response;
|
||
}
|
||
|
||
@Async("deployExecutor")
|
||
public Future<Void> rollbackAsync(Long jobId, Long orgConfigId, String backupData) {
|
||
try {
|
||
updateJobStatus(jobId, "ROLLBACK_PROCESSING");
|
||
|
||
String rollbackResult = metadataApiService.rollback(orgConfigId, backupData);
|
||
|
||
updateJobStatus(jobId, "ROLLBACK_SUCCESS");
|
||
|
||
parseRollbackResult(jobId, rollbackResult);
|
||
|
||
} catch (Exception e) {
|
||
updateJobStatus(jobId, "ROLLBACK_FAILED");
|
||
throw new RuntimeException("Rollback failed", e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private void updateJobStatus(Long jobExecutionId, String status) {
|
||
DataiMetaJobExecution jobExecution = new DataiMetaJobExecution();
|
||
jobExecution.setId(jobExecutionId);
|
||
jobExecution.setStatus(status);
|
||
jobExecution.setUpdateTime(new Date());
|
||
jobExecutionMapper.updateById(jobExecution);
|
||
}
|
||
|
||
private void parseDeployResult(Long jobExecutionId, String deployResult) {
|
||
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobExecutionId);
|
||
if (jobExecution != null) {
|
||
jobExecution.setRemark(deployResult);
|
||
jobExecutionMapper.updateById(jobExecution);
|
||
}
|
||
}
|
||
|
||
private void parseRollbackResult(Long jobId, String rollbackResult) {
|
||
DataiMetaJobExecution jobExecution = jobExecutionMapper.selectById(jobId);
|
||
if (jobExecution != null) {
|
||
jobExecution.setRemark(rollbackResult);
|
||
jobExecutionMapper.updateById(jobExecution);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4. IDestructiveChangesConfigService 服务接口
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.service;
|
||
|
||
import com.datai.salesforce.metadata.destructive.dto.*;
|
||
|
||
public interface IDestructiveChangesConfigService {
|
||
|
||
DestructiveChangesConfigResponse saveConfig(DestructiveChangesConfigRequest request);
|
||
|
||
DestructiveChangesConfigResponse getConfig(Long taskId);
|
||
|
||
DestructiveChangesValidateResponse validateConfig(DestructiveChangesValidateRequest request);
|
||
}
|
||
```
|
||
|
||
#### 5. DestructiveChangesConfigServiceImpl 服务实现
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.service;
|
||
|
||
import com.datai.salesforce.metadata.destructive.dto.*;
|
||
import com.datai.salesforce.metadata.destructive.entity.DestructiveChangeRule;
|
||
import com.datai.salesforce.metadata.destructive.mapper.DestructiveChangeRuleMapper;
|
||
import com.datai.salesforce.metadata.destructive.util.DestructiveChangesXmlParser;
|
||
import com.datai.salesforce.metadata.destructive.util.DestructiveChangesXmlBuilder;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
|
||
import java.util.List;
|
||
|
||
@Service
|
||
public class DestructiveChangesConfigServiceImpl implements IDestructiveChangesConfigService {
|
||
|
||
@Autowired
|
||
private DestructiveChangeRuleMapper destructiveChangeRuleMapper;
|
||
|
||
@Autowired
|
||
private DestructiveChangesXmlParser xmlParser;
|
||
|
||
@Autowired
|
||
private DestructiveChangesXmlBuilder xmlBuilder;
|
||
|
||
@Override
|
||
@Transactional
|
||
public DestructiveChangesConfigResponse saveConfig(DestructiveChangesConfigRequest request) {
|
||
QueryWrapper<DestructiveChangeRule> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("task_id", request.getTaskId());
|
||
destructiveChangeRuleMapper.delete(queryWrapper);
|
||
|
||
for (DestructiveChangesConfigRequest.Rule rule : request.getRules()) {
|
||
DestructiveChangeRule entity = new DestructiveChangeRule();
|
||
entity.setTaskId(request.getTaskId());
|
||
entity.setMetadataType(rule.getMetadataType());
|
||
entity.setMemberName(rule.getMemberName());
|
||
destructiveChangeRuleMapper.insert(entity);
|
||
}
|
||
|
||
String xml = xmlBuilder.buildXml(request.getTaskId());
|
||
|
||
DestructiveChangesConfigResponse response = new DestructiveChangesConfigResponse();
|
||
response.setTaskId(request.getTaskId());
|
||
response.setXml(xml);
|
||
response.setMessage("Configuration saved successfully");
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
public DestructiveChangesConfigResponse getConfig(Long taskId) {
|
||
QueryWrapper<DestructiveChangeRule> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("task_id", taskId);
|
||
List<DestructiveChangeRule> rules = destructiveChangeRuleMapper.selectList(queryWrapper);
|
||
|
||
String xml = xmlBuilder.buildXml(taskId);
|
||
|
||
DestructiveChangesConfigResponse response = new DestructiveChangesConfigResponse();
|
||
response.setTaskId(taskId);
|
||
response.setXml(xml);
|
||
response.setRules(rules);
|
||
|
||
return response;
|
||
}
|
||
|
||
@Override
|
||
public DestructiveChangesValidateResponse validateConfig(DestructiveChangesValidateRequest request) {
|
||
try {
|
||
xmlParser.parseXml(request.getXml());
|
||
|
||
DestructiveChangesValidateResponse response = new DestructiveChangesValidateResponse();
|
||
response.setValid(true);
|
||
response.setMessage("Configuration is valid");
|
||
|
||
return response;
|
||
} catch (Exception e) {
|
||
DestructiveChangesValidateResponse response = new DestructiveChangesValidateResponse();
|
||
response.setValid(false);
|
||
response.setMessage(e.getMessage());
|
||
|
||
return response;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 6. DestructiveChangesXmlParser 工具类
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.util;
|
||
|
||
import org.springframework.stereotype.Component;
|
||
import org.w3c.dom.Document;
|
||
import org.w3c.dom.Element;
|
||
import org.w3c.dom.NodeList;
|
||
import org.xml.sax.SAXException;
|
||
|
||
import javax.xml.parsers.DocumentBuilder;
|
||
import javax.xml.parsers.DocumentBuilderFactory;
|
||
import javax.xml.parsers.ParserConfigurationException;
|
||
import java.io.ByteArrayInputStream;
|
||
import java.io.IOException;
|
||
|
||
@Component
|
||
public class DestructiveChangesXmlParser {
|
||
|
||
public void parseXml(String xml) throws Exception {
|
||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||
Document document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
|
||
|
||
Element root = document.getDocumentElement();
|
||
if (!"Package".equals(root.getNodeName())) {
|
||
throw new RuntimeException("Root element must be 'Package'");
|
||
}
|
||
|
||
NodeList types = root.getElementsByTagName("types");
|
||
for (int i = 0; i < types.getLength(); i++) {
|
||
Element typeElement = (Element) types.item(i);
|
||
NodeList members = typeElement.getElementsByTagName("members");
|
||
NodeList name = typeElement.getElementsByTagName("name");
|
||
|
||
if (members.getLength() == 0) {
|
||
throw new RuntimeException("Type element must have at least one 'members' element");
|
||
}
|
||
|
||
if (name.getLength() != 1) {
|
||
throw new RuntimeException("Type element must have exactly one 'name' element");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 7. DestructiveChangesXmlBuilder 工具类
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.util;
|
||
|
||
import com.datai.salesforce.metadata.destructive.entity.DestructiveChangeRule;
|
||
import com.datai.salesforce.metadata.destructive.mapper.DestructiveChangeRuleMapper;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
import java.io.File;
|
||
import java.io.FileWriter;
|
||
import java.io.IOException;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.stream.Collectors;
|
||
|
||
@Component
|
||
public class DestructiveChangesXmlBuilder {
|
||
|
||
@Autowired
|
||
private DestructiveChangeRuleMapper destructiveChangeRuleMapper;
|
||
|
||
public String buildXml(Long taskId) {
|
||
QueryWrapper<DestructiveChangeRule> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.eq("task_id", taskId);
|
||
List<DestructiveChangeRule> rules = destructiveChangeRuleMapper.selectList(queryWrapper);
|
||
|
||
return buildXml(rules);
|
||
}
|
||
|
||
public String buildXml(List<DestructiveChangeRule> rules) {
|
||
StringBuilder xml = new StringBuilder();
|
||
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||
xml.append("<Package xmlns=\"http://soap.sforce.com/2006/04/metadata\">\n");
|
||
|
||
Map<String, List<DestructiveChangeRule>> groupedRules = rules.stream()
|
||
.collect(Collectors.groupingBy(DestructiveChangeRule::getMetadataType));
|
||
|
||
for (Map.Entry<String, List<DestructiveChangeRule>> entry : groupedRules.entrySet()) {
|
||
xml.append(" <types>\n");
|
||
for (DestructiveChangeRule rule : entry.getValue()) {
|
||
xml.append(" <members>").append(rule.getMemberName()).append("</members>\n");
|
||
}
|
||
xml.append(" <name>").append(entry.getKey()).append("</name>\n");
|
||
xml.append(" </types>\n");
|
||
}
|
||
|
||
xml.append(" <version>56.0</version>\n");
|
||
xml.append("</Package>");
|
||
|
||
return xml.toString();
|
||
}
|
||
|
||
public File saveXmlToFile(Long taskId, String xml) throws IOException {
|
||
String fileName = "destructiveChanges-" + taskId + ".xml";
|
||
File file = new File(System.getProperty("java.io.tmpdir"), fileName);
|
||
|
||
try (FileWriter writer = new FileWriter(file)) {
|
||
writer.write(xml);
|
||
}
|
||
|
||
return file;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 8. DestructiveChangeRule 实体类
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.entity;
|
||
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import lombok.Data;
|
||
|
||
import java.time.LocalDateTime;
|
||
|
||
@Data
|
||
@TableName("datai_destructive_change_rule")
|
||
public class DestructiveChangeRule {
|
||
|
||
@TableId(type = IdType.AUTO)
|
||
private Long id;
|
||
|
||
private Long taskId;
|
||
|
||
private String metadataType;
|
||
|
||
private String memberName;
|
||
|
||
private LocalDateTime createTime;
|
||
|
||
private LocalDateTime updateTime;
|
||
}
|
||
```
|
||
|
||
#### 9. DestructiveChangeRuleMapper 接口
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import com.datai.salesforce.metadata.destructive.entity.DestructiveChangeRule;
|
||
import org.apache.ibatis.annotations.Mapper;
|
||
|
||
@Mapper
|
||
public interface DestructiveChangeRuleMapper extends BaseMapper<DestructiveChangeRule> {
|
||
}
|
||
```
|
||
|
||
#### 10. DTO 类
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.dto;
|
||
|
||
import lombok.Data;
|
||
import java.util.List;
|
||
|
||
@Data
|
||
public class DestructiveChangesConfigRequest {
|
||
private Long taskId;
|
||
private List<Rule> rules;
|
||
|
||
@Data
|
||
public static class Rule {
|
||
private String metadataType;
|
||
private String memberName;
|
||
}
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesConfigResponse {
|
||
private Long taskId;
|
||
private String xml;
|
||
private List<Object> rules;
|
||
private String message;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesValidateRequest {
|
||
private String xml;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesValidateResponse {
|
||
private boolean valid;
|
||
private String message;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesDeployRequest {
|
||
private Long taskId;
|
||
private Long orgConfigId;
|
||
private String createdBy;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesDeployResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesHistoryResponse {
|
||
private long total;
|
||
private int page;
|
||
private int size;
|
||
private List<Object> records;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesRollbackRequest {
|
||
private Long jobId;
|
||
}
|
||
|
||
@Data
|
||
public class DestructiveChangesRollbackResponse {
|
||
private String jobId;
|
||
private String status;
|
||
private String message;
|
||
}
|
||
```
|
||
|
||
#### 11. DestructiveChangesException 异常类
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.destructive.exception;
|
||
|
||
public class DestructiveChangesException extends RuntimeException {
|
||
|
||
public DestructiveChangesException(String message) {
|
||
super(message);
|
||
}
|
||
|
||
public DestructiveChangesException(String message, Throwable cause) {
|
||
super(message, cause);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 约束
|
||
|
||
### 技术栈限制
|
||
|
||
- 必须基于 Spring Boot 3 + MyBatis Plus + MySQL 技术栈
|
||
- 必须使用现有的 IMetadataApiService 接口进行 Salesforce API 调用
|
||
- 必须使用现有的 DataiMetaJobExecution 实体和 Mapper
|
||
- 必须使用 Java XML 解析库(如 JAXB 或 DOM4J)处理 destructiveChanges.xml
|
||
|
||
### 架构约束
|
||
|
||
- 必须遵循 Authentication.canvas 中定义的架构和调用关系
|
||
- 必须在 datai-salesforce-metadata 模块下实现
|
||
- 必须使用 RESTful API 设计规范
|
||
|
||
### 性能要求
|
||
|
||
- destructiveChanges.xml 配置响应时间 < 1s
|
||
- Destructive Changes 部署触发响应时间 < 1s
|
||
- Destructive Changes 验证响应时间 < 500ms
|
||
- Destructive Changes 历史记录查询响应时间 < 1s
|
||
- Destructive Changes 回滚响应时间 < 2s
|
||
|
||
### 安全性要求
|
||
|
||
- Destructive Changes 操作需要认证
|
||
- 删除操作需要备份,支持回滚
|
||
- 部署数据传输加密
|
||
- 敏感信息不记录到日志
|
||
|
||
## Rule Set
|
||
|
||
"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。"
|
||
|
||
**具体规则**:
|
||
- 必须使用 Canvas 中定义的类名和方法名
|
||
- 必须遵循 Canvas 中定义的调用关系
|
||
- 必须参考 Canvas 中的流程图逻辑
|
||
- 必须复用现有的 DeployStatus 枚举和状态管理逻辑
|
||
- 必须复用现有的 AsyncConfig 配置类和线程池
|
||
|
||
## 验收标准
|
||
|
||
### 功能完整性
|
||
|
||
- destructiveChanges.xml 配置成功,支持可视化编辑
|
||
- destructiveChanges.xml 格式符合 Salesforce Metadata API 规范
|
||
- 支持多种元数据类型配置
|
||
- 支持通配符配置
|
||
- destructiveChanges.xml 验证功能正常工作
|
||
- Destructive Changes 部署成功,支持删除指定的元数据组件
|
||
- 支持批量删除
|
||
- 删除操作安全可靠
|
||
- API 接口符合 RESTful 规范
|
||
- Destructive Changes 验证功能正常工作
|
||
- destructiveChanges.xml 格式验证正确
|
||
- 删除规则验证正确
|
||
- 验证失败返回详细错误信息
|
||
- Destructive Changes 历史记录成功
|
||
- 支持分页查询
|
||
- 支持条件查询
|
||
- 历史记录完整
|
||
- Destructive Changes 回滚功能正常工作
|
||
- 支持恢复被删除的元数据组件
|
||
- 回滚操作安全可靠
|
||
- 回滚日志记录完整
|
||
|
||
### 代码正确性
|
||
|
||
- 代码符合项目编码规范,有清晰的注释
|
||
- 单元测试覆盖率 > 80%
|
||
- 集成测试通过率 100%
|
||
- 无严重的代码质量问题
|
||
|
||
### 文档准确性
|
||
|
||
- API 文档完整,包含接口说明、参数说明、返回值说明
|
||
- 代码注释清晰,包含方法说明、参数说明、返回值说明
|
||
- 使用文档完整,包含使用示例和注意事项
|
||
|
||
### 性能指标
|
||
|
||
- destructiveChanges.xml 配置响应时间 < 1s
|
||
- Destructive Changes 部署触发响应时间 < 1s
|
||
- Destructive Changes 验证响应时间 < 500ms
|
||
- Destructive Changes 历史记录查询响应时间 < 1s
|
||
- Destructive Changes 回滚响应时间 < 2s
|
||
|
||
## 风险
|
||
|
||
### 输出质量风险
|
||
|
||
- destructiveChanges.xml 格式验证不完善可能导致误删除
|
||
- 删除操作不可逆可能导致数据丢失
|
||
- 回滚功能不完善可能导致无法恢复
|
||
|
||
### 技术实现风险
|
||
|
||
- XML 解析库选择不当可能导致解析错误
|
||
- 备份和回滚逻辑复杂可能导致实现困难
|
||
- 异步任务状态跟踪困难
|
||
|
||
### 时间成本风险
|
||
|
||
- Destructive Changes 功能实现可能需要较长时间
|
||
- 测试和验证可能需要较长时间
|
||
- 文档编写可能需要较长时间
|
||
|
||
## 使用记录
|
||
|
||
| 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 |
|
||
|------|---------|---------|---------|------|----------|
|
||
| 2026-01-19 | Destructive Changes功能实现 | REQ-010-10, ADR-0019 | Destructive Changes功能代码 | 待反馈 | 待改进 |
|