feat(bpm): 添加流程映射配置系统

- 新增 BpmProcessMappingConfigDO 数据对象和相关 Mapper
- 实现流程映射配置的查询功能
- 添加日志记录和错误处理
-优化权限控制注解
This commit is contained in:
aikai 2025-07-02 09:27:18 +08:00
parent 8ee70f4944
commit b8339c9056
8 changed files with 308 additions and 3 deletions

View File

@ -46,7 +46,7 @@ public class BpmProcessMappingConfigController {
@PutMapping("/update")
@Operation(summary = "更新流程映射配置")
@PreAuthorize("@ss.hasPermission('bmp:process-mapping-config:update')")
@PreAuthorize("@ss.hasPermission('bpm:process-mapping-config:update')")
public CommonResult<Boolean> updateProcessMappingConfig(@Valid @RequestBody BpmProcessMappingConfigUpdateReqVO updateReqVO) {
processMappingConfigService.updateProcessMappingConfig(updateReqVO);
return success(true);

View File

@ -34,6 +34,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@ -59,6 +61,8 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
@Validated
public class BpmProcessInstanceController {
private static final Logger log = LoggerFactory.getLogger(BpmProcessInstanceController.class);
@Resource
private BpmProcessInstanceService processInstanceService;
@ -265,7 +269,9 @@ public class BpmProcessInstanceController {
private List<Class<?>> resolveEntityType(String typeName) {
List<Class<?>> entityClazzs = entityTypeMap.get(typeName);
if (entityClazzs == null) {
throw new ServiceException(500, "不支持的流程类型");
// 添加日志记录以便更好地调试
log.error("不支持的流程类型: {}", typeName);
throw new ServiceException(500, "不支持的流程类型: " + typeName);
}
return entityClazzs;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.processmapping;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 流程映射配置 DO
*
* @author 系统
*/
@TableName("bpm_process_mapping_config")
@KeySequence("bpm_process_mapping_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmProcessMappingConfigDO extends TenantBaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 流程唯一标识(:ORDER,PAYMENT)
*/
private String processCode;
/**
* 流程显示名称
*/
private String processName;
/**
* 物理表名(:order_info)
*/
private String tableName;
/**
* 实体类全限定名(:com.example.order.OrderDO)
*/
private String entityClass;
/**
* Mapper接口全限定名(:com.example.order.OrderMapper)
*/
private String mapperClass;
/**
* 流程描述
*/
private String description;
}

View File

@ -29,4 +29,4 @@ public interface BpmProcessMappingConfigMapper extends BaseMapperX<BpmProcessMap
return selectOne(BpmProcessMappingConfigDO::getProcessCode, processCode);
}
}
}

View File

@ -0,0 +1,191 @@
package cn.iocoder.yudao.module.bpm.framework.core.util;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 反射调用工具类
* 用于动态调用Mapper方法支持流程映射配置系统的动态查询功能
*
* @author 系统
*/
@Slf4j
public class ReflectionInvoker {
/**
* 动态调用Mapper方法
*
* @param mapperBean Spring容器中的Mapper实例
* @param methodName 方法名("selectById")
* @param paramType 参数类型
* @param args 参数值
* @return 方法执行结果
*/
public static Object invokeMapperMethod(Object mapperBean, String methodName, Class<?> paramType, Object... args) {
return invokeMapperMethod(mapperBean, methodName, new Class<?>[]{paramType}, args);
}
/**
* 动态调用Mapper方法
*
* @param mapperBean Spring容器中的Mapper实例
* @param methodName 方法名("selectById")
* @param paramTypes 参数类型数组
* @param args 参数值数组
* @return 方法执行结果
*/
public static Object invokeMapperMethod(Object mapperBean, String methodName, Class<?>[] paramTypes, Object... args) {
if (mapperBean == null) {
throw new IllegalArgumentException("mapperBean cannot be null");
}
if (methodName == null || methodName.trim().isEmpty()) {
throw new IllegalArgumentException("methodName cannot be null or empty");
}
try {
// 获取Mapper类
Class<?> mapperClass = mapperBean.getClass();
// 处理代理类的情况获取实际的接口类型
Class<?> targetClass = getTargetInterface(mapperClass);
log.debug("[invokeMapperMethod][开始调用] class={}, method={}, paramTypes={}, args={}",
targetClass.getSimpleName(), methodName, Arrays.toString(paramTypes), Arrays.toString(args));
// 查找匹配的方法
Method method = findMatchingMethod(targetClass, methodName, paramTypes);
if (method == null) {
throw new NoSuchMethodException(String.format("Method '%s' with parameter types %s not found in class %s",
methodName, Arrays.toString(paramTypes), targetClass.getName()));
}
// 设置方法可访问
method.setAccessible(true);
// 调用方法
Object result = method.invoke(mapperBean, args);
log.debug("[invokeMapperMethod][调用成功] class={}, method={}, result={}",
targetClass.getSimpleName(), methodName, result);
return result;
} catch (Exception e) {
String errorMsg = String.format("Failed to invoke method '%s' on class '%s'",
methodName, mapperBean.getClass().getName());
log.error("[invokeMapperMethod][调用失败] {}, paramTypes={}, args={}",
errorMsg, Arrays.toString(paramTypes), Arrays.toString(args), e);
throw new RuntimeException(errorMsg, e);
}
}
/**
* 获取目标接口类型
* 处理Spring代理类的情况
*/
private static Class<?> getTargetInterface(Class<?> clazz) {
// 如果是代理类尝试获取实际的接口
if (clazz.getName().contains("$Proxy") || clazz.getName().contains("CGLIB")) {
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length > 0) {
// 选择第一个看起来像Mapper的接口
for (Class<?> intf : interfaces) {
if (intf.getName().contains("Mapper")) {
return intf;
}
}
return interfaces[0];
}
}
return clazz;
}
/**
* 查找匹配的方法
* 支持精确匹配和兼容性匹配
*/
private static Method findMatchingMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes) {
Method[] methods = clazz.getMethods();
// 先尝试精确匹配
for (Method method : methods) {
if (method.getName().equals(methodName) &&
Arrays.equals(method.getParameterTypes(), paramTypes)) {
return method;
}
}
// 再尝试兼容性匹配参数类型可赋值
for (Method method : methods) {
if (method.getName().equals(methodName) &&
isParameterTypesAssignable(method.getParameterTypes(), paramTypes)) {
return method;
}
}
// 最后尝试参数数量匹配用于处理泛型擦除的情况
for (Method method : methods) {
if (method.getName().equals(methodName) &&
method.getParameterCount() == paramTypes.length) {
log.warn("[findMatchingMethod][使用参数数量匹配] method={}, expected={}, actual={}",
method, Arrays.toString(paramTypes), Arrays.toString(method.getParameterTypes()));
return method;
}
}
return null;
}
/**
* 检查参数类型是否可赋值
*/
private static boolean isParameterTypesAssignable(Class<?>[] methodParams, Class<?>[] providedParams) {
if (methodParams.length != providedParams.length) {
return false;
}
for (int i = 0; i < methodParams.length; i++) {
if (!isAssignable(methodParams[i], providedParams[i])) {
return false;
}
}
return true;
}
/**
* 检查类型是否可赋值包含基本类型和包装类型的转换
*/
private static boolean isAssignable(Class<?> target, Class<?> source) {
if (target.isAssignableFrom(source)) {
return true;
}
// 处理基本类型和包装类型
if (target.isPrimitive() || source.isPrimitive()) {
return isPrimitiveAssignable(target, source);
}
return false;
}
/**
* 检查基本类型是否可赋值
*/
private static boolean isPrimitiveAssignable(Class<?> target, Class<?> source) {
if (target == source) return true;
// 常见的基本类型转换
if ((target == int.class && source == Integer.class) ||
(target == Integer.class && source == int.class) ||
(target == long.class && source == Long.class) ||
(target == Long.class && source == long.class) ||
(target == boolean.class && source == Boolean.class) ||
(target == Boolean.class && source == boolean.class)) {
return true;
}
return false;
}
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.bpm.service.processmapping;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.io.Serializable;
import java.util.List;
/**
* 流程动态查询 Service 接口
@ -17,4 +20,13 @@ public interface ProcessQueryService {
* @return 对应的实体对象
*/
Object queryById(String processCode, Serializable id);
/**
* 根据流程代码和条件查询列表
*
* @param processCode 流程标识
* @param queryWrapper 查询条件
* @return 实体列表
*/
List<?> queryList(String processCode, QueryWrapper<?> queryWrapper);
}

