feat(aima): 添加爱玛任务和测试用例复制功能

- 在AimaTaskController中添加/copy/{id}端点支持任务复制
- 在AimaTestCaseController中添加/copy/{id}端点支持用例复制
- 实现AimaTaskServiceImpl.copyAimaTask方法完成任务及关联配置复制
- 实现AimaTestCaseServiceImpl.copyAimaTestCase方法完成用例及检测项复制
- 添加EntityCopyUtils工具类处理复制时的名称和编码生成
- 更新服务接口定义IAimaTaskService和IAimaTestCaseService
- 实现完整的事务性复制逻辑包括关联数据的复制
- 添加复制操作的权限验证和日志记录功能
This commit is contained in:
lixiaolong 2026-08-14 09:08:18 +08:00
parent da679363a7
commit 519e4de846
19 changed files with 301 additions and 0 deletions

View File

@ -58,6 +58,14 @@ public class AimaTaskController extends BaseController {
return toAjax(aimaTaskService.insertAimaTask(aimaTask));
}
@ApiOperation("复制爱玛任务")
@PreAuthorize("@ss.hasPermi('aima:task:add')")
@Log(title = "爱玛任务管理", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id) {
return success(aimaTaskService.copyAimaTask(id));
}
@ApiOperation("修改任务")
@PreAuthorize("@ss.hasPermi('aima:task:edit')")
@Log(title = "任务管理", businessType = BusinessType.UPDATE)

View File

@ -58,6 +58,14 @@ public class AimaTestCaseController extends BaseController {
return toAjax(aimaTestCaseService.insertAimaTestCase(aimaTestCase));
}
@ApiOperation("复制测试用例")
@PreAuthorize("@ss.hasPermi('aima:testcase:add')")
@Log(title = "测试用例管理", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id) {
return success(aimaTestCaseService.copyAimaTestCase(id));
}
@ApiOperation("修改测试用例")
@PreAuthorize("@ss.hasPermi('aima:testcase:edit')")
@Log(title = "测试用例管理", businessType = BusinessType.UPDATE)

View File

@ -89,6 +89,15 @@ public class InspectionTaskController extends BaseController
return toAjax(inspectionTaskService.insertInspectionTask(inspectionTask));
}
@ApiOperation("复制巡检任务")
@PreAuthorize("@ss.hasPermi('inspection:task:add')")
@Log(title = "巡检任务", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id)
{
return success(inspectionTaskService.copyInspectionTask(id));
}
/**
* 修改巡检任务
*/

View File

@ -88,6 +88,15 @@ public class InspectionWaypointController extends BaseController
return toAjax(inspectionWaypointService.insertInspectionWaypoint(inspectionWaypoint));
}
@ApiOperation("复制巡检点位")
@PreAuthorize("@ss.hasPermi('inspection:waypoint:add')")
@Log(title = "巡检点位", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id)
{
return success(inspectionWaypointService.copyInspectionWaypoint(id));
}
/**
* 修改巡检点位
*/

View File

@ -60,6 +60,14 @@ public class TeDetectionItemController extends BaseController {
return toAjax(rows);
}
@ApiOperation("复制检测项配置")
@PreAuthorize("@ss.hasPermi('test:detect:add')")
@Log(title = "检测项配置", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id) {
return success(teDetectionItemService.copyTeDetectionItem(id));
}
@ApiOperation("修改检测项配置")
@PreAuthorize("@ss.hasPermi('test:detect:edit')")
@Log(title = "检测项配置", businessType = BusinessType.UPDATE)

View File

@ -48,6 +48,14 @@ public class TeTaskConfigInfoController extends BaseController {
return toAjax(teTaskConfigInfoService.insertTeTaskConfigInfo(teTaskConfigInfo));
}
@ApiOperation("复制任务配置")
@PreAuthorize("@ss.hasPermi('test:config:add')")
@Log(title = "任务配置", businessType = BusinessType.INSERT)
@PostMapping("/copy/{id}")
public AjaxResult copy(@PathVariable String id) {
return success(teTaskConfigInfoService.copyTeTaskConfigInfo(id));
}
@ApiOperation("修改任务配置信息")
@PreAuthorize("@ss.hasPermi('test:config:edit')")
@Log(title = "任务配置信息", businessType = BusinessType.UPDATE)

View File

