856 lines
26 KiB
Markdown
856 lines
26 KiB
Markdown
# Prompt - 元数据任务定义管理
|
||
|
||
## 输入引用
|
||
|
||
引用相关的 docs 文档链接:
|
||
|
||
- [REQ-010-4.md](../requirements/REQ-010-4.md) - 元数据任务定义管理需求文档
|
||
- [REQ-010-1.md](../requirements/REQ-010-1.md) - 数据库表结构设计和创建
|
||
- [REQ-010-2.md](../requirements/REQ-010-2.md) - 基础实体类和Mapper创建
|
||
- [REQ-010-3.md](../requirements/REQ-010-3.md) - Salesforce组织配置管理
|
||
- [0013-metadata-task-management.md](../decisions/adr/0013-metadata-task-management.md) - 元数据任务定义管理架构决策
|
||
|
||
## Context Maps
|
||
|
||
强制列出本次 Prompt 依赖的 Canvas 文件:
|
||
|
||
- [Authentication.canvas](../../Authentication.canvas) - 项目架构视觉化展示
|
||
- **相关节点**: [集成任务](node_integration_task) - 处理Salesforce的定时同步任务
|
||
|
||
## 目标
|
||
|
||
实现元数据任务定义的完整管理功能,包括:
|
||
1. 任务定义 CRUD 功能 - 支持分页查询、条件查询
|
||
2. package.xml 内容配置 - 支持可视化编辑,符合 Salesforce Metadata API 规范
|
||
3. API 版本选择 - 支持多种 API 版本
|
||
4. 调度类型选择 - 支持 Manual/Cron 调度类型
|
||
5. Cron 表达式配置 - 支持 Cron 表达式配置和验证
|
||
6. 任务验证功能 - 验证 package.xml 和调度配置
|
||
|
||
## 输出格式
|
||
|
||
### 代码输出格式
|
||
|
||
#### 1. 枚举类
|
||
|
||
```java
|
||
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 编辑器
|
||
|
||
```java
|
||
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 表达式验证器
|
||
|
||
```java
|
||
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. 控制器
|
||
|
||
```java
|
||
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. 服务接口
|
||
|
||
```java
|
||
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. 服务实现
|
||
|
||
```java
|
||
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. 任务验证服务接口
|
||
|
||
```java
|
||
package com.datai.salesforce.metadata.service;
|
||
|
||
import com.datai.salesforce.metadata.validation.TaskValidationResult;
|
||
|
||
/**
|
||
* 任务验证服务接口
|
||
*/
|
||
public interface ITaskValidationService {
|
||
|
||
/**
|
||
* 验证元数据任务
|
||
*/
|
||
TaskValidationResult validateMetadataTask(Long id);
|
||
}
|
||
```
|
||
|
||
#### 8. 任务验证服务实现
|
||
|
||
```java
|
||
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. 验证结果类
|
||
|
||
```java
|
||
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. 单元测试
|
||
|
||
```java
|
||
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 | 功能正常,符合需求 | 无 |
|