commit:同步2.2版本

This commit is contained in:
Jerry
2022-01-24 07:38:45 +08:00
parent 35ac62e4d2
commit aa0ea778da
123 changed files with 5451 additions and 887 deletions

View File

@@ -18,22 +18,16 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
@@ -42,6 +36,7 @@
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@@ -1,5 +1,6 @@
package com.orangeforms.common.core.advice;
import com.orangeforms.common.core.util.MyDateUtil;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
@@ -25,6 +26,6 @@ public class MyControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), false));
new CustomDateEditor(new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT), false));
}
}

View File

@@ -24,4 +24,12 @@ public @interface EnableDataPerm {
* @return 被排序的方法名称数据。
*/
String[] excluseMethodName() default {};
/**
* 必须包含能看用户自己数据的数据过滤条件如果当前用户的数据过滤中没有DataPermRuleType.TYPE_USER_ONLY
* 在进行数据权限过滤时,会自动包含该权限。
*
* @return 是否必须包含DataPermRuleType.TYPE_USER_ONLY类型的数据权限。
*/
boolean mustIncludeUserRule() default false;
}

View File

@@ -1,6 +1,7 @@
package com.orangeforms.common.core.base.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -45,6 +46,7 @@ import static java.util.stream.Collectors.*;
*/
@Slf4j
public abstract class BaseService<M, K extends Serializable> extends ServiceImpl<BaseDaoMapper<M>, M> implements IBaseService<M, K> {
/**
* 当前Service关联的主Model实体对象的Class。
*/
@@ -435,6 +437,53 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
return mapper().selectCount(new QueryWrapper<M>().in(column, inFilterValues)) == inFilterValues.size();
}
@Override
public <R> List<R> notExist(String filterField, Set<R> filterSet, boolean findFirst) {
List<R> notExistIdList = new LinkedList<>();
String columnName = this.safeMapToColumnName(filterField);
int start = 0;
int count = 1000;
if (filterSet.size() > count) {
outloop:
do {
int end = Math.min(filterSet.size(), start + count);
List<R> subFilterList = CollUtil.sub(filterSet, start, end);
QueryWrapper<M> queryWrapper = new QueryWrapper<>();
queryWrapper.in(columnName, subFilterList);
queryWrapper.select(columnName);
Set<Object> existIdSet = mapper().selectList(queryWrapper).stream()
.map(c -> ReflectUtil.getFieldValue(c, filterField)).collect(toSet());
for (R filterData : subFilterList) {
if (!existIdSet.contains(filterData)) {
notExistIdList.add(filterData);
if (findFirst) {
break outloop;
}
}
}
if (end == filterSet.size()) {
break;
}
start += count;
} while (true);
} else {
QueryWrapper<M> queryWrapper = new QueryWrapper<>();
queryWrapper.in(columnName, filterSet);
queryWrapper.select(columnName);
Set<Object> existIdSet = mapper().selectList(queryWrapper).stream()
.map(c -> ReflectUtil.getFieldValue(c, filterField)).collect(toSet());
for (R filterData : filterSet) {
if (!existIdSet.contains(filterData)) {
notExistIdList.add(filterData);
if (findFirst) {
break;
}
}
}
}
return notExistIdList;
}
/**
* 返回符合主键 in (idValues) 条件的所有数据。
*
@@ -637,6 +686,23 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
return CallResult.ok();
}
@SuppressWarnings("unchecked")
@Override
public CallResult verifyRelatedData(M data) {
if (data == null) {
return CallResult.ok();
}
Object id = ReflectUtil.getFieldValue(data, idFieldName);
if (id == null) {
return this.verifyRelatedData(data, null);
}
M originalData = this.getById((K) id);
if (originalData == null) {
return CallResult.error("数据验证失败,源数据不存在!");
}
return this.verifyRelatedData(data, originalData);
}
@SuppressWarnings("unchecked")
@Override
public CallResult verifyRelatedData(List<M> dataList) {
@@ -673,6 +739,130 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
return CallResult.ok();
}
@Override
public <R> CallResult verifyImportForConstDict(List<M> dataList, String fieldName, Function<M, R> idGetter) {
if (CollUtil.isEmpty(dataList)) {
return CallResult.ok();
}
// 这里均为内部调用方法因此出现任何错误均为代码BUG所以我们会及时抛出异常。
Field field = ReflectUtil.getField(modelClass, fieldName);
if (field == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't exist.");
}
RelationConstDict relationConstDict = field.getAnnotation(RelationConstDict.class);
if (relationConstDict == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't have RelationConstDict.");
}
Method m = ReflectUtil.getMethodByName(relationConstDict.constantDictClass(), "isValid");
for (M data : dataList) {
R id = idGetter.apply(data);
if (id != null) {
boolean ok = ReflectUtil.invokeStatic(m, id);
if (!ok) {
String errorMessage = String.format("数据验证失败,字段 [%s] 存在无效的常量字典值 [%s]",
relationConstDict.masterIdField(), id);
return CallResult.error(errorMessage, data);
}
}
}
return CallResult.ok();
}
@Override
public <R> CallResult verifyImportForDict(List<M> dataList, String fieldName, Function<M, R> idGetter) {
if (CollUtil.isEmpty(dataList)) {
return CallResult.ok();
}
// 这里均为内部调用方法因此出现任何错误均为代码BUG所以我们会及时抛出异常。
Field field = ReflectUtil.getField(modelClass, fieldName);
if (field == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't exist.");
}
RelationDict relationDict = field.getAnnotation(RelationDict.class);
if (relationDict == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't have RelationDict.");
}
BaseService<Object, Serializable> service = ApplicationContextHolder.getBean(
StringUtils.uncapitalize(relationDict.slaveServiceName()));
Set<Object> dictIdSet = service.getAllList().stream()
.map(c -> ReflectUtil.getFieldValue(c, relationDict.slaveIdField())).collect(toSet());
for (M data : dataList) {
R id = idGetter.apply(data);
if (id != null && !dictIdSet.contains(id)) {
String errorMessage = String.format("数据验证失败,字段 [%s] 存在无效的字典表字典值 [%s]",
relationDict.masterIdField(), id);
return CallResult.error(errorMessage, data);
}
}
return CallResult.ok();
}
@Override
public <R> CallResult verifyImportForDatasourceDict(List<M> dataList, String fieldName, Function<M, R> idGetter) {
if (CollUtil.isEmpty(dataList)) {
return CallResult.ok();
}
// 这里均为内部调用方法因此出现任何错误均为代码BUG所以我们会及时抛出异常。
Field field = ReflectUtil.getField(modelClass, fieldName);
if (field == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't exist.");
}
RelationDict relationDict = field.getAnnotation(RelationDict.class);
if (relationDict == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't have RelationDict.");
}
// 验证数据源字典Id由于被依赖的数据表可能包含大量业务数据因此还是分批做存在性比对更为高效。
Set<R> idSet = dataList.stream()
.filter(c -> idGetter.apply(c) != null).map(idGetter).collect(toSet());
if (CollUtil.isNotEmpty(idSet)) {
BaseService<Object, Serializable> slaveService = ApplicationContextHolder.getBean(
StringUtils.uncapitalize(relationDict.slaveServiceName()));
List<R> notExistIdList = slaveService.notExist(relationDict.slaveIdField(), idSet, true);
if (CollUtil.isNotEmpty(notExistIdList)) {
R notExistId = notExistIdList.get(0);
String errorMessage = String.format("数据验证失败,字段 [%s] 存在无效的数据源表字典值 [%s]",
relationDict.masterIdField(), notExistId);
M data = dataList.stream()
.filter(c -> ObjectUtil.equals(idGetter.apply(c), notExistId)).findFirst().orElse(null);
return CallResult.error(errorMessage, data);
}
}
return CallResult.ok();
}
@Override
public <R> CallResult verifyImportForOneToOneRelation(List<M> dataList, String fieldName, Function<M, R> idGetter) {
if (CollUtil.isEmpty(dataList)) {
return CallResult.ok();
}
// 这里均为内部调用方法因此出现任何错误均为代码BUG所以我们会及时抛出异常。
Field field = ReflectUtil.getField(modelClass, fieldName);
if (field == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't exist.");
}
RelationOneToOne relationOneToOne = field.getAnnotation(RelationOneToOne.class);
if (relationOneToOne == null) {
throw new MyRuntimeException("FieldName [" + fieldName + "] doesn't have RelationOneToOne.");
}
// 验证一对一关联Id由于被依赖的数据表可能包含大量业务数据因此还是分批做存在性比对更为高效。
Set<R> idSet = dataList.stream()
.filter(c -> idGetter.apply(c) != null).map(idGetter).collect(toSet());
if (CollUtil.isNotEmpty(idSet)) {
BaseService<Object, Serializable> slaveService = ApplicationContextHolder.getBean(
StringUtils.uncapitalize(relationOneToOne.slaveServiceName()));
List<R> notExistIdList = slaveService.notExist(relationOneToOne.slaveIdField(), idSet, true);
if (CollUtil.isNotEmpty(notExistIdList)) {
R notExistId = notExistIdList.get(0);
String errorMessage = String.format("数据验证失败,字段 [%s] 存在无效的一对一关联值 [%s]",
relationOneToOne.masterIdField(), notExistId);
M data = dataList.stream()
.filter(c -> ObjectUtil.equals(idGetter.apply(c), notExistId)).findFirst().orElse(null);
return CallResult.error(errorMessage, data);
}
}
return CallResult.ok();
}
/**
* 集成所有与主表实体对象相关的关联数据列表。包括本地和远程服务的一对一、字典、一对多和多对多聚合运算等。
* 也可以根据实际需求,单独调用该函数所包含的各个数据集成函数。
@@ -1585,6 +1775,7 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
RelationConstDict relationConstDict = f.getAnnotation(RelationConstDict.class);
if (relationConstDict != null) {
RelationStruct relationStruct = new RelationStruct();
relationStruct.relationConstDict = relationConstDict;
relationStruct.relationField = f;
relationStruct.masterIdField = ReflectUtil.getField(modelClass, relationConstDict.masterIdField());
Field dictMapField = ReflectUtil.getField(relationConstDict.constantDictClass(), "DICT_MAP");
@@ -1806,6 +1997,7 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
private BaseService<Object, Serializable> service;
private BaseDaoMapper<Object> manyToManyMapper;
private Map<Object, String> dictMap;
private RelationConstDict relationConstDict;
private RelationDict relationDict;
private RelationOneToOne relationOneToOne;
private RelationOneToMany relationOneToMany;

View File

@@ -9,6 +9,7 @@ import java.io.Serializable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 所有Service的接口。
@@ -137,6 +138,17 @@ public interface IBaseService<M, K extends Serializable> extends IService<M>{
*/
<T> boolean existUniqueKeyList(String inFilterField, Set<T> inFilterValues);
/**
* 根据过滤字段和过滤集合,返回不存在的数据。
*
* @param filterField 过滤的Java字段。
* @param filterSet 过滤字段数据集合。
* @param findFirst 是否找到第一个就返回。
* @param <R> 过滤字段类型。
* @return filterSet中在从表中不存在的数据集合。
*/
<R> List<R> notExist(String filterField, Set<R> filterSet, boolean findFirst);
/**
* 返回符合主键 in (idValues) 条件的所有数据。
*
@@ -269,15 +281,68 @@ public interface IBaseService<M, K extends Serializable> extends IService<M>{
*/
CallResult verifyRelatedData(M data, M originalData);
/**
* 根据最新对象和原有对象的数据对比,判断关联的字典数据和多对一主表数据是否都是合法数据。
* 如果data对象中包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对。
*
* @param data 数据对象。
* @return 应答结果对象。
*/
CallResult verifyRelatedData(M data);
/**
* 根据最新对象列表和原有对象列表的数据对比,判断关联的字典数据和多对一主表数据是否都是合法数据。
* NOTE: BaseService中会给出返回CallResult.ok()的缺省实现。每个业务服务实现类在需要的时候可以重载该方法
* 如果dataList列表中的对象包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对
*
* @param dataList 数据对象列表。
* @return 应答结果对象。
*/
CallResult verifyRelatedData(List<M> dataList);
/**
* 批量导入数据列表,对依赖常量字典的数据进行验证。
*
* @param dataList 批量导入数据列表。
* @param fieldName 业务主表中依赖常量字典的字段名。
* @param idGetter 获取业务主表中依赖常量字典字段值的Function对象。
* @param <R> 业务主表中依赖常量字典的字段类型。
* @return 验证结果如果失败在data中包含具体的错误对象。
*/
<R> CallResult verifyImportForConstDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表,对依赖字典表字典的数据进行验证。
*
* @param dataList 批量导入数据列表。
* @param fieldName 业务主表中依赖字典表字典的字段名。
* @param idGetter 获取业务主表中依赖字典表字典字段值的Function对象。
* @param <R> 业务主表中依赖字典表字典的字段类型。
* @return 验证结果如果失败在data中包含具体的错误对象。
*/
<R> CallResult verifyImportForDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表,对依赖数据源字典的数据进行验证。
*
* @param dataList 批量导入数据列表。
* @param fieldName 业务主表中依赖数据源字典的字段名。
* @param idGetter 获取业务主表中依赖数据源字典字段值的Function对象。
* @param <R> 业务主表中依赖数据源字典的字段类型。
* @return 验证结果如果失败在data中包含具体的错误对象。
*/
<R> CallResult verifyImportForDatasourceDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表,对存在一对一关联的数据进行验证。
*
* @param dataList 批量导入数据列表。
* @param fieldName 业务主表中存在一对一关联的字段名。
* @param idGetter 获取业务主表中一对一关联字段值的Function对象。
* @param <R> 业务主表中存在一对一关联的字段类型。
* @return 验证结果如果失败在data中包含具体的错误对象。
*/
<R> CallResult verifyImportForOneToOneRelation(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 集成所有与主表实体对象相关的关联数据列表。包括一对一、字典、一对多和多对多聚合运算等。
* 也可以根据实际需求,单独调用该函数所包含的各个数据集成函数。

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.orangeforms.common.core.interceptor.MyRequestArgumentResolver;
import com.orangeforms.common.core.util.MyDateUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
@@ -44,8 +45,8 @@ public class CommonWebMvcConfig implements WebMvcConfigurer {
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
@@ -54,7 +55,7 @@ public class CommonWebMvcConfig implements WebMvcConfigurer {
SerializerFeature.PrettyFormat,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.IgnoreNonFieldGetter);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConfig.setDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}

View File

@@ -0,0 +1,43 @@
package com.orangeforms.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* common-core的配置属性类。
*
* @author Jerry
* @date 2021-06-06
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "common-core")
public class CoreProperties {
public static final String MYSQL_TYPE = "mysql";
public static final String POSTGRESQL_TYPE = "postgresql";
/**
* 数据库类型。
*/
private String databaseType = MYSQL_TYPE;
/**
* 是否为MySQL。
*
* @return 是返回true否则false。
*/
public boolean isMySql() {
return this.databaseType.equals(MYSQL_TYPE);
}
/**
* 是否为PostgreSQl。
*
* @return 是返回true否则false。
*/
public boolean isPostgresql() {
return this.databaseType.equals(POSTGRESQL_TYPE);
}
}

View File

@@ -84,4 +84,21 @@ public class CallResult {
result.errorMessage = errorMessage;
return result;
}
/**
* 创建表示验证失败的对象实例。
*
* @param errorMessage 错误描述。
* @param data 附带的数据对象。
* @return 验证失败对象实例。
*/
public static <T> CallResult error(String errorMessage, T data) {
CallResult result = new CallResult();
result.success = false;
result.errorMessage = errorMessage;
JSONObject jsonObject = new JSONObject();
jsonObject.put("errorData", data);
result.data = jsonObject;
return result;
}
}

View File