@ -16,6 +16,7 @@ public interface IAimaTaskService {
List<AimaTask> selectAimaTaskList(AimaTask aimaTask);
List<AimaTaskVo> selectAimaTaskVoList(AimaTask aimaTask);
int insertAimaTask(AimaTask aimaTask);
String copyAimaTask(String id);
int updateAimaTask(AimaTask aimaTask);
int deleteAimaTaskByIds(String[] ids);
int deleteAimaTaskById(String id);

View File

@ -43,6 +43,9 @@ public interface IAimaTestCaseService {
*/
int insertAimaTestCase(AimaTestCase aimaTestCase);
/** Copy a test case and its independent detection item. */
String copyAimaTestCase(String id);
/**
* 修改爱玛测试用例
*

View File

@ -18,6 +18,7 @@ import com.cmvr.test.service.ITeTaskOrchestrationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.aima.mapper.AimaTaskMapper;
import com.cmvr.aima.mapper.AimaTaskTestCaseMapper;
@ -75,6 +76,39 @@ public class AimaTaskServiceImpl extends ServiceImpl<AimaTaskMapper, AimaTask> i
return aimaTaskMapper.insertAimaTask(aimaTask);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyAimaTask(String id) {
AimaTask source = aimaTaskMapper.selectAimaTaskById(id);
if (source == null) {
throw new IllegalArgumentException("爱玛任务不存在: " + id);
}
if (source.getTaskConfigId() == null || source.getTaskConfigId().isEmpty()) {
throw new IllegalStateException("爱玛任务未关联任务配置,无法完整复制: " + id);
}
String copiedConfigId = teTaskConfigInfoService.copyTeTaskConfigInfo(source.getTaskConfigId());
AimaTask copy = new AimaTask();
copy.setId(UUID.randomUUID().toString().replace("-", ""));
copy.setTaskCode(EntityCopyUtils.copyCode(source.getTaskCode(), 50));
copy.setTaskName(EntityCopyUtils.copyName(source.getTaskName(), 100));
copy.setTaskType(source.getTaskType());
copy.setPhoneId(source.getPhoneId());
copy.setVehicleId(source.getVehicleId());
copy.setTestCaseCount(0);
copy.setTaskConfigId(copiedConfigId);
copy.setRemark(source.getRemark());
copy.setCreateTime(DateUtils.getNowDate());
copy.setCreateBy(SecurityUtils.getUsername());
aimaTaskMapper.insertAimaTask(copy);
List<String> testCaseIds = aimaTaskTestCaseMapper.selectTestCasesByTaskId(id).stream()
.map(AimaTestCase::getId)
.collect(Collectors.toList());
bindTestCases(copy.getId(), testCaseIds);
return copy.getId();
}
@Override
public int updateAimaTask(AimaTask aimaTask) {
aimaTask.setUpdateTime(DateUtils.getNowDate());

View File

@ -9,6 +9,7 @@ import com.cmvr.test.service.ITeDetectionItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.aima.mapper.AimaTestCaseMapper;
import com.cmvr.aima.domain.AimaTestCase;
@ -87,6 +88,40 @@ public class AimaTestCaseServiceImpl extends ServiceImpl<AimaTestCaseMapper, Aim
return aimaTestCaseMapper.insertAimaTestCase(aimaTestCase);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyAimaTestCase(String id) {
AimaTestCase source = aimaTestCaseMapper.selectAimaTestCaseById(id);
if (source == null) {
throw new IllegalArgumentException("测试用例不存在: " + id);
}
if (source.getDetectItemId() == null || source.getDetectItemId().isEmpty()) {
throw new IllegalStateException("测试用例未关联检测项,无法完整复制: " + id);
}
String copiedDetectItemId = teDetectionItemService.copyTeDetectionItem(source.getDetectItemId());
AimaTestCase copy = AimaTestCase.builder()
.id(UUID.randomUUID().toString().replace("-", ""))
.caseCode(EntityCopyUtils.copyCode(source.getCaseCode(), 50))
.caseName(EntityCopyUtils.copyName(source.getCaseName(), 100))
.caseCategory(source.getCaseCategory())
.testDescription(source.getTestDescription())
.precondition(source.getPrecondition())
.testSteps(source.getTestSteps())
.expectedResult(source.getExpectedResult())
.priority(source.getPriority())
.enabled(source.getEnabled())
.sortOrder(source.getSortOrder())
.estimatedDuration(source.getEstimatedDuration())
.detectItemId(copiedDetectItemId)
.build();
copy.setRemark(source.getRemark());
copy.setCreateTime(DateUtils.getNowDate());
copy.setCreateBy(SecurityUtils.getUsername());
aimaTestCaseMapper.insertAimaTestCase(copy);
return copy.getId();
}
/**
* 修改爱玛测试用例
*

View File

@ -0,0 +1,33 @@
package com.cmvr.common.utils;
import java.util.UUID;
/**
* Helpers for generating user-facing names and unique codes for copied records.
*/
public final class EntityCopyUtils {
private static final String COPY_NAME_SUFFIX = " - 副本";
private EntityCopyUtils() {
}
public static String copyName(String sourceName, int maxLength) {
String baseName = StringUtils.isBlank(sourceName) ? "未命名" : sourceName;
int baseMaxLength = Math.max(0, maxLength - COPY_NAME_SUFFIX.length());
if (baseName.length() > baseMaxLength) {
baseName = baseName.substring(0, baseMaxLength);
}
return baseName + COPY_NAME_SUFFIX;
}
public static String copyCode(String sourceCode, int maxLength) {
String suffix = "_COPY_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
String baseCode = StringUtils.isBlank(sourceCode) ? "COPY" : sourceCode;
int baseMaxLength = Math.max(0, maxLength - suffix.length());
if (baseCode.length() > baseMaxLength) {
baseCode = baseCode.substring(0, baseMaxLength);
}
return baseCode + suffix;
}
}

View File

@ -38,6 +38,9 @@ public interface IInspectionTaskService extends IService<InspectionTask>
*/
int insertInspectionTask(InspectionTask inspectionTask);
/** Copy a task, its independent task configuration and ordered waypoint bindings. */
String copyInspectionTask(String id);
/**
* 修改巡检任务
*

View File

@ -38,6 +38,9 @@ public interface IInspectionWaypointService extends IService<InspectionWaypoint>
*/
int insertInspectionWaypoint(InspectionWaypoint inspectionWaypoint);
/** Copy a waypoint and its independent detection item. */
String copyInspectionWaypoint(String id);
/**
* 修改巡检点位
*

View File

@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.inspection.mapper.InspectionTaskMapper;
import com.cmvr.inspection.domain.InspectionTask;
@ -90,6 +91,39 @@ public class InspectionTaskServiceImpl extends ServiceImpl<InspectionTaskMapper,
return inspectionTaskMapper.insertInspectionTask(inspectionTask);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyInspectionTask(String id)
{
InspectionTask source = inspectionTaskMapper.selectInspectionTaskById(id);
if (source == null) {
throw new IllegalArgumentException("巡检任务不存在: " + id);
}
if (source.getTaskConfigId() == null || source.getTaskConfigId().isEmpty()) {
throw new IllegalStateException("巡检任务未关联任务配置,无法完整复制: " + id);
}
String copiedConfigId = teTaskConfigInfoService.copyTeTaskConfigInfo(source.getTaskConfigId());
InspectionTask copy = new InspectionTask();
copy.setId(UUID.randomUUID().toString().replace("-", ""));
copy.setTaskCode(EntityCopyUtils.copyCode(source.getTaskCode(), 64));
copy.setTaskName(EntityCopyUtils.copyName(source.getTaskName(), 100));
copy.setTaskType(source.getTaskType());
copy.setTaskConfigId(copiedConfigId);
copy.setMapId(source.getMapId());
copy.setWaypointCount(0);
copy.setRemark(source.getRemark());
copy.setCreateTime(DateUtils.getNowDate());
copy.setCreateBy(SecurityUtils.getUsername());
inspectionTaskMapper.insertInspectionTask(copy);
String[] waypointIds = inspectionTaskWaypointMapper.selectWaypointsByTaskId(id).stream()
.map(InspectionWaypoint::getId)
.toArray(String[]::new);
bindWaypoints(copy.getId(), waypointIds);
return copy.getId();
}
/**
* 修改巡检任务
*

View File

@ -10,6 +10,7 @@ import com.cmvr.test.service.ITeDetectionItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.inspection.mapper.InspectionWaypointMapper;
import com.cmvr.inspection.domain.InspectionWaypoint;
@ -82,6 +83,34 @@ public class InspectionWaypointServiceImpl extends ServiceImpl<InspectionWaypoin
return inspectionWaypointMapper.insertInspectionWaypoint(inspectionWaypoint);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyInspectionWaypoint(String id)
{
InspectionWaypoint source = inspectionWaypointMapper.selectInspectionWaypointById(id);
if (source == null) {
throw new IllegalArgumentException("巡检点位不存在: " + id);
}
if (source.getDetectItemId() == null || source.getDetectItemId().isEmpty()) {
throw new IllegalStateException("巡检点位未关联检测项,无法完整复制: " + id);
}
String copiedDetectItemId = teDetectionItemService.copyTeDetectionItem(source.getDetectItemId());
InspectionWaypoint copy = InspectionWaypoint.builder()
.id(UUID.randomUUID().toString().replace("-", ""))
.waypointName(EntityCopyUtils.copyName(source.getWaypointName(), 100))
.enabled(source.getEnabled())
.sortOrder(source.getSortOrder())
.detectItemId(copiedDetectItemId)
.mapId(source.getMapId())
.build();
copy.setRemark(source.getRemark());
copy.setCreateTime(DateUtils.getNowDate());
copy.setCreateBy(SecurityUtils.getUsername());
inspectionWaypointMapper.insertInspectionWaypoint(copy);
return copy.getId();
}
/**
* 修改巡检点位
*

View File

@ -37,6 +37,14 @@ public interface ITeDetectionItemService extends IService<TeDetectionItem> {
*/
public int insertTeDetectionItem(TeDetectionItem teDetectionItem);
/**
* Copy a detection item, including its flow and parameter configuration.
*
* @param id source detection item ID
* @return copied detection item ID
*/
String copyTeDetectionItem(String id);
/**
* 修改检测项配置
*

View File

@ -36,6 +36,14 @@ public interface ITeTaskConfigInfoService extends IService<TeTaskConfigInfo> {
*/
public int insertTeTaskConfigInfo(TeTaskConfigInfo teTaskConfigInfo);
/**
* Copy a task configuration and its ordered detection-item orchestration.
*
* @param id source task configuration ID
* @return copied task configuration ID
*/
String copyTeTaskConfigInfo(String id);
/**
* 修改任务流程编排
*

View File

@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
import com.cmvr.common.core.domain.entity.SysGroup;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.test.enums.NodeTypeEnum;
import com.cmvr.test.mapper.TeDetectionItemMapper;
import com.cmvr.test.model.domain.TeDetectionItem;
@ -83,6 +84,30 @@ public class TeDetectionItemServiceImpl extends ServiceImpl<TeDetectionItemMappe
return this.baseMapper.insert(teDetectionItem);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyTeDetectionItem(String id) {
TeDetectionItem source = this.baseMapper.selectById(id);
if (source == null) {
throw new IllegalArgumentException("检测项不存在: " + id);
}
TeDetectionItem copy = TeDetectionItem.builder()
.detectName(EntityCopyUtils.copyName(source.getDetectName(), 100))
.detectDesc(source.getDetectDesc())
.flowData(source.getFlowData())
.status(source.getStatus())
.isDeploy(source.getIsDeploy())
.detectVersion(source.getDetectVersion())
.groupId(source.getGroupId())
.config(source.getConfig())
.sceneCode(source.getSceneCode())
.build();
copy.setRemark(source.getRemark());
this.baseMapper.insert(copy);
return copy.getId();
}
/**
* 修改检测项配置
*

View File

@ -4,19 +4,23 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
import com.cmvr.common.core.domain.entity.SysGroup;
import com.cmvr.common.utils.EntityCopyUtils;
import com.cmvr.common.utils.StringUtils;
import com.cmvr.test.enums.RunModeEnum;
import com.cmvr.test.mapper.TeTaskConfigInfoMapper;
import com.cmvr.test.model.domain.TeTaskConfigInfo;
import com.cmvr.test.model.vo.TeTaskConfigInfoVO;
import com.cmvr.test.model.vo.TeTaskOrchestraVO;
import com.cmvr.test.service.ITeTaskConfigInfoService;
import com.cmvr.test.service.ITeTaskOrchestrationService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 任务流程编排Service业务层处理
@ -79,6 +83,37 @@ public class TeTaskConfigInfoServiceImpl extends ServiceImpl<TeTaskConfigInfoMap
return this.baseMapper.insert(teTaskConfigInfo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String copyTeTaskConfigInfo(String id) {
TeTaskConfigInfo source = this.baseMapper.selectById(id);
if (source == null) {
throw new IllegalArgumentException("任务配置不存在: " + id);
}
TeTaskConfigInfo copy = TeTaskConfigInfo.builder()
.taskName(EntityCopyUtils.copyName(source.getTaskName(), 100))
.taskDesc(source.getTaskDesc())
.status(source.getStatus())
.versionNo(source.getVersionNo())
.groupId(source.getGroupId())
.runMode(source.getRunMode())
.build();
copy.setRemark(source.getRemark());
this.baseMapper.insert(copy);
List<String> itemIds = taskOrchestrationService.queryByTaskId(id).stream()
.map(item -> item.getItemId())
.collect(Collectors.toList());
if (!itemIds.isEmpty()) {
taskOrchestrationService.insertTeTaskOrchestration(TeTaskOrchestraVO.builder()
.taskId(copy.getId())
.itemIds(itemIds)
.build());
}
return copy.getId();
}
/**
* 修改任务流程编排
*