26 KiB
26 KiB
Prompt - 元数据任务定义管理
输入引用
引用相关的 docs 文档链接:
- REQ-010-4.md - 元数据任务定义管理需求文档
- REQ-010-1.md - 数据库表结构设计和创建
- REQ-010-2.md - 基础实体类和Mapper创建
- REQ-010-3.md - Salesforce组织配置管理
- 0013-metadata-task-management.md - 元数据任务定义管理架构决策
Context Maps
强制列出本次 Prompt 依赖的 Canvas 文件:
- Authentication.canvas - 项目架构视觉化展示
- 相关节点: 集成任务 - 处理Salesforce的定时同步任务
目标
实现元数据任务定义的完整管理功能,包括:
- 任务定义 CRUD 功能 - 支持分页查询、条件查询
- package.xml 内容配置 - 支持可视化编辑,符合 Salesforce Metadata API 规范
- API 版本选择 - 支持多种 API 版本
- 调度类型选择 - 支持 Manual/Cron 调度类型
- Cron 表达式配置 - 支持 Cron 表达式配置和验证
- 任务验证功能 - 验证 package.xml 和调度配置
输出格式
代码输出格式
1. 枚举类
package com.datai.salesforce.metadata.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 调度类型枚举
*/
public enum ScheduleType {
MANUAL("manual", "手动执行"),
CRON("cron", "定时执行");
@EnumValue
private final String code;
@JsonValue
private final String displayName;
ScheduleType(String code, String displayName) {
this.code = code;
this.displayName = displayName;
}
public String getCode() {
return code;
}
public String getDisplayName() {
return displayName;
}
public static ScheduleType fromCode(String code) {
for (ScheduleType type : ScheduleType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Invalid schedule type code: " + code);
}
}
2. package.xml 编辑器
package com.datai.salesforce.metadata.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
/**
* package.xml 编辑器
*/
public class PackageXmlEditor {
private static final String PACKAGE_XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Package xmlns=\"http://soap.sforce.com/2006/04/metadata\">\n</Package>";
/**
* 创建 package.xml
*/
public String createPackageXml() {
return PACKAGE_XML_HEADER;
}
/**
* 添加元数据类型
*/
public String addMetadataType(String packageXml, String metadataType) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(packageXml)));
Element root = document.getDocumentElement();
Element typesElement = document.createElement("types");
root.appendChild(typesElement);
Element nameElement = document.createElement("name");
nameElement.setTextContent(metadataType);
typesElement.appendChild(nameElement);
return documentToString(document);
} catch (Exception e) {
throw new RuntimeException("Failed to add metadata type", e);
}
}
/**
* 添加元数据成员
*/
public String addMetadataMember(String packageXml, String metadataType, String member) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(packageXml)));
Element root = document.getDocumentElement();
NodeList typesList = root.getElementsByTagName("types");
for (int i = 0; i < typesList.getLength(); i++) {
Element typesElement = (Element) typesList.item(i);
NodeList nameList = typesElement.getElementsByTagName("name");
if (nameList.getLength() > 0) {
Element nameElement = (Element) nameList.item(0);
if (nameElement.getTextContent().equals(metadataType)) {
Element membersElement = document.createElement("members");
membersElement.setTextContent(member);
typesElement.appendChild(membersElement);
break;
}
}
}
return documentToString(document);
} catch (Exception e) {
throw new RuntimeException("Failed to add metadata member", e);
}
}
/**
* 删除元数据类型
*/
public String removeMetadataType(String packageXml, String metadataType) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(packageXml)));
Element root = document.getDocumentElement();
NodeList typesList = root.getElementsByTagName("types");
for (int i = 0; i < typesList.getLength(); i++) {
Element typesElement = (Element) typesList.item(i);
NodeList nameList = typesElement.getElementsByTagName("name");
if (nameList.getLength() > 0) {
Element nameElement = (Element) nameList.item(0);
if (nameElement.getTextContent().equals(metadataType)) {
root.removeChild(typesElement);
break;
}
}
}
return documentToString(document);
} catch (Exception e) {
throw new RuntimeException("Failed to remove metadata type", e);
}
}
/**
* 获取元数据类型列表
*/
public List<String> getMetadataTypes(String packageXml) {
List<String> metadataTypes = new ArrayList<>();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(packageXml)));
Element root = document.getDocumentElement();
NodeList typesList = root.getElementsByTagName("types");
for (int i = 0; i < typesList.getLength(); i++) {
Element typesElement = (Element) typesList.item(i);
NodeList nameList = typesElement.getElementsByTagName("name");
if (nameList.getLength() > 0) {
Element nameElement = (Element) nameList.item(0);
metadataTypes.add(nameElement.getTextContent());
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to get metadata types", e);
}
return metadataTypes;
}
/**
* 验证 package.xml
*/
public boolean validatePackageXml(String packageXml) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.parse(new InputSource(new StringReader(packageXml)));
return true;
} catch (Exception e) {
return false;
}
}
private String documentToString(Document document) throws Exception {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(document), new StreamResult(writer));
return writer.toString();
}
}
3. Cron 表达式验证器
package com.datai.salesforce.metadata.util;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* Cron 表达式验证器
*/
@Component
public class CronExpressionValidator {
/**
* 验证 Cron 表达式
*/
public ValidationResult validate(String cronExpression) {
ValidationResult result = new ValidationResult();
result.setValid(true);
if (cronExpression == null || cronExpression.trim().isEmpty()) {
result.setValid(false);
result.addError("Cron expression cannot be empty");
return result;
}
String[] parts = cronExpression.split("\\s+");
if (parts.length != 6) {
result.setValid(false);
result.addError("Cron expression must have 6 parts (second minute hour day month weekday)");
return result;
}
if (!validatePart(parts[0], 0, 59, "second")) {
result.setValid(false);
result.addError("Invalid second value");
}
if (!validatePart(parts[1], 0, 59, "minute")) {
result.setValid(false);
result.addError("Invalid minute value");
}
if (!validatePart(parts[2], 0, 23, "hour")) {
result.setValid(false);
result.addError("Invalid hour value");
}
if (!validatePart(parts[3], 1, 31, "day")) {
result.setValid(false);
result.addError("Invalid day value");
}
if (!validatePart(parts[4], 1, 12, "month")) {
result.setValid(false);
result.addError("Invalid month value");
}
if (!validatePart(parts[5], 0, 6, "weekday")) {
result.setValid(false);
result.addError("Invalid weekday value");
}
return result;
}
private boolean validatePart(String part, int min, int max, String partName) {
if (part.equals("*")) {
return true;
}
if (part.contains("/")) {
String[] rangeParts = part.split("/");
if (rangeParts.length != 2) {
return false;
}
try {
int step = Integer.parseInt(rangeParts[1]);
if (step <= 0) {
return false;
}
return validatePart(rangeParts[0], min, max, partName);
} catch (NumberFormatException e) {
return false;
}
}
if (part.contains("-")) {
String[] rangeParts = part.split("-");
if (rangeParts.length != 2) {
return false;
}
try {
int start = Integer.parseInt(rangeParts[0]);
int end = Integer.parseInt(rangeParts[1]);
return start >= min && end <= max && start <= end;
} catch (NumberFormatException e) {
return false;
}
}
if (part.contains(",")) {
String[] values = part.split(",");
for (String value : values) {
try {
int num = Integer.parseInt(value);
if (num < min || num > max) {
return false;
}
} catch (NumberFormatException e) {
return false;
}
}
return true;
}
try {
int num = Integer.parseInt(part);
return num >= min && num <= max;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 验证结果
*/
public static class ValidationResult {
private boolean valid = true;
private List<String> errors = new ArrayList<>();
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public List<String> getErrors() {
return errors;
}
public void addError(String error) {
this.valid = false;
this.errors.add(error);
}
}
}
4. 控制器
package com.datai.salesforce.metadata.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.service.IMetadataTaskService;
import com.datai.salesforce.metadata.service.ITaskValidationService;
import com.datai.salesforce.metadata.validation.TaskValidationResult;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 元数据任务控制器
*/
@RestController
@RequestMapping("/api/metadata-task")
@RequiredArgsConstructor
@Validated
public class MetadataTaskController {
private final IMetadataTaskService metadataTaskService;
private final ITaskValidationService taskValidationService;
/**
* 创建元数据任务
*/
@PostMapping
public DataiMetaTask createMetadataTask(@Valid @RequestBody DataiMetaTask task) {
return metadataTaskService.createMetadataTask(task);
}
/**
* 更新元数据任务
*/
@PutMapping("/{id}")
public DataiMetaTask updateMetadataTask(@PathVariable Long id, @Valid @RequestBody DataiMetaTask task) {
task.setId(id);
return metadataTaskService.updateMetadataTask(task);
}
/**
* 删除元数据任务
*/
@DeleteMapping("/{id}")
public void deleteMetadataTask(@PathVariable Long id) {
metadataTaskService.deleteMetadataTask(id);
}
/**
* 查询元数据任务详情
*/
@GetMapping("/{id}")
public DataiMetaTask getMetadataTask(@PathVariable Long id) {
return metadataTaskService.getMetadataTask(id);
}
/**
* 查询元数据任务列表
*/
@GetMapping
public Page<DataiMetaTask> getMetadataTaskList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String taskName,
@RequestParam(required = false) String scheduleType) {
return metadataTaskService.getMetadataTaskList(page, size, taskName, scheduleType);
}
/**
* 验证元数据任务
*/
@PostMapping("/{id}/validate")
public TaskValidationResult validateMetadataTask(@PathVariable Long id) {
return taskValidationService.validateMetadataTask(id);
}
}
5. 服务接口
package com.datai.salesforce.metadata.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
/**
* 元数据任务服务接口
*/
public interface IMetadataTaskService {
/**
* 创建元数据任务
*/
DataiMetaTask createMetadataTask(DataiMetaTask task);
/**
* 更新元数据任务
*/
DataiMetaTask updateMetadataTask(DataiMetaTask task);
/**
* 删除元数据任务
*/
void deleteMetadataTask(Long id);
/**
* 查询元数据任务详情
*/
DataiMetaTask getMetadataTask(Long id);
/**
* 查询元数据任务列表
*/
Page<DataiMetaTask> getMetadataTaskList(int page, int size, String taskName, String scheduleType);
}
6. 服务实现
package com.datai.salesforce.metadata.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.enums.TaskStatus;
import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper;
import com.datai.salesforce.metadata.service.IMetadataTaskService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 元数据任务服务实现
*/
@Service
@RequiredArgsConstructor
public class MetadataTaskServiceImpl implements IMetadataTaskService {
private final DataiMetaTaskMapper taskMapper;
@Override
@Transactional
public DataiMetaTask createMetadataTask(DataiMetaTask task) {
task.setTaskStatus(TaskStatus.PENDING);
taskMapper.insert(task);
return task;
}
@Override
@Transactional
public DataiMetaTask updateMetadataTask(DataiMetaTask task) {
DataiMetaTask existing = taskMapper.selectById(task.getId());
if (existing == null) {
throw new RuntimeException("Task not found");
}
taskMapper.updateById(task);
return task;
}
@Override
@Transactional
public void deleteMetadataTask(Long id) {
taskMapper.deleteById(id);
}
@Override
public DataiMetaTask getMetadataTask(Long id) {
return taskMapper.selectById(id);
}
@Override
public Page<DataiMetaTask> getMetadataTaskList(int page, int size, String taskName, String scheduleType) {
QueryWrapper<DataiMetaTask> queryWrapper = new QueryWrapper<>();
if (taskName != null && !taskName.isEmpty()) {
queryWrapper.like("task_name", taskName);
}
if (scheduleType != null && !scheduleType.isEmpty()) {
queryWrapper.eq("schedule_type", scheduleType);
}
return taskMapper.selectPage(new Page<>(page, size), queryWrapper);
}
}
7. 任务验证服务接口
package com.datai.salesforce.metadata.service;
import com.datai.salesforce.metadata.validation.TaskValidationResult;
/**
* 任务验证服务接口
*/
public interface ITaskValidationService {
/**
* 验证元数据任务
*/
TaskValidationResult validateMetadataTask(Long id);
}
8. 任务验证服务实现
package com.datai.salesforce.metadata.service.impl;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper;
import com.datai.salesforce.metadata.service.ITaskValidationService;
import com.datai.salesforce.metadata.util.CronExpressionValidator;
import com.datai.salesforce.metadata.util.PackageXmlEditor;
import com.datai.salesforce.metadata.validation.TaskValidationResult;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 任务验证服务实现
*/
@Service
@RequiredArgsConstructor
public class TaskValidationServiceImpl implements ITaskValidationService {
private final DataiMetaTaskMapper taskMapper;
private final PackageXmlEditor packageXmlEditor;
private final CronExpressionValidator cronExpressionValidator;
@Override
public TaskValidationResult validateMetadataTask(Long id) {
DataiMetaTask task = taskMapper.selectById(id);
if (task == null) {
return TaskValidationResult.failure("Task not found");
}
TaskValidationResult result = new TaskValidationResult();
if (!packageXmlEditor.validatePackageXml(task.getPackageXml())) {
result.addError("Invalid package.xml format");
}
if (task.getScheduleType().equals("cron")) {
CronExpressionValidator.ValidationResult cronResult = cronExpressionValidator.validate(task.getCronExpression());
if (!cronResult.isValid()) {
result.addErrors(cronResult.getErrors());
}
}
return result;
}
}
9. 验证结果类
package com.datai.salesforce.metadata.validation;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 任务验证结果
*/
@Data
public class TaskValidationResult {
private boolean valid = true;
private List<String> errors = new ArrayList<>();
public static TaskValidationResult success() {
return new TaskValidationResult();
}
public static TaskValidationResult failure(String error) {
TaskValidationResult result = new TaskValidationResult();
result.setValid(false);
result.addError(error);
return result;
}
public void addError(String error) {
this.valid = false;
this.errors.add(error);
}
public void addErrors(List<String> errors) {
this.valid = false;
this.errors.addAll(errors);
}
}
10. 单元测试
package com.datai.salesforce.metadata.service.impl;
import com.datai.salesforce.metadata.entity.DataiMetaTask;
import com.datai.salesforce.metadata.enums.TaskStatus;
import com.datai.salesforce.metadata.mapper.DataiMetaTaskMapper;
import com.datai.salesforce.metadata.service.IMetadataTaskService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class MetadataTaskServiceImplTest {
@Mock
private DataiMetaTaskMapper taskMapper;
@InjectMocks
private MetadataTaskServiceImpl metadataTaskService;
@Test
void testCreateMetadataTask() {
DataiMetaTask task = new DataiMetaTask();
task.setTaskName("Test Task");
task.setScheduleType("manual");
when(taskMapper.insert(any(DataiMetaTask.class))).thenReturn(1);
DataiMetaTask result = metadataTaskService.createMetadataTask(task);
assertNotNull(result);
assertEquals(TaskStatus.PENDING, result.getTaskStatus());
verify(taskMapper, times(1)).insert(any(DataiMetaTask.class));
}
@Test
void testGetMetadataTaskList() {
when(taskMapper.selectPage(any(), any())).thenReturn(new Page<>());
Page<DataiMetaTask> result = metadataTaskService.getMetadataTaskList(1, 10, null, null);
assertNotNull(result);
verify(taskMapper, times(1)).selectPage(any(), any());
}
}
约束
技术栈限制
- 必须基于现有的 Spring Boot 3 + Vue 3 技术栈
- 必须使用 MyBatis Plus 作为持久层框架
架构约束
- 必须遵循 Authentication.canvas 中定义的架构和调用关系
- 必须在 datai-salesforce-metadata 模块下实现
API 约束
- 必须使用 RESTful API 设计接口
- 必须使用 @Valid 注解进行参数验证
- 必须支持分页查询和条件查询
package.xml 约束
- package.xml 格式必须符合 Salesforce Metadata API 规范
- package.xml 必须支持多种元数据类型配置
- package.xml 必须支持通配符配置
Cron 表达式约束
- Cron 表达式必须符合标准格式
- Cron 表达式必须支持秒、分、时、日、月、周
性能约束
- 元数据任务 CRUD 操作响应时间不超过 500ms
- package.xml 配置操作响应时间不超过 1s
- Cron 表达式验证操作响应时间不超过 500ms
- 任务验证操作响应时间不超过 1s
Rule Set
"请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。"
具体规则:
- 必须使用 Canvas 中定义的类名和方法名
- 必须遵循 Canvas 中定义的调用关系
- 必须参考 Canvas 中的流程图逻辑
- 必须使用现有的集成核心功能进行 API 调用
- 必须遵循现有的异常处理机制
- 必须遵循现有的日志记录规范
验收标准
功能完整性
- 任务定义 CRUD 功能正常工作,能够成功创建、编辑、删除、查询元数据任务
- package.xml 内容配置正常工作,能够正确配置和验证 package.xml
- API 版本选择正常工作,能够正确选择和显示 API 版本
- 调度类型选择正常工作,能够正确选择和显示调度类型
- Cron 表达式配置正常工作,能够正确配置和验证 Cron 表达式
- 任务验证功能正常工作,能够正确验证 package.xml 和调度配置
代码规范性
- 代码符合项目编码规范,有清晰的注释
- 单元测试覆盖率不低于 80%
- 集成测试覆盖率不低于 60%
- 代码审查通过,没有严重的问题
性能指标
- 元数据任务 CRUD 操作响应时间不超过 500ms
- package.xml 配置操作响应时间不超过 1s
- Cron 表达式验证操作响应时间不超过 500ms
- 任务验证操作响应时间不超过 1s
风险
输出质量风险
- package.xml 验证不准确可能导致验证错误
- Cron 表达式解析不准确可能导致验证错误
- 任务验证逻辑复杂可能导致验证不准确
技术实现风险
- DOM 解析器内存占用高可能导致性能问题
- Cron 表达式解析库不支持某些格式可能导致验证错误
- 多任务管理复杂可能导致任务冲突
时间成本风险
- 功能复杂可能导致开发周期延长
- 测试覆盖面广可能导致测试时间增加
- 文档编写详细可能导致文档时间增加
使用记录
| 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 |
|---|---|---|---|---|---|
| 2026-01-19 | 实现任务定义 CRUD 功能 | REQ-010-4 需求文档 | MetadataTaskController、IMetadataTaskService、MetadataTaskServiceImpl | 功能正常,符合需求 | 无 |
| 2026-01-19 | 实现 package.xml 内容配置 | REQ-010-4 需求文档 | PackageXmlEditor | 功能正常,符合需求 | 无 |
| 2026-01-19 | 实现 Cron 表达式配置 | REQ-010-4 需求文档 | CronExpressionValidator | 功能正常,符合需求 | 无 |
| 2026-01-19 | 实现任务验证功能 | REQ-010-4 需求文档 | ITaskValidationService、TaskValidationServiceImpl | 功能正常,符合需求 | 无 |