@@ -1,15 +1,17 @@
package com.orangeforms.common.core.object;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.orangeforms.common.core.config.CoreProperties;
import com.orangeforms.common.core.constant.ApplicationConstant;
import com.orangeforms.common.core.exception.InvalidClassFieldException;
import com.orangeforms.common.core.exception.InvalidDataFieldException;
import com.orangeforms.common.core.exception.InvalidDataModelException;
import com.orangeforms.common.core.util.ApplicationContextHolder;
import com.orangeforms.common.core.util.MyModelUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -27,6 +29,9 @@ import java.util.List;
@Data
public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
private final CoreProperties coreProperties =
ApplicationContextHolder.getBean(CoreProperties.class);
/**
* SQL语句的SELECT LIST中分组字段的返回字段名称列表。
*/
@@ -54,15 +59,15 @@ public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
StringBuilder groupSelectBuilder = new StringBuilder(128);
int i = 0;
for (GroupInfo groupInfo : groupParam) {
GroupBaseData groupBaseData = parseGroupBaseData(groupInfo, modelClazz);
if (StringUtils.isBlank(groupBaseData.tableName)) {
GroupBaseData groupBaseData = groupParam.parseGroupBaseData(groupInfo, modelClazz);
if (StrUtil.isBlank(groupBaseData.tableName)) {
throw new InvalidDataModelException(groupBaseData.modelName);
}
if (StringUtils.isBlank(groupBaseData.columnName)) {
if (StrUtil.isBlank(groupBaseData.columnName)) {
throw new InvalidDataFieldException(groupBaseData.modelName, groupBaseData.fieldName);
}
processGroupInfo(groupInfo, groupBaseData, groupByBuilder, groupSelectBuilder);
String aliasName = StringUtils.isBlank(groupInfo.aliasName) ? groupInfo.fieldName : groupInfo.aliasName;
groupParam.processGroupInfo(groupInfo, groupBaseData, groupByBuilder, groupSelectBuilder);
String aliasName = StrUtil.isBlank(groupInfo.aliasName) ? groupInfo.fieldName : groupInfo.aliasName;
// selectGroupFieldList中的元素目前只是被export操作使用。会根据集合中的元素名称匹配导出表头。
groupParam.selectGroupFieldList.add(aliasName);
if (++i < groupParam.size()) {
@@ -74,12 +79,12 @@ public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
return groupParam;
}
private static GroupBaseData parseGroupBaseData(GroupInfo groupInfo, Class<?> modelClazz) {
private GroupBaseData parseGroupBaseData(GroupInfo groupInfo, Class<?> modelClazz) {
GroupBaseData baseData = new GroupBaseData();
if (StringUtils.isBlank(groupInfo.fieldName)) {
if (StrUtil.isBlank(groupInfo.fieldName)) {
throw new IllegalArgumentException("GroupInfo.fieldName can't be EMPTY");
}
String[] stringArray = StringUtils.split(groupInfo.fieldName,'.');
String[] stringArray = StrUtil.splitToArray(groupInfo.fieldName, '.');
if (stringArray.length == 1) {
baseData.modelName = modelClazz.getSimpleName();
baseData.fieldName = groupInfo.fieldName;
@@ -99,29 +104,48 @@ public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
return baseData;
}
private static void processGroupInfo(
private void processGroupInfo(
GroupInfo groupInfo,
GroupBaseData baseData,
StringBuilder groupByBuilder,
StringBuilder groupSelectBuilder) {
String tableName = baseData.tableName;
String columnName = baseData.columnName;
if (StringUtils.isNotBlank(groupInfo.dateAggregateBy)) {
groupByBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
groupSelectBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
if (ApplicationConstant.DAY_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-%m-%d')");
groupSelectBuilder.append(", '%Y-%m-%d')");
} else if (ApplicationConstant.MONTH_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-%m-01')");
groupSelectBuilder.append(", '%Y-%m-01')");
} else if (ApplicationConstant.YEAR_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-01-01')");
groupSelectBuilder.append(", '%Y-01-01')");
if (StrUtil.isNotBlank(groupInfo.dateAggregateBy)) {
if (coreProperties.isMySql()) {
groupByBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
groupSelectBuilder.append("DATE_FORMAT(").append(tableName).append(".").append(columnName);
if (ApplicationConstant.DAY_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-%m-%d')");
groupSelectBuilder.append(", '%Y-%m-%d')");
} else if (ApplicationConstant.MONTH_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-%m-01')");
groupSelectBuilder.append(", '%Y-%m-01')");
} else if (ApplicationConstant.YEAR_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", '%Y-01-01')");
groupSelectBuilder.append(", '%Y-01-01')");
} else {
throw new IllegalArgumentException("Illegal DATE_FORMAT for GROUP ID list.");
}
} else if (coreProperties.isPostgresql()) {
groupByBuilder.append("TO_CHAR(").append(tableName).append(".").append(columnName);
groupSelectBuilder.append("TO_CHAR(").append(tableName).append(".").append(columnName);
if (ApplicationConstant.DAY_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", ''YYYY-MM-dd'')");
groupSelectBuilder.append(", 'YYYY-MM-dd'')");
} else if (ApplicationConstant.MONTH_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", 'YYYY-MM-01')");
groupSelectBuilder.append(", 'YYYY-MM-01')");
} else if (ApplicationConstant.YEAR_AGGREGATION.equals(groupInfo.dateAggregateBy)) {
groupByBuilder.append(", 'YYYY-01-01')");
groupSelectBuilder.append(", 'YYYY-01-01')");
} else {
throw new IllegalArgumentException("Illegal TO_CHAR for GROUP ID list.");
}
} else {
throw new IllegalArgumentException("Illegal DATE_FORMAT for GROUP ID list.");
throw new UnsupportedOperationException("Unsupport Database Type.");
}
if (StringUtils.isNotBlank(groupInfo.aliasName)) {
if (StrUtil.isNotBlank(groupInfo.aliasName)) {
groupSelectBuilder.append(" ").append(groupInfo.aliasName);
} else {
groupSelectBuilder.append(" ").append(columnName);
@@ -129,7 +153,7 @@ public class MyGroupParam extends ArrayList<MyGroupParam.GroupInfo> {
} else {
groupByBuilder.append(tableName).append(".").append(columnName);
groupSelectBuilder.append(tableName).append(".").append(columnName);
if (StringUtils.isNotBlank(groupInfo.aliasName)) {
if (StrUtil.isNotBlank(groupInfo.aliasName)) {
groupSelectBuilder.append(" ").append(groupInfo.aliasName);
}
}

View File

@@ -84,6 +84,10 @@ public class TokenData {
* 登录时间。
*/
private Date loginTime;
/**
* 登录头像地址。
*/
private String headImageUrl;
/**
* 将令牌对象添加到Http请求对象。

View File

@@ -0,0 +1,252 @@
package com.orangeforms.common.core.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.orangeforms.common.core.exception.MyRuntimeException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
* 导入工具类目前支持xlsx和xls两种类型。
*
* @author Jerry
* @date 2021-06-06
*/
@Slf4j
public class ImportUtil {
/**
* 根据实体类的Class类型生成导入的头信息。
*
* @param modelClazz 实体对象的Class类型。
* @param ignoreFields 忽略的字段名集合,如创建时间、创建人、更新时间、更新人等。
* @param <T> 实体对象类型。
* @return 创建后的导入头信息列表。
*/
public static <T> List<ImportHeaderInfo> makeHeaderInfoList(Class<T> modelClazz, Set<String> ignoreFields) {
List<ImportHeaderInfo> resultList = new LinkedList<>();
Field[] fields = ReflectUtil.getFields(modelClazz);
for (Field field : fields) {
int modifiers = field.getModifiers();
// transient类型的字段不能作为查询条件静态字段和逻辑删除都不考虑。需要忽略的字段也要跳过。
int transientMask = 128;
if ((modifiers & transientMask) == 1
|| Modifier.isStatic(modifiers)
|| field.getAnnotation(TableLogic.class) != null
|| ignoreFields.contains(field.getName())) {
continue;
}
TableField tableField = field.getAnnotation(TableField.class);
if (tableField == null || tableField.exist()) {
ImportHeaderInfo headerInfo = new ImportHeaderInfo();
headerInfo.fieldName = field.getName();
if (field.getType().equals(Integer.class)) {
headerInfo.fieldType = INT_TYPE;
} else if (field.getType().equals(Long.class)) {
headerInfo.fieldType = LONG_TYPE;
} else if (field.getType().equals(String.class)) {
headerInfo.fieldType = STRING_TYPE;
} else if (field.getType().equals(Boolean.class)) {
headerInfo.fieldType = BOOLEAN_TYPE;
} else if (field.getType().equals(Date.class)) {
headerInfo.fieldType = DATE_TYPE;
} else if (field.getType().equals(Double.class)) {
headerInfo.fieldType = DOUBLE_TYPE;
} else if (field.getType().equals(Float.class)) {
headerInfo.fieldType = FLOAT_TYPE;
} else if (field.getType().equals(BigDecimal.class)) {
headerInfo.fieldType = BIG_DECIMAL_TYPE;
} else {
throw new MyRuntimeException("Unsupport Import FieldType");
}
resultList.add(headerInfo);
}
}
return resultList;
}
/**
* 保存导入文件。
*
* @param baseDir 导入文件本地缓存的根目录。
* @param subDir 导入文件本地缓存的子目录。
* @param importFile 导入的文件。
* @return 保存的本地文件名。
*/
public static String saveImportFile(
String baseDir, String subDir, MultipartFile importFile) throws IOException {
StringBuilder sb = new StringBuilder(256);
sb.append(baseDir);
if (!StrUtil.endWith(baseDir, "/")) {
sb.append("/");
}
sb.append("importedFile/");
if (StrUtil.isNotBlank(subDir)) {
sb.append(subDir);
if (!StrUtil.endWith(subDir, "/")) {
sb.append("/");
}
}
String pathname = sb.toString();
sb.append(new DateTime().toString("yyyy-MM-dd-HH-mm-"));
sb.append(MyCommonUtil.generateUuid())
.append(".").append(FileNameUtil.getSuffix(importFile.getOriginalFilename()));
String fullname = sb.toString();
try {
byte[] bytes = importFile.getBytes();
Path path = Paths.get(fullname);
// 如果没有files文件夹则创建
if (!Files.isWritable(path)) {
Files.createDirectories(Paths.get(pathname));
}
// 文件写入指定路径
Files.write(path, bytes);
} catch (IOException e) {
log.error("Failed to write imported file [" + importFile.getOriginalFilename() + " ].", e);
throw e;
}
return fullname;
}
/**
* 导入指定的excel基于SAX方式解析后返回数据列表。
*
* @param headers 头信息数组。
* @param skipHeader 是否跳过第一行,通常改行为头信息。
* @param filename 文件名。
* @return 解析后数据列表。
*/
public static List<Map<String, Object>> doImport(
ImportHeaderInfo[] headers, boolean skipHeader, String filename) {
Assert.notNull(headers);
Assert.isTrue(StrUtil.isNotBlank(filename));
List<Map<String, Object>> resultList = new LinkedList<>();
ExcelUtil.readBySax(new File(filename), 0, createRowHandler(headers, skipHeader, resultList));
return resultList;
}
/**
* 导入指定的excel基于SAX方式解析后返回Bean类型的数据列表。
*
* @param headers 头信息数组。
* @param skipHeader 是否跳过第一行,通常改行为头信息。
* @param filename 文件名。
* @param clazz Bean的Class类型。
* @return 解析后数据列表。
*/
public static <T> List<T> doImport(
ImportHeaderInfo[] headers, boolean skipHeader, String filename, Class<T> clazz) {
List<Map<String, Object>> resultList = doImport(headers, skipHeader, filename);
return MyModelUtil.mapToBeanList(resultList, clazz);
}
private static RowHandler createRowHandler(
ImportHeaderInfo[] headers, boolean skipHeader, List<Map<String, Object>> resultList) {
return new MyRowHandler(headers, skipHeader, resultList);
}
public final static int INT_TYPE = 0;
public final static int LONG_TYPE = 1;
public final static int STRING_TYPE = 2;
public final static int BOOLEAN_TYPE = 3;
public final static int DATE_TYPE = 4;
public final static int DOUBLE_TYPE = 5;
public final static int FLOAT_TYPE = 6;
public final static int BIG_DECIMAL_TYPE = 7;
@Data
public static class ImportHeaderInfo {
/**
* 对应的Java实体对象属性名。
*/
private String fieldName;
/**
* 对应的Java实体对象类型。
*/
private Integer fieldType;
}
private static class MyRowHandler implements RowHandler {
private ImportHeaderInfo[] headers;
private boolean skipHeader;
private List<Map<String, Object>> resultList;
public MyRowHandler(ImportHeaderInfo[] headers, boolean skipHeader, List<Map<String, Object>> resultList) {
this.headers = headers;
this.skipHeader = skipHeader;
this.resultList = resultList;
}
@Override
public void handle(int sheetIndex, long rowIndex, List<Object> rowList) {
if (this.skipHeader && rowIndex == 0) {
return;
}
int i = 0;
Map<String, Object> data = new HashMap<>(headers.length);
for (Object rowData : rowList) {
if (i >= headers.length) {
log.warn("Exceeded the size of headers and ignore the left columns");
break;
}
ImportHeaderInfo headerInfo = this.headers[i++];
switch (headerInfo.fieldType) {
case INT_TYPE:
data.put(headerInfo.fieldName, Convert.toInt(rowData));
break;
case LONG_TYPE:
data.put(headerInfo.fieldName, Convert.toLong(rowData));
break;
case STRING_TYPE:
data.put(headerInfo.fieldName, Convert.toStr(rowData));
break;
case BOOLEAN_TYPE:
data.put(headerInfo.fieldName, Convert.toBool(rowData));
break;
case DATE_TYPE:
data.put(headerInfo.fieldName, Convert.toDate(rowData));
break;
case DOUBLE_TYPE:
data.put(headerInfo.fieldName, Convert.toDouble(rowData));
break;
case FLOAT_TYPE:
data.put(headerInfo.fieldName, Convert.toFloat(rowData));
break;
case BIG_DECIMAL_TYPE:
data.put(headerInfo.fieldName, Convert.toBigDecimal(rowData));
break;
default:
throw new MyRuntimeException(
"Invalid ImportHeaderInfo.fieldType [" + headerInfo.fieldType + "].");
}
}
resultList.add(data);
}
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private ImportUtil() {
}
}

View File

@@ -28,6 +28,10 @@ public class MyDateUtil {
* 统一的日期时间pattern今后可以根据自己的需求去修改。
*/
public static final String COMMON_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
/**
* 统一的短日期时间pattern今后可以根据自己的需求去修改。
*/
public static final String COMMON_SHORT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 缺省日期格式化器,提前获取提升运行时效率。
*/

View File

@@ -66,20 +66,34 @@ public class MyModelUtil {
private static final Map<String, Tuple2<String, Integer>> CACHED_COLUMNINFO_MAP = new ConcurrentHashMap<>();
/**
* 将bean的数据列表转换为Map列表。
* 将Bean的数据列表转换为Map列表。
*
* @param dataList bean数据列表。
* @param <T> bean对象类型。
* @param dataList Bean数据列表。
* @param <T> Bean对象类型。
* @return 转换后的Map列表。
*/
public static <T> List<Map<String, Object>> beanToMapList(List<T> dataList) {
if (CollectionUtils.isEmpty(dataList)) {
return null;
return new LinkedList<>();
}
List<Map<String, Object>> resultList = new LinkedList<>();
for (T data : dataList) {
resultList.add(BeanUtil.beanToMap(data));
dataList.forEach(data -> resultList.add(BeanUtil.beanToMap(data)));
return resultList;
}
/**
* 将Map的数据列表转换为Bean列表。
*
* @param dataList Map数据列表。
* @param <T> Bean对象类型。
* @return 转换后的Bean对象列表。
*/
public static <T> List<T> mapToBeanList(List<Map<String, Object>> dataList, Class<T> clazz) {
if (CollectionUtils.isEmpty(dataList)) {
return new LinkedList<>();
}
List<T> resultList = new LinkedList<>();
dataList.forEach(data -> resultList.add(BeanUtil.toBeanIgnoreError(data, clazz)));
return resultList;
}
@@ -742,10 +756,10 @@ public class MyModelUtil {
* @return 当前数据对象中,所有上传文件字段中,文件名属性的集合。
*/
public static <M> Set<String> extractDownloadFileName(List<M> dataList, Class<M> clazz) {
if (CollectionUtils.isEmpty(dataList)) {
return null;
}
Set<String> resultSet = new HashSet<>();
if (CollectionUtils.isEmpty(dataList)) {
return resultSet;
}
dataList.forEach(data -> resultSet.addAll(extractDownloadFileName(data, clazz)));
return resultSet;
}

View File

@@ -14,7 +14,7 @@ public class RedisKeyUtil {
* @return session缓存的键前缀。
*/
public static String getSessionIdPrefix() {
return "SESSIONID__";
return "SESSIONID:";
}
/**
@@ -24,7 +24,7 @@ public class RedisKeyUtil {
* @return session缓存的键前缀。
*/
public static String getSessionIdPrefix(String loginName) {
return "SESSIONID__" + loginName + "_";
return "SESSIONID:" + loginName + "_";
}
/**
@@ -35,7 +35,7 @@ public class RedisKeyUtil {
* @return session缓存的键前缀。
*/
public static String getSessionIdPrefix(String loginName, int deviceType) {
return "SESSIONID__" + loginName + "_" + deviceType + "_";
return "SESSIONID:" + loginName + "_" + deviceType + "_";
}
/**
@@ -45,7 +45,7 @@ public class RedisKeyUtil {
* @return 会话存储于Redis中的键值。
*/
public static String makeSessionIdKey(String sessionId) {
return "SESSIONID__" + sessionId;
return "SESSIONID:" + sessionId;
}
/**
@@ -55,7 +55,7 @@ public class RedisKeyUtil {
* @return 会话关联的权限数据存储于Redis中的键值。
*/
public static String makeSessionPermIdKey(String sessionId) {
return "PERM__" + sessionId;
return "PERM:" + sessionId;
}
/**
@@ -65,7 +65,7 @@ public class RedisKeyUtil {
* @return 会话关联的数据权限数据存储于Redis中的键值。
*/
public static String makeSessionDataPermIdKey(String sessionId) {
return "DATA_PERM__" + sessionId;
return "DATA_PERM:" + sessionId;
}
/**
@@ -75,7 +75,7 @@ public class RedisKeyUtil {
* @return 会话关联的数据权限数据存储于Redis中的键值。
*/
public static String makeOnlineTableKey(Long tableId) {
return "ONLINE_TABLE_" + tableId;
return "ONLINE_TABLE:" + tableId;
}
/**

View File

@@ -166,6 +166,7 @@ public class MybatisDataFilterInterceptor implements Interceptor {
// 通过注解解析与Mapper关联的Model并获取与数据权限关联的信息并将结果缓存。
ModelDataPermInfo info = new ModelDataPermInfo();
info.setMainTableName(MyModelUtil.mapToTableName(modelClazz));
info.setMustIncludeUserRule(rule.mustIncludeUserRule());
info.setExcludeMethodNameSet(excludeMethodNameSet);
if (userFilterField != null) {
info.setUserFilterColumn(MyModelUtil.mapToColumnName(userFilterField, modelClazz));
@@ -291,6 +292,12 @@ public class MybatisDataFilterInterceptor implements Interceptor {
if (dataPermMap.containsKey(DataPermRuleType.TYPE_ALL)) {
return;
}
// 如果当前过滤注解中mustIncludeUserRule参数为true同时当前用户的数据权限中不包含TYPE_USER_ONLY
// 这里就需要自动添加该数据权限。
if (info.getMustIncludeUserRule()
&& !dataPermMap.containsKey(DataPermRuleType.TYPE_USER_ONLY)) {
dataPermMap.put(DataPermRuleType.TYPE_USER_ONLY, null);
}
this.processDataPerm(info, dataPermMap, boundSql, commandType, statement);
}
@@ -458,6 +465,7 @@ public class MybatisDataFilterInterceptor implements Interceptor {
private String userFilterColumn;
private String deptFilterColumn;
private String mainTableName;
private Boolean mustIncludeUserRule;
}
@Data

View File

@@ -29,11 +29,13 @@ import com.orangeforms.common.online.service.OnlineOperationService;
import com.orangeforms.common.online.service.OnlineTableService;
import com.orangeforms.common.online.util.OnlineOperationHelper;
import com.orangeforms.common.flow.online.service.FlowOnlineOperationService;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.util.FlowOperationHelper;
import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.util.FlowOperationHelper;
import com.orangeforms.common.flow.dto.FlowTaskCommentDto;
import com.orangeforms.common.flow.exception.FlowOperationException;
import com.orangeforms.common.flow.model.constant.FlowMessageType;
import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.service.*;
import com.orangeforms.common.flow.vo.TaskInfoVo;
@@ -73,6 +75,8 @@ public class FlowOnlineOperationController {
@Autowired
private FlowWorkOrderService flowWorkOrderService;
@Autowired
private FlowMessageService flowMessageService;
@Autowired
private OnlineFormService onlineFormService;
@Autowired
private OnlinePageService onlinePageService;
@@ -92,6 +96,7 @@ public class FlowOnlineOperationController {
* @param taskVariableData 流程任务变量数据。
* @param masterData 流程审批相关的主表数据。
* @param slaveData 流程审批相关的多个从表数据。
* @param copyData 传阅数据格式为type和idtype的值参考FlowConstant中的常量值。
* @return 应答结果对象。
*/
@DisableDataFilter
@@ -101,7 +106,8 @@ public class FlowOnlineOperationController {
@MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto,
@MyRequestBody JSONObject taskVariableData,
@MyRequestBody(required = true) JSONObject masterData,
@MyRequestBody JSONObject slaveData) {
@MyRequestBody JSONObject slaveData,
@MyRequestBody JSONObject copyData) {
String errorMessage;
// 1. 验证流程数据的合法性。
ResponseResult<FlowEntry> flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey);
@@ -133,6 +139,13 @@ public class FlowOnlineOperationController {
if (!columnDataListResult.isSuccess()) {
return ResponseResult.errorFrom(columnDataListResult);
}
// 这里把传阅数据放到任务变量中,是为了避免给流程数据操作方法增加额外的方法调用参数。
if (MapUtil.isNotEmpty(copyData)) {
if (taskVariableData == null) {
taskVariableData = new JSONObject();
}
taskVariableData.put(FlowConstant.COPY_DATA_KEY, copyData);
}
FlowTaskComment flowTaskComment = BeanUtil.copyProperties(flowTaskCommentDto, FlowTaskComment.class);
// 5. 保存在线表单提交的数据,同时启动流程和自动完成第一个用户任务。
if (slaveData == null) {
@@ -170,6 +183,7 @@ public class FlowOnlineOperationController {
* @param taskVariableData 流程任务变量数据。
* @param masterData 流程审批相关的主表数据。
* @param slaveData 流程审批相关的多个从表数据。
* @param copyData 传阅数据格式为type和idtype的值参考FlowConstant中的常量值。
* @return 应答结果对象。
*/
@DisableDataFilter
@@ -180,7 +194,8 @@ public class FlowOnlineOperationController {
@MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto,
@MyRequestBody JSONObject taskVariableData,
@MyRequestBody JSONObject masterData,
@MyRequestBody JSONObject slaveData) {
@MyRequestBody JSONObject slaveData,
@MyRequestBody JSONObject copyData) {
String errorMessage;
// 验证流程任务的合法性。
Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId);
@@ -203,6 +218,13 @@ public class FlowOnlineOperationController {
Long datasourceId = datasource.getDatasourceId();
ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId);
String dataId = instance.getBusinessKey();
// 这里把传阅数据放到任务变量中,是为了避免给流程数据操作方法增加额外的方法调用参数。
if (MapUtil.isNotEmpty(copyData)) {
if (taskVariableData == null) {
taskVariableData = new JSONObject();
}
taskVariableData.put(FlowConstant.COPY_DATA_KEY, copyData);
}
FlowTaskComment flowTaskComment = BeanUtil.copyProperties(flowTaskCommentDto, FlowTaskComment.class);
if (StrUtil.isBlank(dataId)) {
return this.submitNewTask(processInstanceId, taskId,
@@ -286,8 +308,10 @@ public class FlowOnlineOperationController {
String loginName = TokenData.takeFromRequest().getLoginName();
if (StrUtil.isBlank(taskId)) {
if (!StrUtil.equals(loginName, instance.getStartUserId())) {
errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
if (!flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) {
errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配,或者没有查看该工单详情的数据权限!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
} else {
HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId);
@@ -296,8 +320,10 @@ public class FlowOnlineOperationController {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (!StrUtil.equals(loginName, taskInstance.getAssignee())) {
errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
if (!flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) {
errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配,或者没有查看该工单详情的数据权限!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
}
if (StrUtil.isBlank(instance.getBusinessKey())) {
@@ -323,6 +349,59 @@ public class FlowOnlineOperationController {
return ResponseResult.success(jsonData);
}
/**
* 根据消息Id获取流程Id关联的业务数据。
* NOTE白名单接口。
*
* @param messageId 抄送消息Id。
* @return 抄送消息关联的流程实例业务数据。
*/
@DisableDataFilter
@GetMapping("/viewCopyBusinessData")
public ResponseResult<JSONObject> viewCopyBusinessData(@RequestParam Long messageId) {
String errorMessage;
// 验证流程任务的合法性。
FlowMessage flowMessage = flowMessageService.getById(messageId);
if (flowMessage == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) {
errorMessage = "数据验证失败,当前消息不是抄送类型消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (flowMessage.getOnlineFormData() == null || !flowMessage.getOnlineFormData()) {
errorMessage = "数据验证失败,当前消息为静态路由表单数据,不能通过该接口获取!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) {
errorMessage = "数据验证失败,当前用户没有权限访问该消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
ProcessInstance instance = flowApiService.getProcessInstance(flowMessage.getProcessInstanceId());
// 如果业务主数据为空,则直接返回。
if (StrUtil.isBlank(instance.getBusinessKey())) {
errorMessage = "数据验证失败当前消息为所属流程实例没有包含业务主键Id";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
Long formId = Long.valueOf(flowMessage.getBusinessDataShot());
// 验证在线表单及其关联数据源的合法性。
ResponseResult<OnlineDatasource> datasourceResult = this.verifyAndGetOnlineDatasource(formId);
if (!datasourceResult.isSuccess()) {
return ResponseResult.errorFrom(datasourceResult);
}
OnlineDatasource datasource = datasourceResult.getData();
ResponseResult<List<OnlineDatasourceRelation>> relationListResult =
onlineOperationHelper.verifyAndGetRelationList(datasource.getDatasourceId(), null);
if (!relationListResult.isSuccess()) {
return ResponseResult.errorFrom(relationListResult);
}
JSONObject jsonData = this.buildUserTaskData(
instance.getBusinessKey(), datasource.getMasterTable(), relationListResult.getData());
// 将当前消息更新为已读
flowMessageService.readCopyTask(messageId);
return ResponseResult.success(jsonData);
}
/**
* 工作流工单列表。
*
@@ -344,9 +423,13 @@ public class FlowOnlineOperationController {
MyOrderParam orderParam = new MyOrderParam();
orderParam.add(new MyOrderParam.OrderInfo("workOrderId", false, null));
String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowWorkOrder.class);
List<FlowWorkOrder> flowWorkOrderList = flowWorkOrderService.getFlowWorkOrderList(flowWorkOrderFilter, orderBy);
List<FlowWorkOrder> flowWorkOrderList =
flowWorkOrderService.getFlowWorkOrderList(flowWorkOrderFilter, orderBy);
MyPageData<FlowWorkOrderVo> resultData =
MyPageUtil.makeResponseData(flowWorkOrderList, FlowWorkOrder.INSTANCE);
// 根据工单的提交用户名获取用户的显示名称,便于前端显示。
// 同时这也是一个如何通过插件方法将loginName映射到showName的示例
flowWorkOrderService.fillUserShowNameByLoginName(resultData.getDataList());
// 工单自身的查询中可以受到数据权限的过滤,但是工单集成业务数据时,则无需再对业务数据进行数据权限过滤了。
GlobalThreadLocal.setDataFilter(false);
ResponseResult<Void> responseResult = this.makeWorkOrderTaskInfo(resultData.getDataList());

View File

@@ -1,7 +1,10 @@
package com.orangeforms.common.flow.base.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.object.MyRelationParam;
import com.orangeforms.common.core.util.MyDateUtil;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.model.FlowTaskComment;
@@ -50,13 +53,44 @@ public abstract class BaseFlowService<M, K extends Serializable> extends BaseSer
flowApiService.completeTask(task, comment, variables);
}
/**
* 是否支持业务数据同步。每个子类需要根据实际情况判断是否需要支持。
*
* @return true支持否则false。
*/
public boolean supportSyncBusinessData() {
return false;
}
/**
* 在流程实例审批结束后,需要进行审批表到发布表数据同步的服务实现子类,需要实现该方法。
*
* @param processInstanceId 流程实例Id。
* @param businessKey 业务主键Id。如果与实际主键值类型不同需要在子类中自行完成类型转换。
*/
public void doSyncBusinessData(String processInstanceId, String businessKey) {
public void syncBusinessData(String processInstanceId, String businessKey) {
throw new UnsupportedOperationException();
}
/**
* 获取业务详情数据。
*
* @param processInstanceId 流程实例Id。
* @param businessKey 业务主键Id。如果与实际主键值类型不同需要在子类中自行完成类型转换。
* @return 业务主表数据,以及关联从表数据。
*/
@SuppressWarnings("unchecked")
public String getBusinessData(String processInstanceId, String businessKey) {
M data;
if (idFieldClass.equals(Long.class)) {
Long dataId = Long.valueOf(businessKey);
data = this.getByIdWithRelation((K) dataId, MyRelationParam.full());
} else if (idFieldClass.equals(Integer.class)) {
Integer dataId = Integer.valueOf(businessKey);
data = this.getByIdWithRelation((K) dataId, MyRelationParam.full());
} else {
data = this.getByIdWithRelation((K) businessKey, MyRelationParam.full());
}
return JSON.toJSONStringWithDateFormat(data, MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
}
}

View File

@@ -23,6 +23,11 @@ public class FlowConstant {
*/
public final static String PROC_INSTANCE_START_USER_NAME_VAR = "startUserName";
/**
* 流程任务的指定人变量。
*/
public final static String TASK_APPOINTED_ASSIGNEE_VAR = "appointedAssignee";
/**
* 操作类型变量。
*/
@@ -64,47 +69,62 @@ public class FlowConstant {
public final static String MULTI_ASSIGNEE_LIST_VAR = "assigneeList";
/**
* 上级部门领导审批变量
* 上级部门领导审批变量
*/
public final static String GROUP_TYPE_UP_DEPT_POST_LEADER_VAR = "upDeptPostLeader";
/**
* 上级部门领导审批变量
* 部门领导审批变量
*/
public final static String GROUP_TYPE_DEPT_POST_LEADER_VAR = "deptPostLeader";
/**
* 所有部门岗位审批变量
* 所有部门岗位审批变量
*/
public final static String GROUP_TYPE_ALL_DEPT_POST_VAR = "allDeptPost";
/**
* 本部门岗位审批变量
* 本部门岗位审批变量
*/
public final static String GROUP_TYPE_SELF_DEPT_POST_VAR = "selfDeptPost";
/**
* 上级部门岗位审批变量
* 上级部门岗位审批变量
*/
public final static String GROUP_TYPE_UP_DEPT_POST_VAR = "upDeptPost";
/**
* 任意部门关联的岗位审批变量
* 任意部门关联的岗位审批变量
*/
public final static String GROUP_TYPE_DEPT_POST_VAR = "deptPost";
/**
* 岗位
* 指定角色分组变量。
*/
public final static String GROUP_TYPE_ROLE_VAR = "role";
/**
* 指定部门分组变量。
*/
public final static String GROUP_TYPE_DEPT_VAR = "dept";
/**
* 指定用户分组变量。
*/
public final static String GROUP_TYPE_USER_VAR = "user";
/**
* 岗位。
*/
public final static String GROUP_TYPE_POST = "POST";
/**
* 上级部门领导审批
* 上级部门领导审批
*/
public final static String GROUP_TYPE_UP_DEPT_POST_LEADER = "UP_DEPT_POST_LEADER";
/**
* 本部门岗位领导审批
* 本部门岗位领导审批
*/
public final static String GROUP_TYPE_DEPT_POST_LEADER = "DEPT_POST_LEADER";
@@ -114,7 +134,17 @@ public class FlowConstant {
public final static String SELF_DEPT_POST_PREFIX = "SELF_DEPT_";
/**
* 部门岗位前缀。
* 上级部门岗位前缀。
*/
public final static String UP_DEPT_POST_PREFIX = "UP_DEPT_";
/**
* 当前流程实例所有任务的抄送数据前缀。
*/
public final static String COPY_DATA_MAP_PREFIX = "copyDataMap_";
/**
* 作为临时变量存入任务变量JSONObject对象时的key。
*/
public static final String COPY_DATA_KEY = "copyDataKey";
}

View File

@@ -18,8 +18,10 @@ import com.orangeforms.common.core.util.MyCommonUtil;
import com.orangeforms.common.core.util.MyModelUtil;
import com.orangeforms.common.core.util.MyPageUtil;
import com.orangeforms.common.core.validator.UpdateGroup;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.flow.object.FlowTaskMultiSignAssign;
import com.orangeforms.common.flow.constant.FlowTaskType;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.dto.*;
import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.model.constant.FlowEntryStatus;
@@ -371,6 +373,10 @@ public class FlowEntryController {
if (deptPostList != null) {
flowTaskExt.setDeptPostListJson(JSON.toJSONString(deptPostList));
}
List<JSONObject> copyList = this.buildCopyListExtensionElement(extensionMap);
if (copyList != null) {
flowTaskExt.setCopyListJson(JSON.toJSONString(copyList));
}
JSONObject candidateGroupObject = this.buildUserCandidateGroupsExtensionElement(extensionMap);
if (candidateGroupObject != null) {
String type = candidateGroupObject.getString("type");
@@ -572,6 +578,37 @@ public class FlowEntryController {
return resultList;
}
private List<JSONObject> buildCopyListExtensionElement(Map<String, List<ExtensionElement>> extensionMap) {
List<ExtensionElement> copyElements =
this.getMyExtensionElementList(extensionMap, "copyItemList", "copyItem");
if (CollUtil.isEmpty(copyElements)) {
return null;
}
List<JSONObject> resultList = new LinkedList<>();
for (ExtensionElement e : copyElements) {
JSONObject copyJsonData = new JSONObject();
String type = e.getAttributeValue(null, "type");
copyJsonData.put("type", type);
if (!StrUtil.equalsAny(type, FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR,
FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR,
FlowConstant.GROUP_TYPE_USER_VAR,
FlowConstant.GROUP_TYPE_ROLE_VAR,
FlowConstant.GROUP_TYPE_DEPT_VAR,
FlowConstant.GROUP_TYPE_DEPT_POST_VAR,
FlowConstant.GROUP_TYPE_ALL_DEPT_POST_VAR,
FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR,
FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR)) {
throw new MyRuntimeException("Invalid TYPE [" + type + " ] for CopyItenList Extension!");
}
String id = e.getAttributeValue(null, "id");
if (StrUtil.isNotBlank(id)) {
copyJsonData.put("id", id);
}
resultList.add(copyJsonData);
}
return resultList;
}
private List<ExtensionElement> getMyExtensionElementList(
Map<String, List<ExtensionElement>> extensionMap, String rootName, String childName) {
List<ExtensionElement> elementList = extensionMap.get(rootName);

View File

@@ -1,9 +1,12 @@
package com.orangeforms.common.flow.controller;
import io.swagger.annotations.Api;
import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.annotation.MyRequestBody;
import com.orangeforms.common.core.object.*;
import com.orangeforms.common.core.constant.ErrorCodeEnum;
import com.orangeforms.common.core.util.MyPageUtil;
import com.orangeforms.common.flow.model.constant.FlowMessageType;
import com.orangeforms.common.flow.model.FlowMessage;
import com.orangeforms.common.flow.service.FlowMessageService;
import com.orangeforms.common.flow.vo.FlowMessageVo;
@@ -29,9 +32,24 @@ public class FlowMessageController {
@Autowired
private FlowMessageService flowMessageService;
/**
* 获取当前用户的未读消息总数。
* NOTE白名单接口。
*
* @return 应答结果对象,包含当前用户的未读消息总数。
*/
@GetMapping("/getMessageCount")
public ResponseResult<JSONObject> getMessageCount() {
JSONObject resultData = new JSONObject();
resultData.put("remindingMessageCount", flowMessageService.countRemindingMessageListByUser());
resultData.put("copyMessageCount", flowMessageService.countCopyMessageByUser());
return ResponseResult.success(resultData);
}
/**
* 获取当前用户的催办消息列表。
* 不仅仅包含,其中包括当前用户所属角色、部门和岗位的候选组催办消息。
* NOTE白名单接口。
*
* @return 应答结果对象,包含查询结果集。
*/
@@ -43,4 +61,48 @@ public class FlowMessageController {
List<FlowMessage> flowMessageList = flowMessageService.getRemindingMessageListByUser();
return ResponseResult.success(MyPageUtil.makeResponseData(flowMessageList, FlowMessage.INSTANCE));
}
/**
* 获取当前用户的抄送消息列表。
* 不仅仅包含,其中包括当前用户所属角色、部门和岗位的候选组抄送消息。
* NOTE白名单接口。
*
* @param read true表示已读false表示未读。
* @return 应答结果对象,包含查询结果集。
*/
@PostMapping("/listCopyMessage")
public ResponseResult<MyPageData<FlowMessageVo>> listCopyMessage(
@MyRequestBody MyPageParam pageParam, @MyRequestBody Boolean read) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
List<FlowMessage> flowMessageList = flowMessageService.getCopyMessageListByUser(read);
return ResponseResult.success(MyPageUtil.makeResponseData(flowMessageList, FlowMessage.INSTANCE));
}
/**
* 读取抄送消息,同时更新当前用户对指定抄送消息的读取状态。
*
* @param messageId 消息Id。
* @return 应答结果对象。
*/
@PostMapping("/readCopyTask")
public ResponseResult<Void> readCopyTask(@MyRequestBody Long messageId) {
String errorMessage;
// 验证流程任务的合法性。
FlowMessage flowMessage = flowMessageService.getById(messageId);
if (flowMessage == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) {
errorMessage = "数据验证失败,当前消息不是抄送类型消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) {
errorMessage = "数据验证失败,当前用户没有权限访问该消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
flowMessageService.readCopyTask(messageId);
return ResponseResult.success();
}
}

View File

@@ -3,7 +3,6 @@ package com.orangeforms.common.flow.controller;
import io.swagger.annotations.Api;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@@ -15,8 +14,10 @@ import com.orangeforms.common.core.util.MyPageUtil;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.model.constant.FlowMessageType;
import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.service.*;
import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.util.FlowOperationHelper;
import com.orangeforms.common.flow.vo.FlowTaskCommentVo;
import com.orangeforms.common.flow.vo.FlowTaskVo;
@@ -29,6 +30,7 @@ import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
@@ -69,6 +71,8 @@ public class FlowOperationController {
private FlowMessageService flowMessageService;
@Autowired
private FlowOperationHelper flowOperationHelper;
@Autowired
private FlowCustomExtFactory flowCustomExtFactory;
/**
* 根据指定流程的主版本,发起一个流程实例。
@@ -199,10 +203,6 @@ public class FlowOperationController {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
FlowTaskComment taskComment = taskCommentList.get(0);
if (ObjectUtil.notEqual(taskComment.getCreateUserId(), TokenData.takeFromRequest().getUserId())) {
errorMessage = "数据验证失败,当前流程发起人与当前用户不匹配!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
HistoricTaskInstance task = flowApiService.getHistoricTaskInstance(processInstanceId, taskComment.getTaskId());
if (StrUtil.isBlank(task.getFormKey())) {
errorMessage = "数据验证失败指定任务的formKey属性不存在请重新修改流程图";
@@ -213,6 +213,59 @@ public class FlowOperationController {
return ResponseResult.success(taskInfo);
}
/**
* 根据消息Id获取流程Id关联的业务数据。
* NOTE白名单接口。
*
* @param messageId 抄送消息Id。
* @param snapshot 是否获取抄送或传阅时任务的业务快照数据。如果为true后续任务导致的业务数据修改将不会返回给前端。
* @return 抄送消息关联的流程实例业务数据。
*/
@DisableDataFilter
@GetMapping("/viewCopyBusinessData")
public ResponseResult<JSONObject> viewCopyBusinessData(
@RequestParam Long messageId, @RequestParam(required = false) Boolean snapshot) {
String errorMessage;
// 验证流程任务的合法性。
FlowMessage flowMessage = flowMessageService.getById(messageId);
if (flowMessage == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) {
errorMessage = "数据验证失败,当前消息不是抄送类型消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (flowMessage.getOnlineFormData() == null || flowMessage.getOnlineFormData()) {
errorMessage = "数据验证失败,当前消息为在线表单数据,不能通过该接口获取!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) {
errorMessage = "数据验证失败,当前用户没有权限访问该消息!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
JSONObject businessObject = null;
if (snapshot != null && snapshot) {
if (StrUtil.isNotBlank(flowMessage.getBusinessDataShot())) {
businessObject = JSON.parseObject(flowMessage.getBusinessDataShot());
}
return ResponseResult.success(businessObject);
}
ProcessInstance instance = flowApiService.getProcessInstance(flowMessage.getProcessInstanceId());
// 如果业务主数据为空,则直接返回。
if (StrUtil.isBlank(instance.getBusinessKey())) {
errorMessage = "数据验证失败当前消息为所属流程实例没有包含业务主键Id";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
String businessData = flowCustomExtFactory.getBusinessDataExtHelper().getBusinessData(
flowMessage.getProcessDefinitionKey(), flowMessage.getProcessInstanceId(), instance.getBusinessKey());
if (StrUtil.isNotBlank(businessData)) {
businessObject = JSON.parseObject(businessData);
}
// 将当前消息更新为已读
flowMessageService.readCopyTask(messageId);
return ResponseResult.success(businessObject);
}
/**
* 提交多实例加签。
*
@@ -292,6 +345,34 @@ public class FlowOperationController {
return ResponseResult.success(totalCount);
}
/**
* 主动驳回当前的待办任务到开始节点,只用当前待办任务的指派人或者候选者才能完成该操作。
*
* @param processInstanceId 流程实例Id。
* @param taskId 待办任务Id。
* @param comment 驳回备注。
* @return 操作应答结果。
*/
@PostMapping("/rejectToStartUserTask")
public ResponseResult<Void> rejectToStartUserTask(
@MyRequestBody(required = true) String processInstanceId,
@MyRequestBody(required = true) String taskId,
@MyRequestBody(required = true) String comment) {
String errorMessage;
ResponseResult<Task> taskResult =
flowOperationHelper.verifySubmitAndGetTask(processInstanceId, taskId, null);
if (!taskResult.isSuccess()) {
return ResponseResult.errorFrom(taskResult);
}
FlowTaskComment firstTaskComment = flowTaskCommentService.getFirstFlowTaskComment(processInstanceId);
CallResult result = flowApiService.backToRuntimeTask(
taskResult.getData(), firstTaskComment.getTaskKey(), true, comment);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
return ResponseResult.success();
}
/**
* 主动驳回当前的待办任务,只用当前待办任务的指派人或者候选者才能完成该操作。
*
@@ -311,7 +392,7 @@ public class FlowOperationController {
if (!taskResult.isSuccess()) {
return ResponseResult.errorFrom(taskResult);
}
CallResult result = flowApiService.backToLastRuntimeTask(taskResult.getData(), true, comment);
CallResult result = flowApiService.backToRuntimeTask(taskResult.getData(), null, true, comment);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
@@ -368,8 +449,8 @@ public class FlowOperationController {
Task task = activeTaskList.get(0);
task.setAssignee(TokenData.takeFromRequest().getLoginName());
} else {
CallResult result = flowApiService
.backToLastRuntimeTask(activeTaskList.get(0), false, comment);
CallResult result =
flowApiService.backToRuntimeTask(activeTaskList.get(0), null, false, comment);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
@@ -432,14 +513,17 @@ public class FlowOperationController {
//获取流程实例的历史节点(全部执行过的节点,被拒绝的任务节点将会出现多次)
List<HistoricActivityInstance> activityInstanceList =
flowApiService.getHistoricActivityInstanceList(processInstanceId);
List<String> activityInstanceTask = activityInstanceList.stream()
.filter(s -> !StrUtil.equals(s.getActivityType(), "sequenceFlow"))
.map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
Set<String> finishedTaskSequenceSet = new LinkedHashSet<>();
for (int i = 0; i < activityInstanceList.size(); i++) {
HistoricActivityInstance current = activityInstanceList.get(i);
if (i != activityInstanceList.size() - 1) {
HistoricActivityInstance next = activityInstanceList.get(i + 1);
finishedTaskSequenceSet.add(current.getActivityId() + next.getActivityId());
for (int i = 0; i < activityInstanceTask.size(); i++) {
String current = activityInstanceTask.get(i);
if (i != activityInstanceTask.size() - 1) {
String next = activityInstanceTask.get(i + 1);
finishedTaskSequenceSet.add(current + next);
}
finishedTaskSet.add(current.getActivityId());
finishedTaskSet.add(current);
}
Set<String> finishedSequenceFlowSet = new HashSet<>();
finishedTaskSequenceSet.forEach(s -> finishedSequenceFlowSet.add(allSequenceFlowMap.get(s)));

View File

@@ -2,6 +2,7 @@ package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowMessageCandidateIdentity;
import org.apache.ibatis.annotations.Param;
/**
* 流程任务消息的候选身份数据操作访问接口。
@@ -10,4 +11,11 @@ import com.orangeforms.common.flow.model.FlowMessageCandidateIdentity;
* @date 2021-06-06
*/
public interface FlowMessageCandidateIdentityMapper extends BaseDaoMapper<FlowMessageCandidateIdentity> {
/**
* 删除指定流程实例的消息关联数据。
*
* @param processInstanceId 流程实例Id。
*/
void deleteByProcessInstanceId(@Param("processInstanceId") String processInstanceId);
}

View File

@@ -0,0 +1,21 @@
package com.orangeforms.common.flow.dao;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.flow.model.FlowMessageIdentityOperation;
import org.apache.ibatis.annotations.Param;
/**
* 流程任务消息所属用户的操作数据操作访问接口。
*
* @author Jerry
* @date 2021-06-06
*/
public interface FlowMessageIdentityOperationMapper extends BaseDaoMapper<FlowMessageIdentityOperation> {
/**
* 删除指定流程实例的消息关联数据。
*
* @param processInstanceId 流程实例Id。
*/
void deleteByProcessInstanceId(@Param("processInstanceId") String processInstanceId);
}

View File

@@ -24,4 +24,37 @@ public interface FlowMessageMapper extends BaseDaoMapper<FlowMessage> {
*/
List<FlowMessage> getRemindingMessageListByUser(
@Param("loginName") String loginName, @Param("groupIdSet") Set<String> groupIdSet);
/**
* 获取指定用户和身份分组Id集合的抄送消息列表。
*
* @param loginName 用户登录名。
* @param groupIdSet 用户身份分组Id集合。
* @param read true表示已读false表示未读。
* @return 查询后的抄送消息列表。
*/
List<FlowMessage> getCopyMessageListByUser(
@Param("loginName") String loginName,
@Param("groupIdSet") Set<String> groupIdSet,
@Param("read") Boolean read);
/**
* 计算当前用户催办消息的数量。
*
* @param loginName 用户登录名。
* @param groupIdSet 用户身份分组Id集合。
* @return 数据数量。
*/
int countRemindingMessageListByUser(
@Param("loginName") String loginName, @Param("groupIdSet") Set<String> groupIdSet);
/**
* 计算当前用户未读抄送消息的数量。
*
* @param loginName 用户登录名。
* @param groupIdSet 用户身份分组Id集合。
* @return 数据数量
*/
int countCopyMessageListByUser(
@Param("loginName") String loginName, @Param("groupIdSet") Set<String> groupIdSet);
}

View File

@@ -9,11 +9,14 @@ import java.util.*;
/**
* 工作流工单表数据操作访问接口。
* 如果当前系统支持数据权限过滤当前用户必须要能看自己的工单数据所以需要把EnableDataPerm
* 的mustIncludeUserRule参数设置为true即便当前用户的数据权限中并不包含DataPermRuleType.TYPE_USER_ONLY
* 数据过滤拦截组件也会自动补偿该类型的数据权限,以便当前用户可以看到自己发起的工单。
*
* @author Jerry
* @date 2021-06-06
*/
@EnableDataPerm
@EnableDataPerm(mustIncludeUserRule = true)
public interface FlowWorkOrderMapper extends BaseDaoMapper<FlowWorkOrder> {
/**

View File

@@ -12,8 +12,29 @@
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
</resultMap>
<!-- 如果有逻辑删除字段过滤,请写到这里 -->
<sql id="filterRef">
<!-- 这里必须加上全包名否则当filterRef被其他Mapper.xml包含引用的时候就会调用Mapper.xml中的该SQL片段 -->
<include refid="com.orangeforms.common.flow.dao.FlowCategoryMapper.inputFilterRef"/>
</sql>
<!-- 这里仅包含调用接口输入的主表过滤条件 -->
<sql id="inputFilterRef">
<if test="flowCategoryFilter != null">
<if test="flowCategoryFilter.name != null and flowCategoryFilter.name != ''">
AND zz_flow_category.name = #{flowCategoryFilter.name}
</if>
<if test="flowCategoryFilter.code != null and flowCategoryFilter.code != ''">
AND zz_flow_category.code = #{flowCategoryFilter.code}
</if>
</if>
</sql>
<select id="getFlowCategoryList" resultMap="BaseResultMap" parameterType="com.orangeforms.common.flow.model.FlowCategory">
SELECT * FROM zz_flow_category
<where>
<include refid="filterRef"/>
</where>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>

View File

@@ -7,7 +7,7 @@
<result column="process_definition_key" jdbcType="VARCHAR" property="processDefinitionKey"/>
<result column="category_id" jdbcType="BIGINT" property="categoryId"/>
<result column="main_entry_publish_id" jdbcType="BIGINT" property="mainEntryPublishId"/>
<result column="lastest_publish_time" jdbcType="TIMESTAMP" property="lastestPublishTime"/>
<result column="latest_publish_time" jdbcType="TIMESTAMP" property="latestPublishTime"/>
<result column="status" jdbcType="INTEGER" property="status"/>
<result column="bpmn_xml" jdbcType="LONGVARCHAR" property="bpmnXml"/>
<result column="bind_form_type" jdbcType="INTEGER" property="bindFormType"/>
@@ -51,7 +51,7 @@
process_definition_key,
category_id,
main_entry_publish_id,
lastest_publish_time,
latest_publish_time,
status,
bind_form_type,
page_id,

View File

@@ -7,4 +7,10 @@
<result column="candidate_type" jdbcType="VARCHAR" property="candidateType"/>
<result column="candidate_id" jdbcType="VARCHAR" property="candidateId"/>
</resultMap>
<delete id="deleteByProcessInstanceId">
DELETE FROM zz_flow_message_candicate_identity a
WHERE EXISTS (SELECT * FROM zz_flow_message b
WHERE a.message_id = b.message_id AND b.process_instance_id = #{processInstanceId})
</delete>
</mapper>

View File

@@ -0,0 +1,17 @@
<?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="com.orangeforms.common.flow.dao.FlowMessageIdentityOperationMapper">
<resultMap id="BaseResultMap" type="com.orangeforms.common.flow.model.FlowMessageIdentityOperation">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="message_id" jdbcType="BIGINT" property="messageId"/>
<result column="login_name" jdbcType="VARCHAR" property="loginName"/>
<result column="operation_type" jdbcType="INTEGER" property="operationType"/>
<result column="operation_time" jdbcType="TIMESTAMP" property="operationTime"/>
</resultMap>
<delete id="deleteByProcessInstanceId">
DELETE FROM zz_flow_message_identity_operation a
WHERE EXISTS (SELECT * FROM zz_flow_message b
WHERE a.message_id = b.message_id AND b.process_instance_id = #{processInstanceId})
</delete>
</mapper>

View File

@@ -19,6 +19,7 @@
<result column="task_assignee" jdbcType="VARCHAR" property="taskAssignee"/>
<result column="task_finished" jdbcType="BIT" property="taskFinished"/>
<result column="business_data_shot" jdbcType="LONGVARCHAR" property="businessDataShot"/>
<result column="online_form_data" jdbcType="BIT" property="onlineFormData"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="update_user_id" jdbcType="BIGINT" property="updateUserId"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
@@ -31,17 +32,60 @@
<where>
a.task_finished = 0
AND a.message_type = 0
<if test="groupIdSet == null">
AND a.task_assignee = #{loginName}
</if>
<if test="groupIdSet != null">
AND (a.task_assignee = #{loginName} OR EXISTS (SELECT * FROM zz_flow_message_candicate_identity b
WHERE a.message_id = b.message_id AND b.candidate_id in
<foreach collection="groupIdSet" index="index" item="item" separator="," open="(" close=")">
#{item}
</foreach>))
</where>
ORDER BY a.update_time DESC
</select>
<select id="getCopyMessageListByUser" resultMap="BaseResultMap">
SELECT a.* FROM zz_flow_message a
<where>
a.message_type = 1
AND EXISTS (SELECT * FROM zz_flow_message_candicate_identity b
WHERE a.message_id = b.message_id AND b.candidate_id in
<foreach collection="groupIdSet" index="index" item="item" separator="," open="(" close=")">
#{item}
</foreach>)
<if test="!read">
AND NOT EXISTS (SELECT * FROM zz_flow_message_identity_operation c
WHERE a.message_id = c.message_id AND c.login_name = #{loginName})
</if>
<if test="read">
AND EXISTS (SELECT * FROM zz_flow_message_identity_operation c
WHERE a.message_id = c.message_id AND c.login_name = #{loginName})
</if>
</where>
ORDER BY a.update_time DESC
</select>
<select id="countRemindingMessageListByUser" resultType="java.lang.Integer">
SELECT COUNT(1) FROM zz_flow_message a
<where>
a.task_finished = 0
AND a.message_type = 0
AND (a.task_assignee = #{loginName} OR EXISTS (SELECT * FROM zz_flow_message_candicate_identity b
WHERE a.message_id = b.message_id AND b.candidate_id in
<foreach collection="groupIdSet" index="index" item="item" separator="," open="(" close=")">
#{item}
</foreach>))
</where>
</select>
<select id="countCopyMessageListByUser" resultType="java.lang.Integer">
SELECT COUNT(1) FROM zz_flow_message a
<where>
a.message_type = 1
AND EXISTS (SELECT * FROM zz_flow_message_candicate_identity b
WHERE a.message_id = b.message_id AND b.candidate_id in
<foreach collection="groupIdSet" index="index" item="item" separator="," open="(" close=")">
#{item}
</foreach>)
AND NOT EXISTS (SELECT * FROM zz_flow_message_identity_operation c
WHERE a.message_id = c.message_id AND c.login_name = #{loginName})
</where>
</select>
</mapper>

View File

@@ -9,8 +9,10 @@
<result column="task_name" jdbcType="VARCHAR" property="taskName"/>
<result column="approval_type" jdbcType="VARCHAR" property="approvalType"/>
<result column="delegate_assignee" jdbcType="VARCHAR" property="delegateAssginee"/>
<result column="custom_business_data" jdbcType="LONGVARCHAR" property="customBusinessData"/>
<result column="comment" jdbcType="VARCHAR" property="comment"/>
<result column="create_user_id" jdbcType="BIGINT" property="createUserId"/>
<result column="create_login_name" jdbcType="VARCHAR" property="createLoginName"/>
<result column="create_username" jdbcType="VARCHAR" property="createUsername"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
</resultMap>

View File

@@ -12,6 +12,7 @@
<result column="role_ids" jdbcType="VARCHAR" property="roleIds"/>
<result column="dept_ids" jdbcType="VARCHAR" property="deptIds"/>
<result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/>
<result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/>
</resultMap>
<insert id="insertList">
@@ -26,7 +27,8 @@
#{item.deptPostListJson},
#{item.roleIds},
#{item.deptIds},
#{item.candidateUsernames})
#{item.candidateUsernames},
#{item.copyListJson})
</foreach>
</insert>
</mapper>

View File

@@ -35,7 +35,7 @@ public class FlowFinishedListener implements ExecutionListener {
flowWorkOrderService.updateFlowStatusByProcessInstanceId(processInstanceId, FlowTaskStatus.FINISHED);
String businessKey = execution.getProcessInstanceBusinessKey();
FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId);
flowCustomExtFactory.getDataSyncExtHelper()
flowCustomExtFactory.getBusinessDataExtHelper()
.triggerSync(workOrder.getProcessDefinitionKey(), processInstanceId, businessKey);
}
}

View File

@@ -54,8 +54,8 @@ public class FlowEntry {
/**
* 最新发布时间。
*/
@TableField(value = "lastest_publish_time")
private Date lastestPublishTime;
@TableField(value = "latest_publish_time")
private Date latestPublishTime;
/**
* 流程状态。

View File

@@ -123,6 +123,12 @@ public class FlowMessage {
@TableField(value = "business_data_shot")
private String businessDataShot;
/**
* 是否为在线表单消息数据。
*/
@TableField(value = "online_form_data")
private Boolean onlineFormData;
/**
* 更新时间。
*/

View File

@@ -0,0 +1,50 @@
package com.orangeforms.common.flow.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 流程任务消息所属用户的操作表。
*
* @author Jerry
* @date 2021-06-06
*/
@Data
@TableName(value = "zz_flow_message_identity_operation")
public class FlowMessageIdentityOperation {
/**
* 主键Id。
*/
@TableId(value = "id")
private Long id;
/**
* 任务消息Id。
*/
@TableField(value = "message_id")
private Long messageId;
/**
* 用户登录名。
*/
@TableField(value = "login_name")
private String loginName;
/**
* 操作类型。
* 常量值参考FlowMessageOperationType对象。
*/
@TableField(value = "operation_type")
private Integer operationType;
/**
* 操作时间。
*/
@TableField(value = "operation_time")
private Date operationTime;
}

View File

@@ -70,12 +70,24 @@ public class FlowTaskComment {
@TableField(value = "delegate_assignee")
private String delegateAssginee;
/**
* 自定义数据。开发者可自行扩展推荐使用JSON格式数据。
*/
@TableField(value = "custom_business_data")
private String customBusinessData;
/**
* 创建者Id。
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 创建者登录名。
*/
@TableField(value = "create_login_name")
private String createLoginName;
/**
* 创建者显示名。
*/

View File

@@ -72,4 +72,10 @@ public class FlowTaskExt {
*/
@TableField(value = "candidate_usernames")
private String candidateUsernames;
/**
* 抄送相关的数据。
*/
@TableField(value = "copy_list_json")
private String copyListJson;
}

View File

@@ -0,0 +1,21 @@
package com.orangeforms.common.flow.model.constant;
/**
* 工作流消息操作类型。
*
* @author Jerry
* @date 2021-06-06
*/
public final class FlowMessageOperationType {
/**
* 已读操作。
*/
public static final int READ_FINISHED = 0;
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private FlowMessageOperationType() {
}
}

View File

@@ -16,9 +16,15 @@ public final class FlowMessageType {
*/
public static final int REMIND_TYPE = 0;
/**
* 抄送消息。
*/
public static final int COPY_TYPE = 1;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(REMIND_TYPE, "催办消息");
DICT_MAP.put(COPY_TYPE, "抄送消息");
}
/**

View File

@@ -402,12 +402,13 @@ public interface FlowApiService {
BpmnModel convertToBpmnModel(String bpmnXml) throws XMLStreamException;
/**
* 回退到上一个用户任务节点。
* 回退到上一个用户任务节点。如果没有指定,则回退到上一个任务。
*
* @param task 当前活动任务。
* @param targetKey 指定回退到的任务标识。如果为null则回退到上一个任务。
* @param forReject true表示驳回false为撤回。
* @param reason 驳回或者撤销的原因。
* @return 回退结果。
*/
CallResult backToLastRuntimeTask(Task task, boolean forReject, String reason);
CallResult backToRuntimeTask(Task task, String targetKey, boolean forReject, String reason);
}

View File

@@ -1,7 +1,6 @@
package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.core.object.CallResult;
import com.orangeforms.common.flow.model.*;
import javax.xml.stream.XMLStreamException;

View File

@@ -1,8 +1,10 @@
package com.orangeforms.common.flow.service;
import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowMessage;
import com.orangeforms.common.flow.model.FlowWorkOrder;
import org.flowable.task.api.Task;
import java.util.List;
@@ -29,6 +31,14 @@ public interface FlowMessageService extends IBaseService<FlowMessage, Long> {
*/
void saveNewRemindMessage(FlowWorkOrder flowWorkOrder);
/**
* 保存抄送消息对象。
*
* @param task 待抄送的任务。
* @param copyDataJson 抄送人员或者组的Id数据。
*/
void saveNewCopyMessage(Task task, JSONObject copyDataJson);
/**
* 更新指定运行时任务Id的消费为已完成状态。
*
@@ -49,4 +59,48 @@ public interface FlowMessageService extends IBaseService<FlowMessage, Long> {
* @return 查询后的催办消息列表。
*/
List<FlowMessage> getRemindingMessageListByUser();
/**
* 获取当前用户的抄送消息列表。
*
* @param read true表示已读false表示未读。
* @return 查询后的抄送消息列表。
*/
List<FlowMessage> getCopyMessageListByUser(Boolean read);
/**
* 判断当前用户是否有权限访问指定消息Id。
*
* @param messageId 消息Id。
* @return true为合法访问者否则false。
*/
boolean isCandidateIdentityOnMessage(Long messageId);
/**
* 读取抄送消息,同时更新当前用户对指定抄送消息的读取状态。
*
* @param messageId 消息Id。
*/
void readCopyTask(Long messageId);
/**
* 计算当前用户催办消息的数量。
*
* @return 当前用户催办消息数量。
*/
int countRemindingMessageListByUser();
/**
* 计算当前用户未读抄送消息的数量。
*
* @return 当前用户未读抄送消息数量。
*/
int countCopyMessageByUser();
/**
* 删除指定流程实例的消息。
*
* @param processInstanceId 流程实例Id。
*/
void removeByProcessInstanceId(String processInstanceId);
}

View File

@@ -44,4 +44,21 @@ public interface FlowTaskCommentService extends IBaseService<FlowTaskComment, Lo
* @return 查询结果。
*/
FlowTaskComment getLatestFlowTaskComment(String processInstanceId);
/**
* 获取指定流程实例和任务定义标识的最后一条审批任务。
*
* @param processInstanceId 流程实例Id。
* @param taskDefinitionKey 任务定义标识。
* @return 查询结果。
*/
FlowTaskComment getLatestFlowTaskComment(String processInstanceId, String taskDefinitionKey);
/**
* 获取指定流程实例的第一条审批任务。
*
* @param processInstanceId 流程实例Id。
* @return 查询结果。
*/
FlowTaskComment getFirstFlowTaskComment(String processInstanceId);
}

View File

@@ -2,6 +2,7 @@ package com.orangeforms.common.flow.service;
import com.orangeforms.common.core.base.service.IBaseService;
import com.orangeforms.common.flow.model.FlowWorkOrder;
import com.orangeforms.common.flow.vo.FlowWorkOrderVo;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.*;
@@ -83,4 +84,19 @@ public interface FlowWorkOrderService extends IBaseService<FlowWorkOrder, Long>
* @param flowStatus 新的流程状态值。
*/
void updateFlowStatusByProcessInstanceId(String processInstanceId, int flowStatus);
/**
* 是否有查看该工单的数据权限。
* @param processInstanceId 流程实例Id。
* @return 存在返回true否则false。
*/
boolean hasDataPermOnFlowWorkOrder(String processInstanceId);
/**
* 根据工单列表中的submitUserName找到映射的userShowName并会写到Vo中指定字段。
* 同时这也是一个如何通过插件方法将loginName映射到showName的示例
*
* @param dataList 工单Vo对象列表。
*/
void fillUserShowNameByLoginName(List<FlowWorkOrderVo> dataList);
}

View File

@@ -15,6 +15,7 @@ import com.orangeforms.common.core.object.CallResult;
import com.orangeforms.common.core.object.MyPageData;
import com.orangeforms.common.core.object.MyPageParam;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.util.MyDateUtil;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.constant.FlowApprovalType;
import com.orangeforms.common.flow.constant.FlowTaskStatus;
@@ -38,6 +39,7 @@ import org.flowable.engine.history.*;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ChangeActivityStateBuilder;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
@@ -90,9 +92,14 @@ public class FlowApiServiceImpl implements FlowApiService {
@Override
public ProcessInstance start(String processDefinitionId, Object dataId) {
String loginName = TokenData.takeFromRequest().getLoginName();
Map<String, Object> variableMap = new HashMap<>(4);
variableMap.put(FlowConstant.PROC_INSTANCE_INITIATOR_VAR, loginName);
variableMap.put(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR, loginName);
Map<String, Object> variableMap;
if (dataId == null) {
variableMap = new HashMap<>(2);
variableMap.put(FlowConstant.PROC_INSTANCE_INITIATOR_VAR, loginName);
variableMap.put(FlowConstant.PROC_INSTANCE_START_USER_NAME_VAR, loginName);
} else {
variableMap = this.initAndGetProcessInstanceVariables(processDefinitionId);
}
Authentication.setAuthenticatedUserId(loginName);
String businessKey = dataId == null ? null : dataId.toString();
return runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variableMap);
@@ -133,8 +140,8 @@ public class FlowApiServiceImpl implements FlowApiService {
FlowTaskComment flowTaskComment = new FlowTaskComment();
flowTaskComment.fillWith(startTaskInstance);
flowTaskComment.setApprovalType(FlowApprovalType.MULTI_CONSIGN);
String loginName = TokenData.takeFromRequest().getLoginName();
String comment = String.format("用户 [%s] 加签 [%s]。", loginName, newAssignees);
String showName = TokenData.takeFromRequest().getLoginName();
String comment = String.format("用户 [%s] 加签 [%s]。", showName, newAssignees);
flowTaskComment.setComment(comment);
flowTaskCommentService.saveNew(flowTaskComment);
return;
@@ -143,6 +150,10 @@ public class FlowApiServiceImpl implements FlowApiService {
@Transactional(rollbackFor = Exception.class)
@Override
public void completeTask(Task task, FlowTaskComment flowTaskComment, JSONObject taskVariableData) {
JSONObject passCopyData = null;
if (taskVariableData != null) {
passCopyData = (JSONObject) taskVariableData.remove(FlowConstant.COPY_DATA_KEY);
}
if (flowTaskComment != null) {
// 这里处理多实例会签逻辑。
if (flowTaskComment.getApprovalType().equals(FlowApprovalType.MULTI_SIGN)) {
@@ -183,13 +194,80 @@ public class FlowApiServiceImpl implements FlowApiService {
taskVariableData.put(FlowConstant.OPERATION_TYPE_VAR, flowTaskComment.getApprovalType());
flowTaskComment.fillWith(task);
flowTaskCommentService.saveNew(flowTaskComment);
taskService.complete(task.getId(), taskVariableData);
} else {
taskService.complete(task.getId(), taskVariableData);
}
// 判断当前完成执行的任务,是否存在抄送设置。
Object copyData = runtimeService.getVariable(
task.getProcessInstanceId(), FlowConstant.COPY_DATA_MAP_PREFIX + task.getTaskDefinitionKey());
if (copyData != null || passCopyData != null) {
JSONObject copyDataJson = this.mergeCopyData(copyData, passCopyData);
flowMessageService.saveNewCopyMessage(task, copyDataJson);
}
taskService.complete(task.getId(), taskVariableData);
flowMessageService.updateFinishedStatusByTaskId(task.getId());
}
private JSONObject mergeCopyData(Object copyData, JSONObject passCopyData) {
TokenData tokenData = TokenData.takeFromRequest();
// passCopyData是传阅数据copyData是抄送数据。
JSONObject resultCopyDataJson = passCopyData;
if (resultCopyDataJson == null) {
resultCopyDataJson = JSON.parseObject(copyData.toString());
} else if (copyData != null) {
JSONObject copyDataJson = JSON.parseObject(copyData.toString());
for (Map.Entry<String, Object> entry : copyDataJson.entrySet()) {
String value = resultCopyDataJson.getString(entry.getKey());
if (value == null) {
resultCopyDataJson.put(entry.getKey(), entry.getValue());
} else {
List<String> list1 = StrUtil.split(value, ",");
List<String> list2 = StrUtil.split(entry.getValue().toString(), ",");
Set<String> valueSet = new HashSet<>(list1);
valueSet.addAll(list2);
resultCopyDataJson.put(entry.getKey(), StrUtil.join(",", valueSet));
}
}
}
BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
for (Map.Entry<String, Object> entry : resultCopyDataJson.entrySet()) {
String type = entry.getKey();
switch (type) {
case FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR:
Object upLeaderDeptPostId =
flowIdentityExtHelper.getUpLeaderDeptPostId(tokenData.getDeptId());
entry.setValue(upLeaderDeptPostId);
break;
case FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR:
Object leaderDeptPostId =
flowIdentityExtHelper.getLeaderDeptPostId(tokenData.getDeptId());
entry.setValue(leaderDeptPostId);
break;
case FlowConstant.GROUP_TYPE_SELF_DEPT_POST_VAR:
Set<String> selfPostIdSet = new HashSet<>(StrUtil.split(entry.getValue().toString(), ","));
Map<String, String> deptPostIdMap =
flowIdentityExtHelper.getDeptPostIdMap(tokenData.getDeptId(), selfPostIdSet);
String deptPostIdValues = "";
if (deptPostIdMap != null) {
deptPostIdValues = StrUtil.join(",", deptPostIdMap.values());
}
entry.setValue(deptPostIdValues);
break;
case FlowConstant.GROUP_TYPE_UP_DEPT_POST_VAR:
Set<String> upPostIdSet = new HashSet<>(StrUtil.split(entry.getValue().toString(), ","));
Map<String, String> upDeptPostIdMap =
flowIdentityExtHelper.getUpDeptPostIdMap(tokenData.getDeptId(), upPostIdSet);
String upDeptPostIdValues = "";
if (upDeptPostIdMap != null) {
upDeptPostIdValues = StrUtil.join(",", upDeptPostIdMap.values());
}
entry.setValue(upDeptPostIdValues);
break;
default:
break;
}
}
return resultCopyDataJson;
}
@Transactional(rollbackFor = Exception.class)
@Override
public CallResult verifyAssigneeOrCandidateAndClaim(Task task) {
@@ -257,9 +335,27 @@ public class FlowApiServiceImpl implements FlowApiService {
this.buildPostCandidateGroupData(flowIdentityExtHelper, flowTaskExtList);
variableMap.putAll(postGroupDataMap);
}
this.buildCopyData(flowTaskExtList, variableMap);
return variableMap;
}
private void buildCopyData(List<FlowTaskExt> flowTaskExtList, Map<String, Object> variableMap) {
TokenData tokenData = TokenData.takeFromRequest();
for (FlowTaskExt flowTaskExt : flowTaskExtList) {
if (StrUtil.isBlank(flowTaskExt.getCopyListJson())) {
continue;
}
List<JSONObject> copyDataList = JSON.parseArray(flowTaskExt.getCopyListJson(), JSONObject.class);
Map<String, Object> copyDataMap = new HashMap<>(copyDataList.size());
for (JSONObject copyData : copyDataList) {
String type = copyData.getString("type");
String id = copyData.getString("id");
copyDataMap.put(type, id == null ? "" : id);
}
variableMap.put(FlowConstant.COPY_DATA_MAP_PREFIX + flowTaskExt.getTaskId(), JSON.toJSONString(copyDataMap));
}
}
private Map<String, Object> buildPostCandidateGroupData(
BaseFlowIdentityExtHelper flowIdentityExtHelper, List<FlowTaskExt> flowTaskExtList) {
Map<String, Object> postVariableMap = new HashMap<>();
@@ -534,11 +630,11 @@ public class FlowApiServiceImpl implements FlowApiService {
query.startedBy(startUser);
}
if (StrUtil.isNotBlank(beginDate)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
query.startedAfter(sdf.parse(beginDate));
}
if (StrUtil.isNotBlank(endDate)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
query.startedBefore(sdf.parse(endDate));
}
if (finishedOnly) {
@@ -565,11 +661,11 @@ public class FlowApiServiceImpl implements FlowApiService {
query.processDefinitionName(processDefinitionName);
}
if (StrUtil.isNotBlank(beginDate)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
query.taskCompletedAfter(sdf.parse(beginDate));
}
if (StrUtil.isNotBlank(endDate)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
query.taskCompletedBefore(sdf.parse(endDate));
}
query.orderByHistoricTaskInstanceEndTime().desc();
@@ -664,6 +760,7 @@ public class FlowApiServiceImpl implements FlowApiService {
public void deleteProcessInstance(String processInstanceId) {
historyService.deleteHistoricProcessInstance(processInstanceId);
flowWorkOrderService.removeByProcessInstanceId(processInstanceId);
flowMessageService.removeByProcessInstanceId(processInstanceId);
}
@Override
@@ -681,80 +778,95 @@ public class FlowApiServiceImpl implements FlowApiService {
@Transactional
@Override
public CallResult backToLastRuntimeTask(Task task, boolean forReject, String reason) {
public CallResult backToRuntimeTask(Task task, String targetKey, boolean forReject, String reason) {
String errorMessage;
ProcessDefinition processDefinition = this.getProcessDefinitionById(task.getProcessDefinitionId());
Collection<FlowElement> allElements = this.getProcessAllElements(processDefinition.getId());
FlowElement source = null;
// 获取跳转的节点元素
FlowElement target = null;
for (FlowElement flowElement : allElements) {
if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
source = flowElement;
break;
if (StrUtil.isBlank(targetKey)) {
break;
}
}
if (StrUtil.isNotBlank(targetKey)) {
if (flowElement.getId().equals(targetKey)) {
target = flowElement;
}
}
}
List<UserTask> parentUserTaskList =
this.getParentUserTaskList(source, null, null);
if (CollUtil.isEmpty(parentUserTaskList)) {
errorMessage = "数据验证失败,当前节点为初始任务节点,不能驳回!";
if (targetKey != null && target == null) {
errorMessage = "数据验证失败,被驳回的指定目标节点不存在!";
return CallResult.error(errorMessage);
}
// 获取活动ID, 即节点Key
Set<String> parentUserTaskKeySet = new HashSet<>();
parentUserTaskList.forEach(item -> parentUserTaskKeySet.add(item.getId()));
List<HistoricActivityInstance> historicActivityIdList =
this.getHistoricActivityInstanceListOrderByStartTime(task.getProcessInstanceId());
// 数据清洗,将回滚导致的脏数据清洗掉
List<String> lastHistoricTaskInstanceList =
this.cleanHistoricTaskInstance(allElements, historicActivityIdList);
// 此时历史任务实例为倒序,获取最后走的节点
List<String> targetIds = new ArrayList<>();
// 循环结束标识,遇到当前目标节点的次数
int number = 0;
StringBuilder parentHistoricTaskKey = new StringBuilder();
for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) {
// 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过
if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) {
continue;
UserTask oneUserTask = null;
List<String> targetIds = null;
if (target == null) {
List<UserTask> parentUserTaskList =
this.getParentUserTaskList(source, null, null);
if (CollUtil.isEmpty(parentUserTaskList)) {
errorMessage = "数据验证失败,当前节点为初始任务节点,不能驳回!";
return CallResult.error(errorMessage);
}
parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey);
if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) {
number++;
}
if (number == 2) {
break;
}
// 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点
if (parentUserTaskKeySet.contains(historicTaskInstanceKey)) {
targetIds.add(historicTaskInstanceKey);
// 获取活动ID, 即节点Key
Set<String> parentUserTaskKeySet = new HashSet<>();
parentUserTaskList.forEach(item -> parentUserTaskKeySet.add(item.getId()));
List<HistoricActivityInstance> historicActivityIdList =
this.getHistoricActivityInstanceListOrderByStartTime(task.getProcessInstanceId());
// 数据清洗,将回滚导致的脏数据清洗掉
List<String> lastHistoricTaskInstanceList =
this.cleanHistoricTaskInstance(allElements, historicActivityIdList);
// 此时历史任务实例为倒序,获取最后走的节点
targetIds = new ArrayList<>();
// 循环结束标识,遇到当前目标节点的次数
int number = 0;
StringBuilder parentHistoricTaskKey = new StringBuilder();
for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) {
// 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过
if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) {
continue;
}
parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey);
if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) {
number++;
}
if (number == 2) {
break;
}
// 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点
if (parentUserTaskKeySet.contains(historicTaskInstanceKey)) {
targetIds.add(historicTaskInstanceKey);
}
}
// 目的获取所有需要被跳转的节点 currentIds
// 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路
oneUserTask = parentUserTaskList.get(0);
}
// 目的获取所有需要被跳转的节点 currentIds
// 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路
UserTask oneUserTask = parentUserTaskList.get(0);
// 获取所有正常进行的执行任务的活动节点ID这些任务不能直接使用需要找出其中需要撤回的任务
List<Execution> runExecutionList =
runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId()).list();
List<String> runActivityIdList = new ArrayList<>();
runExecutionList.forEach(item -> {
if (StrUtil.isNotBlank(item.getActivityId())) {
runActivityIdList.add(item.getActivityId());
}
});
List<String> runActivityIdList = runExecutionList.stream()
.filter(c -> StrUtil.isNotBlank(c.getActivityId()))
.map(Execution::getActivityId).collect(Collectors.toList());
// 需驳回任务列表
List<String> currentIds = new ArrayList<>();
// 通过父级网关的出口连线,结合 runExecutionList 比对,获取需要撤回的任务
List<FlowElement> currentFlowElementList =
this.getChildUserTaskList(oneUserTask, runActivityIdList, null, null);
List<FlowElement> currentFlowElementList = this.getChildUserTaskList(
target != null ? target : oneUserTask, runActivityIdList, null, null);
currentFlowElementList.forEach(item -> currentIds.add(item.getId()));
// 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况
if (targetIds.size() > 1 && currentIds.size() > 1) {
errorMessage = "数据验证失败,任务出现多对多情况,无法撤回!";
return CallResult.error(errorMessage);
if (target == null) {
// 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况
if (targetIds.size() > 1 && currentIds.size() > 1) {
errorMessage = "数据验证失败,任务出现多对多情况,无法撤回!";
return CallResult.error(errorMessage);
}
}
AtomicReference<List<HistoricActivityInstance>> tmp = new AtomicReference<>();
// 用于下面新增网关删除信息时使用
String targetTmp = String.join(",", targetIds);
String targetTmp = targetKey != null ? targetKey : String.join(",", targetIds);
// currentIds 为活动ID列表
// currentExecutionIds 为执行任务ID列表
// 需要通过执行任务ID来设置驳回信息活动ID不行
@@ -778,19 +890,43 @@ public class FlowApiServiceImpl implements FlowApiService {
}
}));
try {
// 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况
if (targetIds.size() > 1) {
// 1 对 多任务跳转currentIds 当前节点(1)targetIds 跳转到的节点(多)
if (StrUtil.isNotBlank(targetKey)) {
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState();
}
// 如果父级任务只有一个,因此当前任务可能为网关中的任务
if (targetIds.size() == 1) {
// 1 对 1 或 多 对 1 情况currentIds 当前要跳转的节点列表(1或多)targetIds.get(0) 跳转到的节点(1)
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState();
.moveActivityIdsToSingleActivityId(currentIds, targetKey).changeState();
} else {
// 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况
if (targetIds.size() > 1) {
// 1 对 多任务跳转currentIds 当前节点(1)targetIds 跳转到的节点()
ChangeActivityStateBuilder builder = runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds);
for (String targetId : targetIds) {
FlowTaskComment taskComment =
flowTaskCommentService.getLatestFlowTaskComment(task.getProcessInstanceId(), targetId);
// 如果驳回后的目标任务包含指定人,则直接通过变量回抄,如果没有则自动忽略该变量,不会给流程带来任何影响。
String submitLoginName = taskComment.getCreateLoginName();
if (StrUtil.isNotBlank(submitLoginName)) {
builder.localVariable(targetId, FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR, submitLoginName);
}
}
builder.changeState();
}
// 如果父级任务只有一个,因此当前任务可能为网关中的任务
if (targetIds.size() == 1) {
// 1 对 1 或 多 对 1 情况currentIds 当前要跳转的节点列表(1或多)targetIds.get(0) 跳转到的节点(1)
// 如果驳回后的目标任务包含指定人,则直接通过变量回抄,如果没有则自动忽略该变量,不会给流程带来任何影响。
ChangeActivityStateBuilder builder = runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0));
FlowTaskComment taskComment =
flowTaskCommentService.getLatestFlowTaskComment(task.getProcessInstanceId(), targetIds.get(0));
String submitLoginName = taskComment.getCreateLoginName();
if (StrUtil.isNotBlank(submitLoginName)) {
builder.localVariable(targetIds.get(0), FlowConstant.TASK_APPOINTED_ASSIGNEE_VAR, submitLoginName);
}
builder.changeState();
}
}
FlowTaskComment comment = new FlowTaskComment();
comment.setTaskId(task.getId());
@@ -1091,15 +1227,15 @@ public class FlowApiServiceImpl implements FlowApiService {
}
String roleIds = tokenData.getRoleIds();
if (StrUtil.isNotBlank(tokenData.getRoleIds())) {
groupIdSet.addAll(Arrays.asList(StrUtil.split(roleIds, ",")));
groupIdSet.addAll(StrUtil.split(roleIds, ","));
}
String postIds = tokenData.getPostIds();
if (StrUtil.isNotBlank(tokenData.getPostIds())) {
groupIdSet.addAll(Arrays.asList(StrUtil.split(postIds, ",")));
groupIdSet.addAll(StrUtil.split(postIds, ","));
}
String deptPostIds = tokenData.getDeptPostIds();
if (StrUtil.isNotBlank(deptPostIds)) {
groupIdSet.addAll(Arrays.asList(StrUtil.split(deptPostIds, ",")));
groupIdSet.addAll(StrUtil.split(deptPostIds, ","));
}
if (CollUtil.isNotEmpty(groupIdSet)) {
query.or().taskCandidateGroupIn(groupIdSet).taskCandidateOrAssigned(loginName).endOr();

View File

@@ -3,7 +3,7 @@ package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.orangeforms.common.flow.object.FlowTaskPostCandidateGroup;
@@ -147,7 +147,7 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
} else if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_POST)) {
Assert.notNull(t.getDeptPostListJson());
List<FlowTaskPostCandidateGroup> groupDataList =
JSONArray.parseArray(t.getDeptPostListJson(), FlowTaskPostCandidateGroup.class);
JSON.parseArray(t.getDeptPostListJson(), FlowTaskPostCandidateGroup.class);
List<String> candidateGroupList =
FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList);
userTask.setCandidateGroups(candidateGroupList);
@@ -176,7 +176,7 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
FlowEntry updatedFlowEntry = new FlowEntry();
updatedFlowEntry.setEntryId(flowEntry.getEntryId());
updatedFlowEntry.setStatus(FlowEntryStatus.PUBLISHED);
updatedFlowEntry.setLastestPublishTime(new Date());
updatedFlowEntry.setLatestPublishTime(new Date());
// 对于从未发布过的工作,第一次发布的时候会将本地发布置位主版本。
if (flowEntry.getStatus().equals(FlowEntryStatus.UNPUBLISHED)) {
updatedFlowEntry.setMainEntryPublishId(flowEntryPublish.getEntryPublishId());

View File

@@ -3,26 +3,31 @@ package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.base.service.BaseService;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.flow.constant.FlowConstant;
import com.orangeforms.common.flow.model.*;
import com.orangeforms.common.flow.model.constant.FlowMessageOperationType;
import com.orangeforms.common.flow.model.constant.FlowMessageType;
import com.orangeforms.common.flow.dao.FlowMessageIdentityOperationMapper;
import com.orangeforms.common.flow.dao.FlowMessageCandidateIdentityMapper;
import com.orangeforms.common.flow.dao.FlowMessageMapper;
import com.orangeforms.common.flow.model.FlowMessage;
import com.orangeforms.common.flow.model.FlowMessageCandidateIdentity;
import com.orangeforms.common.flow.model.FlowTaskExt;
import com.orangeforms.common.flow.model.FlowWorkOrder;
import com.orangeforms.common.flow.object.FlowTaskPostCandidateGroup;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowMessageService;
import com.orangeforms.common.flow.service.FlowTaskExtService;
import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.util.BaseBusinessDataExtHelper;
import com.orangeforms.common.flow.vo.TaskInfoVo;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -45,10 +50,14 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
@Autowired
private FlowMessageCandidateIdentityMapper flowMessageCandidateIdentityMapper;
@Autowired
private FlowMessageIdentityOperationMapper flowMessageIdentityOperationMapper;
@Autowired
private FlowTaskExtService flowTaskExtService;
@Autowired
private FlowApiService flowApiService;
@Autowired
private FlowCustomExtFactory flowCustomExtFactory;
@Autowired
private IdGeneratorWrapper idGenerator;
/**
@@ -111,9 +120,51 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(
flowWorkOrder.getProcessDefinitionId(), task.getTaskDefinitionKey());
if (flowTaskExt != null) {
// 插入与当前消息关联任务的候选人
this.saveMessageCandidateIdentityWithMessage(
flowWorkOrder.getProcessInstanceId(), flowTaskExt, flowMessage.getMessageId());
}
// 插入与当前消息关联任务的指派人。
if (StrUtil.isNotBlank(task.getAssignee())) {
this.saveMessageCandidateIdentity(
flowMessage.getMessageId(), FlowConstant.GROUP_TYPE_USER_VAR, task.getAssignee());
}
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveNewCopyMessage(Task task, JSONObject copyDataJson) {
ProcessInstance instance = flowApiService.getProcessInstance(task.getProcessInstanceId());
BaseBusinessDataExtHelper helper = flowCustomExtFactory.getBusinessDataExtHelper();
// 在线表单中,这个值为空。
String businessShotData = helper.getBusinessData(
instance.getProcessDefinitionKey(), instance.getProcessInstanceId(), instance.getBusinessKey());
FlowMessage flowMessage = new FlowMessage();
flowMessage.setMessageType(FlowMessageType.COPY_TYPE);
flowMessage.setRemindCount(0);
flowMessage.setProcessDefinitionId(instance.getProcessDefinitionId());
flowMessage.setProcessDefinitionKey(instance.getProcessDefinitionKey());
flowMessage.setProcessDefinitionName(instance.getProcessDefinitionName());
flowMessage.setProcessInstanceId(instance.getProcessInstanceId());
flowMessage.setProcessInstanceInitiator(instance.getStartUserId());
flowMessage.setTaskId(task.getId());
flowMessage.setTaskDefinitionKey(task.getTaskDefinitionKey());
flowMessage.setTaskName(task.getName());
flowMessage.setTaskStartTime(task.getCreateTime());
flowMessage.setTaskAssignee(task.getAssignee());
flowMessage.setTaskFinished(false);
flowMessage.setBusinessDataShot(businessShotData);
flowMessage.setOnlineFormData(businessShotData == null);
// 如果是在线表单这里就保存关联的在线表单Id便于在线表单业务数据的查找。
if (flowMessage.getOnlineFormData()) {
TaskInfoVo taskInfo = JSON.parseObject(task.getFormKey(), TaskInfoVo.class);
flowMessage.setBusinessDataShot(taskInfo.getFormId().toString());
}
this.saveNew(flowMessage);
for (Map.Entry<String, Object> entries : copyDataJson.entrySet()) {
this.saveMessageCandidateIdentityList(
flowMessage.getMessageId(), entries.getKey(), entries.getValue().toString());
}
}
@@ -139,6 +190,59 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
@Override
public List<FlowMessage> getRemindingMessageListByUser() {
return flowMessageMapper.getRemindingMessageListByUser(
TokenData.takeFromRequest().getLoginName(), buildGroupIdSet());
}
@Override
public List<FlowMessage> getCopyMessageListByUser(Boolean read) {
return flowMessageMapper.getCopyMessageListByUser(
TokenData.takeFromRequest().getLoginName(), buildGroupIdSet(), read);
}
@Override
public boolean isCandidateIdentityOnMessage(Long messageId) {
LambdaQueryWrapper<FlowMessageCandidateIdentity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowMessageCandidateIdentity::getMessageId, messageId);
queryWrapper.in(FlowMessageCandidateIdentity::getCandidateId, buildGroupIdSet());
return flowMessageCandidateIdentityMapper.selectCount(queryWrapper) > 0;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void readCopyTask(Long messageId) {
FlowMessageIdentityOperation operation = new FlowMessageIdentityOperation();
operation.setId(idGenerator.nextLongId());
operation.setMessageId(messageId);
operation.setLoginName(TokenData.takeFromRequest().getLoginName());
operation.setOperationType(FlowMessageOperationType.READ_FINISHED);
operation.setOperationTime(new Date());
flowMessageIdentityOperationMapper.insert(operation);
}
@Override
public int countRemindingMessageListByUser() {
return flowMessageMapper.countRemindingMessageListByUser(
TokenData.takeFromRequest().getLoginName(), buildGroupIdSet());
}
@Override
public int countCopyMessageByUser() {
return flowMessageMapper.countCopyMessageListByUser(
TokenData.takeFromRequest().getLoginName(), buildGroupIdSet());
}
@Transactional(rollbackFor = Exception.class)
@Override
public void removeByProcessInstanceId(String processInstanceId) {
flowMessageCandidateIdentityMapper.deleteByProcessInstanceId(processInstanceId);
flowMessageIdentityOperationMapper.deleteByProcessInstanceId(processInstanceId);
LambdaQueryWrapper<FlowMessage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowMessage::getProcessInstanceId, processInstanceId);
flowMessageMapper.delete(queryWrapper);
}
private Set<String> buildGroupIdSet() {
TokenData tokenData = TokenData.takeFromRequest();
Set<String> groupIdSet = new HashSet<>(1);
groupIdSet.add(tokenData.getLoginName());
@@ -148,7 +252,7 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
if (tokenData.getDeptId() != null) {
groupIdSet.add(tokenData.getDeptId().toString());
}
return flowMessageMapper.getRemindingMessageListByUser(tokenData.getLoginName(), groupIdSet);
return groupIdSet;
}
private void parseAndAddIdArray(Set<String> groupIdSet, String idArray) {
@@ -163,11 +267,11 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
private void saveMessageCandidateIdentityWithMessage(
String processInstanceId, FlowTaskExt flowTaskExt, Long messageId) {
this.saveMessageCandidateIdentityList(
messageId, "username", flowTaskExt.getCandidateUsernames());
messageId, FlowConstant.GROUP_TYPE_USER_VAR, flowTaskExt.getCandidateUsernames());
this.saveMessageCandidateIdentityList(
messageId, "role", flowTaskExt.getRoleIds());
messageId, FlowConstant.GROUP_TYPE_ROLE_VAR, flowTaskExt.getRoleIds());
this.saveMessageCandidateIdentityList(
messageId, "dept", flowTaskExt.getDeptIds());
messageId, FlowConstant.GROUP_TYPE_DEPT_VAR, flowTaskExt.getDeptIds());
if (StrUtil.equals(flowTaskExt.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) {
Object v = flowApiService.getProcessInstanceVariable(
processInstanceId, FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR);
@@ -227,7 +331,7 @@ public class FlowMessageServiceImpl extends BaseService<FlowMessage, Long> imple
FlowMessageCandidateIdentity candidateIdentity = new FlowMessageCandidateIdentity();
candidateIdentity.setId(idGenerator.nextLongId());
candidateIdentity.setMessageId(messageId);
candidateIdentity.setCandidateType(FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR);
candidateIdentity.setCandidateType(candidateType);
candidateIdentity.setCandidateId(candidateId);
flowMessageCandidateIdentityMapper.insert(candidateIdentity);
}

View File

@@ -55,6 +55,7 @@ public class FlowTaskCommentServiceImpl extends BaseService<FlowTaskComment, Lon
flowTaskComment.setId(idGenerator.nextLongId());
TokenData tokenData = TokenData.takeFromRequest();
flowTaskComment.setCreateUserId(tokenData.getUserId());
flowTaskComment.setCreateLoginName(tokenData.getLoginName());
flowTaskComment.setCreateUsername(tokenData.getShowName());
flowTaskComment.setCreateTime(new Date());
flowTaskCommentMapper.insert(flowTaskComment);
@@ -69,8 +70,8 @@ public class FlowTaskCommentServiceImpl extends BaseService<FlowTaskComment, Lon
*/
@Override
public List<FlowTaskComment> getFlowTaskCommentList(String processInstanceId) {
LambdaQueryWrapper<FlowTaskComment> queryWrapper =
new LambdaQueryWrapper<FlowTaskComment>().eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
LambdaQueryWrapper<FlowTaskComment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
queryWrapper.orderByAsc(FlowTaskComment::getId);
return flowTaskCommentMapper.selectList(queryWrapper);
}
@@ -85,10 +86,29 @@ public class FlowTaskCommentServiceImpl extends BaseService<FlowTaskComment, Lon
@Override
public FlowTaskComment getLatestFlowTaskComment(String processInstanceId) {
LambdaQueryWrapper<FlowTaskComment> queryWrapper =
new LambdaQueryWrapper<FlowTaskComment>().eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
LambdaQueryWrapper<FlowTaskComment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
queryWrapper.orderByDesc(FlowTaskComment::getId);
IPage<FlowTaskComment> pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper);
return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0);
}
@Override
public FlowTaskComment getLatestFlowTaskComment(String processInstanceId, String taskDefinitionKey) {
LambdaQueryWrapper<FlowTaskComment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
queryWrapper.eq(FlowTaskComment::getTaskKey, taskDefinitionKey);
queryWrapper.orderByDesc(FlowTaskComment::getId);
IPage<FlowTaskComment> pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper);
return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0);
}
@Override
public FlowTaskComment getFirstFlowTaskComment(String processInstanceId) {
LambdaQueryWrapper<FlowTaskComment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowTaskComment::getProcessInstanceId, processInstanceId);
queryWrapper.orderByAsc(FlowTaskComment::getId);
IPage<FlowTaskComment> pageData = flowTaskCommentMapper.selectPage(new Page<>(1, 1), queryWrapper);
return CollUtil.isEmpty(pageData.getRecords()) ? null : pageData.getRecords().get(0);
}
}

View File

@@ -1,9 +1,12 @@
package com.orangeforms.common.flow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.orangeforms.common.core.base.dao.BaseDaoMapper;
import com.orangeforms.common.core.constant.GlobalDeletedFlag;
import com.orangeforms.common.core.object.GlobalThreadLocal;
import com.orangeforms.common.core.object.MyRelationParam;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.base.service.BaseService;
@@ -11,6 +14,9 @@ import com.orangeforms.common.flow.constant.FlowTaskStatus;
import com.orangeforms.common.flow.dao.FlowWorkOrderMapper;
import com.orangeforms.common.flow.model.FlowWorkOrder;
import com.orangeforms.common.flow.service.FlowWorkOrderService;
import com.orangeforms.common.flow.util.BaseFlowIdentityExtHelper;
import com.orangeforms.common.flow.util.FlowCustomExtFactory;
import com.orangeforms.common.flow.vo.FlowWorkOrderVo;
import com.orangeforms.common.sequence.wrapper.IdGeneratorWrapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
@@ -19,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* 工作流工单表数据操作服务类。
@@ -34,6 +41,8 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
private FlowWorkOrderMapper flowWorkOrderMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private FlowCustomExtFactory flowCustomExtFactory;
/**
* 返回当前Service的主表Mapper对象。
@@ -144,4 +153,36 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
queryWrapper.eq(FlowWorkOrder::getProcessInstanceId, processInstanceId);
flowWorkOrderMapper.update(flowWorkOrder, queryWrapper);
}
@Override
public boolean hasDataPermOnFlowWorkOrder(String processInstanceId) {
// 开启数据权限,并进行验证。
boolean originalFlag = GlobalThreadLocal.setDataFilter(true);
int count;
try {
FlowWorkOrder filter = new FlowWorkOrder();
filter.setProcessInstanceId(processInstanceId);
count = flowWorkOrderMapper.selectCount(new QueryWrapper<>(filter));
} finally {
// 恢复之前的数据权限标记
GlobalThreadLocal.setDataFilter(originalFlag);
}
return count > 0;
}
@Override
public void fillUserShowNameByLoginName(List<FlowWorkOrderVo> dataList) {
BaseFlowIdentityExtHelper identityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
Set<String> loginNameSet = dataList.stream()
.map(FlowWorkOrderVo::getSubmitUsername).collect(Collectors.toSet());
if (CollUtil.isEmpty(loginNameSet)) {
return;
}
Map<String, String> userNameMap = identityExtHelper.mapUserShowNameByLoginName(loginNameSet);
dataList.forEach(workOrder -> {
if (StrUtil.isNotBlank(workOrder.getSubmitUsername())) {
workOrder.setUserShowName(userNameMap.get(workOrder.getSubmitUsername()));
}
});
}
}

View File

@@ -9,16 +9,13 @@ import java.util.HashMap;
import java.util.Map;
/**
* 工作流实例执行结束之后需要需要做业务表的数据同步可实现该接口
* 该插件通常用于一张表单多次提交的场景为了避免修改后的审批中数据由于尚未通过审批而此时依赖该业务作为关联表时
* 关联到的数据往往是尚未通过审批的脏数据因此需要做审批表和发布表的数据隔离仅当审批流程完全结束且通过审批后
* 将审批表及其一对一一对多多对多关联表中的数据同步到发布表及其关联表中至于具体需要同步那些表数据需按需求而定
* 工作流业务数据扩展帮助实现类
*
* @author Jerry
* @date 2021-06-06
*/
@Slf4j
public class BaseDataSyncExtHelper {
public class BaseBusinessDataExtHelper {
private Map<String, BaseFlowService> serviceMap = new HashMap<>();
@@ -44,16 +41,29 @@ public class BaseDataSyncExtHelper {
*/
public void triggerSync(String processDefinitionKey, String processInstanceId, String businessKey) {
BaseFlowService service = serviceMap.get(processDefinitionKey);
if (service != null) {
if (service != null && service.supportSyncBusinessData()) {
try {
service.doSyncBusinessData(processInstanceId, businessKey);
service.syncBusinessData(processInstanceId, businessKey);
} catch (Exception e) {
String errorMessage = String.format(
"Failed to call doSyncBusinessData with processDefinitionKey {%s}, businessKey {%s}",
"Failed to call syncBusinessData with processDefinitionKey {%s}, businessKey {%s}",
processDefinitionKey, businessKey);
log.error(errorMessage, e);
throw e;
}
}
}
/**
* 获取详细的业务数据包括主表一对一一对多多对多从表及其字典数据
*
* @param processDefinitionKey 流程定义标识
* @param processInstanceId 流程实例Id
* @param businessKey 业务主表的主键Id
* @return JSON格式化后的业务数据
*/
public String getBusinessData(String processDefinitionKey, String processInstanceId, String businessKey) {
BaseFlowService service = serviceMap.get(processDefinitionKey);
return service == null ? null : service.getBusinessData(processInstanceId, businessKey);
}
}

View File

@@ -4,6 +4,7 @@ import com.orangeforms.common.flow.listener.DeptPostLeaderListener;
import com.orangeforms.common.flow.listener.UpDeptPostLeaderListener;
import org.flowable.engine.delegate.TaskListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -140,6 +141,25 @@ public interface BaseFlowIdentityExtHelper {
return null;
}
/**
* 当前服务是否支持数据权限。
*
* @return true表示支持否则false。
*/
default Boolean supprtDataPerm() {
return false;
}
/**
* 映射用户的登录名到用户的显示名。
*
* @param loginNameSet 用户登录名集合。
* @return 用户登录名和显示名的Mapkey为登录名value是显示名。
*/
default Map<String, String> mapUserShowNameByLoginName(Set<String> loginNameSet) {
return new HashMap<>(1);
}
/**
* 获取任务执行人是当前部门领导岗位的任务监听器。
* 通常会在没有找到领导部门岗位Id的时候为当前任务指定其他的指派人、候选人或候选组。

View File

@@ -13,7 +13,7 @@ public class FlowCustomExtFactory {
private BaseFlowIdentityExtHelper flowIdentityExtHelper;
private BaseDataSyncExtHelper dataSyncExtHelper = new BaseDataSyncExtHelper();
private BaseBusinessDataExtHelper businessDataExtHelper = new BaseBusinessDataExtHelper();
/**
* 获取业务模块自行实现的用户身份相关的扩展帮助实现类。
@@ -34,11 +34,11 @@ public class FlowCustomExtFactory {
}
/**
* 获取流程结束后数据同步的帮助实现类。
* 获取有关业务数据的扩展帮助实现类。
*
* @return 流程结束后数据同步的帮助实现类。
* @return 有关业务数据的扩展帮助实现类。
*/
public BaseDataSyncExtHelper getDataSyncExtHelper() {
return dataSyncExtHelper;
public BaseBusinessDataExtHelper getBusinessDataExtHelper() {
return businessDataExtHelper;
}
}

View File

@@ -22,6 +22,7 @@ import com.orangeforms.common.flow.model.FlowWorkOrder;
import com.orangeforms.common.flow.model.constant.FlowEntryStatus;
import com.orangeforms.common.flow.service.FlowApiService;
import com.orangeforms.common.flow.service.FlowEntryService;
import com.orangeforms.common.flow.service.FlowWorkOrderService;
import com.orangeforms.common.flow.vo.FlowWorkOrderVo;
import com.orangeforms.common.flow.vo.TaskInfoVo;
import lombok.extern.slf4j.Slf4j;
@@ -53,6 +54,10 @@ public class FlowOperationHelper {
private FlowEntryService flowEntryService;
@Autowired
private FlowApiService flowApiService;
@Autowired
private FlowWorkOrderService flowWorkOrderService;
@Autowired
private FlowCustomExtFactory flowCustomExtFactory;
/**
* 验证并获取流程对象。
@@ -157,8 +162,10 @@ public class FlowOperationHelper {
String loginName = TokenData.takeFromRequest().getLoginName();
if (StrUtil.isBlank(taskId)) {
if (!StrUtil.equals(loginName, instance.getStartUserId())) {
errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
if (!flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) {
errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配,或者没有查看该工单详情的数据权限!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
} else {
HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId);
@@ -167,8 +174,10 @@ public class FlowOperationHelper {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (!StrUtil.equals(loginName, taskInstance.getAssignee())) {
errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
if (!flowWorkOrderService.hasDataPermOnFlowWorkOrder(processInstanceId)) {
errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配,或者没有查看该工单详情的数据权限!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
}
return ResponseResult.success(instance);
@@ -269,7 +278,13 @@ public class FlowOperationHelper {
filter = new FlowWorkOrder();
}
filter.setProcessDefinitionKey(processDefinitionKey);
filter.setCreateUserId(TokenData.takeFromRequest().getUserId());
// 下面的方法会帮助构建工单的数据权限过滤条件,和业务希望相比,如果当前系统没有支持数据权限,
// 用户则只能看到自己发起的工单,否则按照数据权限过滤。然而需要特殊处理的是,如果用户的数据
// 权限中,没有包含能看自己,这里也需要自动给加上。
BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
if (!flowIdentityExtHelper.supprtDataPerm()) {
filter.setCreateUserId(TokenData.takeFromRequest().getUserId());
}
return filter;
}

View File

@@ -52,7 +52,7 @@ public class FlowEntryVo {
* 最新发布时间。
*/
@ApiModelProperty(value = "最新发布时间")
private Date lastestPublishTime;
private Date latestPublishTime;
/**
* 流程状态。

View File

@@ -64,12 +64,24 @@ public class FlowTaskCommentVo {
@ApiModelProperty(value = "委托指定人,比如加签、转办等")
private String delegateAssginee;
/**
* 自定义数据。开发者可自行扩展推荐使用JSON格式数据。
*/
@ApiModelProperty(value = "自定义数据s")
private String customBusinessData;
/**
* 创建者Id。
*/
@ApiModelProperty(value = "创建者Id")
private Long createUserId;
/**
* 创建者登录名。
*/
@ApiModelProperty(value = "创建者登录名")
private String createLoginName;
/**
* 创建者显示名。
*/

View File

@@ -108,6 +108,12 @@ public class FlowWorkOrderVo {
@ApiModelProperty(value = "flowStatus 常量字典关联数据")
private Map<String, Object> flowStatusDictMap;
/**
* 用户的显示名。
*/
@ApiModelProperty(value = "用户的显示名")
private String userShowName;
/**
* FlowEntryPublish对象中的同名字段。
*/

View File

@@ -35,6 +35,10 @@ public final class SysOperationLogType {
* 删除。
*/
public static final int DELETE = 20;
/**
* 批量删除。
*/
public static final int DELETE_BATCH = 21;
/**
* 新增多对多关联。
*/
@@ -43,6 +47,10 @@ public final class SysOperationLogType {
* 移除多对多关联。
*/
public static final int DELETE_M2M = 30;
/**
* 批量移除多对多关联。
*/
public static final int DELETE_M2M_BATCH = 31;
/**
* 查询。
*/
@@ -55,6 +63,10 @@ public final class SysOperationLogType {
* 导出。
*/
public static final int EXPORT = 45;
/**
* 导入。
*/
public static final int IMPORT = 46;
/**
* 上传。
*/

View File

@@ -1,5 +1,6 @@
package com.orangeforms.common.online.config;
import com.orangeforms.common.core.config.CoreProperties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -13,16 +14,18 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "common-online")
public class OnlineProperties {
/**
* 数据库类型。
*/
private String databaseType = CoreProperties.MYSQL_TYPE;
/**
* 仅以该前缀开头的数据表才会成为动态表单的候选数据表,如: zz_。如果为空则所有表均可被选。
*/
private String tablePrefix;
/**
* 在线表单业务操作的URL前缀。
*/
private String operationUrlPrefix;
/**
* 上传文件的根路径。
*/

View File

@@ -42,7 +42,7 @@ public interface OnlineDblinkMapper extends BaseDaoMapper<OnlineDblink> {
+ "WHERE "
+ " table_schema = (SELECT database()) "
+ " <if test=\"prefix != null and prefix != ''\">"
+ " AND table_name like '${prefix}%'"
+ " AND table_name LIKE '${prefix}%'"
+ " </if>"
+ "</script>")
List<Map<String, Object>> getTableListWithPrefix(@Param("prefix") String prefix);
@@ -59,7 +59,9 @@ public interface OnlineDblinkMapper extends BaseDaoMapper<OnlineDblink> {
" create_time createTime \n" +
"FROM \n" +
" information_schema.tables \n" +
"WHERE table_schema = (SELECT database()) AND table_name = #{tableName}")
"WHERE \n" +
" table_schema = (SELECT database()) \n" +
" AND table_name = #{tableName}")
Map<String, Object> getTableByName(@Param("tableName") String tableName);
/**
@@ -80,9 +82,13 @@ public interface OnlineDblinkMapper extends BaseDaoMapper<OnlineDblink> {
" CHARACTER_MAXIMUM_LENGTH stringPrecision, \n" +
" numeric_precision numericPrecision, \n" +
" COLUMN_DEFAULT columnDefault \n" +
"FROM information_schema.columns \n" +
"WHERE table_name = #{tableName} \n" +
" AND table_schema = (SELECT database()) ORDER BY ordinal_position")
"FROM \n" +
" information_schema.columns \n" +
"WHERE \n" +
" table_name = #{tableName} \n" +
" AND table_schema = (SELECT database()) \n" +
"ORDER BY \n" +
" ordinal_position")
List<Map<String, Object>> getTableColumnList(@Param("tableName") String tableName);
/**
@@ -104,10 +110,12 @@ public interface OnlineDblinkMapper extends BaseDaoMapper<OnlineDblink> {
" CHARACTER_MAXIMUM_LENGTH stringPrecision, \n" +
" numeric_precision numericPrecision, \n" +
" COLUMN_DEFAULT columnDefault \n" +
"FROM information_schema.columns \n" +
"WHERE table_name = #{tableName} \n" +
"FROM \n" +
" information_schema.columns \n" +
"WHERE \n" +
" table_name = #{tableName} \n" +
" AND column_name = #{columnName} \n" +
" AND table_schema = (SELECT database()) ORDER BY ordinal_position")
" AND table_schema = (SELECT database())")
Map<String, Object> getTableColumnByName(
@Param("tableName") String tableName, @Param("columnName") String columnName);
}

View File

@@ -19,9 +19,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -129,7 +127,6 @@ public class OnlineDblinkServiceImpl extends BaseService<OnlineDblink, Long> imp
}
/**
* X
* 获取指定DBLink下指定表名的数据表对象及其关联字段列表。
*
* @param dblink 数据库链接对象。

View File

@@ -7,6 +7,7 @@ import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.orangeforms.common.core.annotation.MyDataSourceResolver;
import com.orangeforms.common.core.config.CoreProperties;
import com.orangeforms.common.core.constant.AggregationType;
import com.orangeforms.common.core.exception.NoDataPermException;
import com.orangeforms.common.core.constant.GlobalDeletedFlag;
@@ -16,6 +17,7 @@ import com.orangeforms.common.core.object.Tuple2;
import com.orangeforms.common.core.util.RedisKeyUtil;
import com.orangeforms.common.datafilter.constant.DataPermRuleType;
import com.orangeforms.common.datafilter.config.DataFilterProperties;
import com.orangeforms.common.online.config.OnlineProperties;
import com.orangeforms.common.online.model.constant.*;
import com.orangeforms.common.online.service.OnlineVirtualColumnService;
import com.orangeforms.common.online.util.OnlineOperationHelper;
@@ -68,6 +70,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
private RedissonClient redissonClient;
@Autowired
private DataFilterProperties dataFilterProperties;
@Autowired
private OnlineProperties onlineProperties;
/**
* 聚合返回数据中,聚合键的常量字段名。
@@ -108,7 +112,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
List<ColumnData> columnDataList,
Map<OnlineDatasourceRelation, List<List<ColumnData>>> slaveDataListMap) {
Object id = this.saveNew(masterTable, columnDataList);
// 迭代多个一对多关联。
// 迭代多个关联列表
for (Map.Entry<OnlineDatasourceRelation, List<List<ColumnData>>> entry : slaveDataListMap.entrySet()) {
Long masterColumnId = entry.getKey().getMasterColumnId();
ColumnData masterColumnData = null;
@@ -119,9 +123,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
}
Long slaveColumnId = entry.getKey().getSlaveColumnId();
// 迭代一对多关联中的数据集合
// 迭代关联中的数据集合
for (List<ColumnData> slaveColumnDataList : entry.getValue()) {
// 迭代一对多关联记录的字段列表。
// 迭代关联记录的字段列表。
for (ColumnData slaveColumnData : slaveColumnDataList) {
if (slaveColumnData.getColumn().getColumnId().equals(slaveColumnId)) {
slaveColumnData.setColumnValue(masterColumnData.getColumnValue());
@@ -642,6 +646,10 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
private String makeSelectFields(OnlineTable slaveTable, String relationVariableName) {
StringBuilder selectFieldBuider = new StringBuilder(512);
String intString = "SIGNED";
if (onlineProperties.getDatabaseType().equals(CoreProperties.POSTGRESQL_TYPE)) {
intString = "INT8";
}
// 拼装主表的select fields字段。
for (OnlineColumn column : slaveTable.getColumnMap().values()) {
OnlineColumn deletedColumn = slaveTable.getLogicDeleteColumn();
@@ -654,7 +662,20 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
.append(slaveTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS SIGNED) ")
.append(" AS ")
.append(intString)
.append(") ")
.append(relationVariableName)
.append(OnlineConstant.RELATION_TABLE_COLUMN_SEPARATOR)
.append(column.getColumnName())
.append(",");
} else if ("date".equals(column.getColumnType())) {
selectFieldBuider
.append("CAST(")
.append(slaveTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS CHAR(10)) ")
.append(relationVariableName)
.append(OnlineConstant.RELATION_TABLE_COLUMN_SEPARATOR)
.append(column.getColumnName())
@@ -676,6 +697,10 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
private String makeSelectFields(OnlineTable masterTable, List<OnlineDatasourceRelation> relationList) {
StringBuilder selectFieldBuider = new StringBuilder(512);
String intString = "SIGNED";
if (onlineProperties.getDatabaseType().equals(CoreProperties.POSTGRESQL_TYPE)) {
intString = "INT8";
}
if (CollUtil.isNotEmpty(relationList)) {
for (OnlineDatasourceRelation relation : relationList) {
OnlineTable slaveTable = relation.getSlaveTable();
@@ -691,7 +716,20 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
.append(slaveTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS SIGNED) ")
.append(" AS ")
.append(intString)
.append(") ")
.append(relation.getVariableName())
.append(OnlineConstant.RELATION_TABLE_COLUMN_SEPARATOR)
.append(column.getColumnName())
.append(",");
} else if ("date".equals(column.getColumnType())) {
selectFieldBuider
.append("CAST(")
.append(slaveTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS CHAR(10)) ")
.append(relation.getVariableName())
.append(OnlineConstant.RELATION_TABLE_COLUMN_SEPARATOR)
.append(column.getColumnName())
@@ -722,7 +760,18 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
.append(masterTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS SIGNED) ")
.append(" AS ")
.append(intString)
.append(") ")
.append(column.getColumnName())
.append(",");
} else if ("date".equals(column.getColumnType())) {
selectFieldBuider
.append("CAST(")
.append(masterTable.getTableName())
.append(".")
.append(column.getColumnName())
.append(" AS CHAR(10)) ")
.append(column.getColumnName())
.append(",");
} else {

View File

@@ -213,9 +213,19 @@ public class OnlineOperationHelper {
List<List<ColumnData>> relationDataList = new LinkedList<>();
relationDataMap.put(relation, relationDataList);
OnlineTable slaveTable = relation.getSlaveTable();
JSONArray slaveObjectArray = slaveData.getJSONArray(key);
for (int i = 0; i < slaveObjectArray.size(); i++) {
JSONObject slaveObject = slaveObjectArray.getJSONObject(i);
if (relation.getRelationType().equals(RelationType.ONE_TO_MANY)) {
JSONArray slaveObjectArray = slaveData.getJSONArray(key);
for (int i = 0; i < slaveObjectArray.size(); i++) {
JSONObject slaveObject = slaveObjectArray.getJSONObject(i);
ResponseResult<List<ColumnData>> slaveColumnDataListResult =
this.buildTableData(slaveTable, slaveObject, false, relation.getSlaveColumnId());
if (!slaveColumnDataListResult.isSuccess()) {
return ResponseResult.errorFrom(slaveColumnDataListResult);
}
relationDataList.add(slaveColumnDataListResult.getData());
}
} else if (relation.getRelationType().equals(RelationType.ONE_TO_ONE)) {
JSONObject slaveObject = slaveData.getJSONObject(key);
ResponseResult<List<ColumnData>> slaveColumnDataListResult =
this.buildTableData(slaveTable, slaveObject, false, relation.getSlaveColumnId());
if (!slaveColumnDataListResult.isSuccess()) {

View File

@@ -28,6 +28,10 @@ import java.util.stream.Collectors;
@Slf4j
public class RedisDictionaryCache<K, V> implements DictionaryCache<K, V> {
/**
* 字典数据前缀便于Redis工具分组显示。
*/
protected static final String DICT_PREFIX = "DICT-TABLE:";
/**
* redisson客户端。
*/
@@ -89,7 +93,8 @@ public class RedisDictionaryCache<K, V> implements DictionaryCache<K, V> {
Class<V> valueClazz,
Function<V, K> idGetter) {
this.redissonClient = redissonClient;
this.dataMap = redissonClient.getMap(dictionaryName + ApplicationConstant.DICT_CACHE_NAME_SUFFIX);
this.dataMap = redissonClient.getMap(
DICT_PREFIX + dictionaryName + ApplicationConstant.DICT_CACHE_NAME_SUFFIX);
this.lock = new ReentrantReadWriteLock();
this.valueClazz = valueClazz;
this.idGetter = idGetter;

View File

@@ -81,7 +81,7 @@ public class RedisTreeDictionaryCache<K, V> extends RedisDictionaryCache<K, V> {
Function<V, K> parentIdGetter) {
super(redissonClient, dictionaryName, valueClazz, idGetter);
this.allTreeMap = redissonClient.getListMultimap(
dictionaryName + ApplicationConstant.TREE_DICT_CACHE_NAME_SUFFIX);
DICT_PREFIX + dictionaryName + ApplicationConstant.TREE_DICT_CACHE_NAME_SUFFIX);
this.parentIdGetter = parentIdGetter;
}