View File

@ -8,9 +8,11 @@ import cn.iocoder.yudao.module.bpm.framework.core.util.ReflectionInvoker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_MAPPING_CONFIG_NOT_EXISTS;
@ -40,6 +42,18 @@ public class ProcessQueryServiceImpl implements ProcessQueryService {
return ReflectionInvoker.invokeMapperMethod(mapperBean, "selectById", Serializable.class, id);
}
@Override
public List<?> queryList(String processCode, QueryWrapper<?> queryWrapper) {
// 1. 获取流程配置
BpmProcessMappingConfigDO config = getProcessMappingConfig(processCode);
// 2. 动态获取Mapper实例
Object mapperBean = getMapperBean(config.getMapperClass());
// 3. 调用selectList方法
return (List<?>) ReflectionInvoker.invokeMapperMethod(mapperBean, "selectList", QueryWrapper.class, queryWrapper);
}
/**
* 获取流程映射配置
*/

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.bpm.dal.mysql.processmapping.BpmProcessMappingConfigMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.iocoder.yudao.module.bpm.dal.dataobject.processmapping.BpmProcessMappingConfigDO">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="process_code" property="processCode" jdbcType="VARCHAR"/>
<result column="process_name" property="processName" jdbcType="VARCHAR"/>
<result column="table_name" property="tableName" jdbcType="VARCHAR"/>
<result column="entity_class" property="entityClass" jdbcType="VARCHAR"/>
<result column="mapper_class" property="mapperClass" jdbcType="VARCHAR"/>
<result column="description" property="description" jdbcType="VARCHAR"/>
<result column="creator" property="creator" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="updater" property="updater" jdbcType="VARCHAR"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BIT"/>
<result column="tenant_id" property="tenantId" jdbcType="BIGINT"/>
</resultMap>
</mapper>