refactor(flow): 重构循环处理逻辑并完善自动填充功能

- 在BaseEntity中添加MyBatis Plus字段自动填充注解
- 实现FlowLoopValueResolver工具类统一处理循环数据源
- 简化FlowGetCurrentObjNodeHandler中的循环对象获取逻辑
- 优化MyMetaObjectHandler中的时间字段填充方法
- 在多个服务实现中添加实体对象参数确保更新操作正确性
- 为SysFileInfo启用创建时间和创建者的自动填充功能
- 添加FlowLoopValueResolver的单元测试确保功能正确性
This commit is contained in:
lixiaolong 2026-07-30 08:51:19 +08:00
parent bdb6640ad8
commit e62f445013
12 changed files with 189 additions and 87 deletions

View File

@ -4,6 +4,8 @@ import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -24,16 +26,20 @@ public class BaseEntity implements Serializable
private String searchValue;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;

View File

@ -1,6 +1,8 @@
package com.cmvr.common.core.domain.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -32,11 +34,11 @@ public class SysFileInfo implements Serializable {
private Integer fileType;
@ApiModelProperty("上传人")
// @TableField(fill = FieldFill.INSERT) // todo 暂时不要 没有token
@TableField(fill = FieldFill.INSERT)
private String createBy;
@ApiModelProperty("上传时间")
// @TableField(fill = FieldFill.INSERT)
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@ApiModelProperty("文件MD5值")

View File

@ -33,9 +33,9 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
}
// 起始版本 3.3.0(推荐使用)
this.setFieldValByName(CREATE_BY, username, metaObject);
this.setFieldValByName(CREATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
this.setFieldValByName(CREATE_TIME, currentTime(metaObject, CREATE_TIME), metaObject);
this.setFieldValByName(UPDATE_BY, username, metaObject);
this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
this.setFieldValByName(UPDATE_TIME, currentTime(metaObject, UPDATE_TIME), metaObject);
this.setFieldValByName(DELETED, "0", metaObject);
// this.setFieldValByName(STATUS, "1", metaObject);
if (getFieldValByName(STATUS, metaObject) == null) {
@ -56,9 +56,14 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
username = "anonymous"; // 匿名接口默认值
}
this.setFieldValByName(UPDATE_BY, username, metaObject);
this.setFieldValByName(UPDATE_TIME, formatDate(metaObject.getSetterType(CREATE_TIME)), metaObject);
this.setFieldValByName(UPDATE_TIME, currentTime(metaObject, UPDATE_TIME), metaObject);
}
private Object currentTime(MetaObject metaObject, String fieldName) {
return metaObject.hasSetter(fieldName)
? formatDate(metaObject.getSetterType(fieldName))
: null;
}
/**
* 处理特殊日期

View File

@ -47,5 +47,12 @@
<artifactId>nashorn-core</artifactId>
<version>${nashorn.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,16 +1,12 @@
package com.cmvr.test.flow.runtime.dispatcher;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.test.flow.runtime.engine.support.FlowLoopValueResolver;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Slf4j
@Component("GET_CURRENT_OBJECT")
public class FlowGetCurrentObjNodeHandler implements FlowNodeTypeHandler {
@ -19,18 +15,11 @@ public class FlowGetCurrentObjNodeHandler implements FlowNodeTypeHandler {
@Override
public TaskNodeExecuteResult handle(TaskNodeExecuteMessage message) {
try {
// JSONObject inputParams = message.getInputParams();
// JSONArray jsonArray = inputParams.getJSONArray("array");
//
// List<Integer> iterations = message.getIterations();
// int last = CollUtil.getLast(iterations) - 1;
int index = message.getLoopNum() - 1;
int iteration = message.getLoopNum();
int index = iteration - 1;
JSONObject output = new JSONObject();
output.put("index", index);
JSONArray objects = (JSONArray)message.getLoopArray();
if (CollUtil.isNotEmpty(objects) && index >= 0) {
output.put("object", objects.get(index));
}
output.put("object", FlowLoopValueResolver.current(message.getLoopArray(), iteration));
return TaskNodeExecuteResult.success(output);

View File

@ -0,0 +1,91 @@
package com.cmvr.test.flow.runtime.engine.support;
import com.alibaba.fastjson2.JSON;
import com.cmvr.common.exception.GlobalException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
/** Resolves the two supported loop sources: arrays and iteration counts. */
public final class FlowLoopValueResolver {
private FlowLoopValueResolver() {
}
public static int count(Object value) {
Object normalized = normalize(value);
if (normalized == null) {
return 0;
}
if (normalized instanceof Collection<?>) {
return ((Collection<?>) normalized).size();
}
if (normalized != null && normalized.getClass().isArray()) {
return Array.getLength(normalized);
}
if (normalized instanceof Number) {
return ((Number) normalized).intValue();
}
throw unsupported(value);
}
public static Object current(Object value, int iteration) {
if (iteration < 1) {
throw new GlobalException("当前循环次数必须从 1 开始: " + iteration);
}
Object normalized = normalize(value);
int index = iteration - 1;
if (normalized instanceof Collection<?>) {
Collection<?> collection = (Collection<?>) normalized;
if (index >= collection.size()) {
throw outOfBounds(iteration, collection.size());
}
return new ArrayList<>(collection).get(index);
}
if (normalized != null && normalized.getClass().isArray()) {
int length = Array.getLength(normalized);
if (index >= length) {
throw outOfBounds(iteration, length);
}
return Array.get(normalized, index);
}
if (normalized instanceof Number) {
int loopCount = ((Number) normalized).intValue();
if (iteration > loopCount) {
throw outOfBounds(iteration, loopCount);
}
return iteration;
}
throw unsupported(value);
}
private static Object normalize(Object value) {
if (!(value instanceof String)) {
return value;
}
String text = ((String) value).trim();
if (text.startsWith("[")) {
try {
return JSON.parseArray(text);
} catch (Exception e) {
throw new GlobalException("循环数组参数格式错误: " + value);
}
}
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw unsupported(value);
}
}
private static GlobalException unsupported(Object value) {
return new GlobalException("循环参数仅支持数组或数字: " + value);
}
private static GlobalException outOfBounds(int iteration, int count) {
return new GlobalException("当前循环次数超出范围: " + iteration + "/" + count);
}
}

