datai/docs/archive/prompts/004-metadata-task-management.md

26 KiB
Raw Blame History

Prompt - 元数据任务定义管理

输入引用

引用相关的 docs 文档链接:

Context Maps

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

目标

实现元数据任务定义的完整管理功能,包括:

  1. 任务定义 CRUD 功能 - 支持分页查询、条件查询
  2. package.xml 内容配置 - 支持可视化编辑,符合 Salesforce Metadata API 规范
  3. API 版本选择 - 支持多种 API 版本
  4. 调度类型选择 - 支持 Manual/Cron 调度类型
  5. Cron 表达式配置 - 支持 Cron 表达式配置和验证
  6. 任务验证功能 - 验证 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 功能正常,符合需求