View File

@ -2,7 +2,6 @@ package com.cmvr.test.flow.runtime.engine.support;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException;
@ -17,8 +16,6 @@ import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.List;
@Component
@ -51,32 +48,7 @@ public class FlowNodeParamPreparer {
if (node.getNodeType() == NodeTypeEnum.LOOP) {
Object loopNumVal = input.get("loopNum");
input.put("loopArray", loopNumVal);
int loopCount = 0;
// 根据迭代路径找到当前层的集合对象
// Object target = resolveLoopTarget(loopNumVal, rootMessage.getIterations(), 0);
Object target = loopNumVal;
if (target instanceof JSONArray) {
loopCount = ((JSONArray) target).size();
} else if (target instanceof Collection) {
loopCount = ((Collection<?>) target).size();
} else if (target != null && target.getClass().isArray()) {
loopCount = Array.getLength(target);
} else if (target instanceof Number) {
loopCount = ((Number) target).intValue();
} else if (target != null) {
try {
String string = target.toString();
if (string.startsWith("[")) {
JSONArray array = JSON.parseArray(string);
loopCount = array.size();
}else {
loopCount = Integer.parseInt(string);
}
} catch (NumberFormatException e) {
throw new GlobalException("loopNum 参数格式错误: " + loopNumVal);
}
}
int loopCount = FlowLoopValueResolver.count(loopNumVal);
// 写回 inputParams LoopHandler 使用
input.put("loopNum", loopCount);
@ -177,40 +149,4 @@ public class FlowNodeParamPreparer {
return curr;
}
/**
* 根据迭代路径iterations逐层解析嵌套集合
* - iterations = [] 返回最外层对象
* - iterations = [2] 返回 list[1]
* - iterations = [2,3] 返回 list[1][2]
*/
private Object resolveLoopTarget(Object obj, List<Integer> iterations, int depth) {
if (obj == null) return null;
// 已经走到当前循环层返回目标
if (depth >= iterations.size()) {
return obj;
}
int idx = iterations.get(depth) - 1;
if (obj instanceof List) {
List<?> list = (List<?>) obj;
if (idx >= 0 && idx < list.size()) {
return resolveLoopTarget(list.get(idx), iterations, depth + 1);
}
} else if (obj instanceof JSONArray) {
JSONArray arr = (JSONArray) obj;
if (idx >= 0 && idx < arr.size()) {
return resolveLoopTarget(arr.get(idx), iterations, depth + 1);
}
} else if (obj != null && obj.getClass().isArray()) {
int length = Array.getLength(obj);
if (idx >= 0 && idx < length) {
return resolveLoopTarget(Array.get(obj, idx), iterations, depth + 1);
}
}
// 如果不是集合类型直接返回
return obj;
}
}

View File

@ -59,6 +59,7 @@ public class TeTaskInstServiceImpl extends ServiceImpl<TeTaskInstMapper, TeTaskI
@Override
public boolean updateStatus(String instId, TaskStatusEnum taskStatusEnum) {
return this.update(
new TeTaskInst(),
new LambdaUpdateWrapper<TeTaskInst>()
.eq(TeTaskInst::getId, instId)
.set(TeTaskInst::getStatus, taskStatusEnum.getCode())

View File

@ -0,0 +1,64 @@
package com.cmvr.test.flow.runtime.engine.support;
import com.alibaba.fastjson2.JSON;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.test.flow.runtime.dispatcher.FlowGetCurrentObjNodeHandler;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
public class FlowLoopValueResolverTest {
@Test
public void resolvesCurrentArrayElement() {
assertEquals(3, FlowLoopValueResolver.count(JSON.parseArray("[\"a\",\"b\",\"c\"]")));
assertEquals("b", FlowLoopValueResolver.current(Arrays.asList("a", "b", "c"), 2));
assertEquals(20, FlowLoopValueResolver.current(new Integer[]{10, 20, 30}, 2));
assertEquals("b", FlowLoopValueResolver.current("[\"a\",\"b\",\"c\"]", 2));
}
@Test
public void resolvesCurrentNumericIteration() {
assertEquals(3, FlowLoopValueResolver.count(3));
assertEquals(2, FlowLoopValueResolver.current(3, 2));
assertEquals(3, FlowLoopValueResolver.count("3"));
assertEquals(2, FlowLoopValueResolver.current("3", 2));
}
@Test
public void currentObjectNodeKeepsOutputContractForArrayLoop() {
TaskNodeExecuteResult result = executeCurrentObjectNode(Arrays.asList("a", "b", "c"), 2);
assertEquals(1, result.getOutputParams().getIntValue("index"));
assertEquals("b", result.getOutputParams().get("object"));
}
@Test
public void currentObjectNodeReturnsIterationForNumericLoop() {
TaskNodeExecuteResult result = executeCurrentObjectNode(3, 2);
assertEquals(1, result.getOutputParams().getIntValue("index"));
assertEquals(2, result.getOutputParams().getIntValue("object"));
}
@Test(expected = GlobalException.class)
public void rejectsUnsupportedLoopValue() {
FlowLoopValueResolver.count("not-a-loop");
}
@Test(expected = GlobalException.class)
public void rejectsIterationOutsideLoopRange() {
FlowLoopValueResolver.current(Arrays.asList("a"), 2);
}
private TaskNodeExecuteResult executeCurrentObjectNode(Object loopValue, int iteration) {
TaskNodeExecuteMessage message = new TaskNodeExecuteMessage();
message.setLoopArray(loopValue);
message.setLoopNum(iteration);
return new FlowGetCurrentObjNodeHandler().handle(message);
}
}

View File

@ -163,7 +163,7 @@ public class TiProjectServiceImpl extends ServiceImpl<TiProjectMapper, TiProject
LambdaUpdateWrapper<TiProject> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(TiProject::getProjectId, projectId)
.set(TiProject::getStatus, status);
return this.update(null, wrapper);
return this.update(new TiProject(), wrapper);
}
// 功能ID -> 音频地址

View File

@ -27,6 +27,6 @@ public class ExViProjectServiceImpl implements ExViProjectService {
wrapper.eq(ViProject::getProjectId, id)
.eq(ViProject::getStatus, 1) // 运行中
.set(ViProject::getStatus, 2); // 完成
return viProjectService.update(null, wrapper);
return viProjectService.update(new ViProject(), wrapper);
}
}

View File

@ -138,6 +138,7 @@ public class ViProjectServiceImpl extends ServiceImpl<ViProjectMapper, ViProject
normalVO.setTerminalId(taskExecuteProjectVO.getTerminalId());
String instId = flowTaskRuntimeService.executeProjectTask(normalVO);
this.update(
new ViProject(),
new LambdaUpdateWrapper<ViProject>()
.eq(ViProject::getProjectId, taskExecuteProjectVO.getProjectId())
.set(ViProject::getStatus, "1") //执行中