mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 18:46:36 +08:00
commit:同步2.2版本
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,12 @@ public @interface EnableDataPerm {
|
||||
* @return 被排序的方法名称数据。
|
||||
*/
|
||||
String[] excluseMethodName() default {};
|
||||
|
||||
/**
|
||||
* 必须包含能看用户自己数据的数据过滤条件,如果当前用户的数据过滤中,没有DataPermRuleType.TYPE_USER_ONLY,
|
||||
* 在进行数据权限过滤时,会自动包含该权限。
|
||||
*
|
||||
* @return 是否必须包含DataPermRuleType.TYPE_USER_ONLY类型的数据权限。
|
||||
*/
|
||||
boolean mustIncludeUserRule() default false;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* 集成所有与主表实体对象相关的关联数据列表。包括一对一、字典、一对多和多对多聚合运算等。
|
||||
* 也可以根据实际需求,单独调用该函数所包含的各个数据集成函数。
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 2020-09-24
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,10 @@ public class TokenData {
|
||||
* 登录时间。
|
||||
*/
|
||||
private Date loginTime;
|
||||
/**
|
||||
* 登录头像地址。
|
||||
*/
|
||||
private String headImageUrl;
|
||||
|
||||
/**
|
||||
* 将令牌对象添加到Http请求对象。
|
||||
|
||||
@@ -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 2020-09-24
|
||||
*/
|
||||
@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() {
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
/**
|
||||
* 缺省日期格式化器,提前获取提升运行时效率。
|
||||
*/
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.orangeforms.common.core.util;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.orangeforms.common.core.exception.InvalidDataFieldException;
|
||||
import com.orangeforms.common.core.annotation.*;
|
||||
import com.orangeforms.common.core.exception.MyRuntimeException;
|
||||
import com.orangeforms.common.core.object.TokenData;
|
||||
import com.orangeforms.common.core.object.Tuple2;
|
||||
import com.orangeforms.common.core.upload.UploadResponseInfo;
|
||||
import com.orangeforms.common.core.upload.UploadStoreInfo;
|
||||
import com.google.common.base.CaseFormat;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -64,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;
|
||||
}
|
||||
|
||||
@@ -707,6 +723,46 @@ public class MyModelUtil {
|
||||
ReflectUtil.setFieldValue(data, fieldName, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据对象中,所有上传文件字段的数据,并将上传后的文件名存到集合中并返回。
|
||||
*
|
||||
* @param data 数据对象。
|
||||
* @param clazz 数据对象的Class类型。
|
||||
* @param <M> 数据对象类型。
|
||||
* @return 当前数据对象中,所有上传文件字段中,文件名属性的集合。
|
||||
*/
|
||||
public static <M> Set<String> extractDownloadFileName(M data, Class<M> clazz) {
|
||||
Set<String> resultSet = new HashSet<>();
|
||||
Field[] fields = ReflectUtil.getFields(clazz);
|
||||
for (Field field : fields) {
|
||||
if (field.isAnnotationPresent(UploadFlagColumn.class)) {
|
||||
String v = (String) ReflectUtil.getFieldValue(data, field);
|
||||
List<UploadResponseInfo> fileInfoList = JSON.parseArray(v, UploadResponseInfo.class);
|
||||
if (CollectionUtils.isNotEmpty(fileInfoList)) {
|
||||
fileInfoList.forEach(fileInfo -> resultSet.add(fileInfo.getFilename()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据对象列表中,所有上传文件字段的数据,并将上传后的文件名存到集合中并返回。
|
||||
*
|
||||
* @param dataList 数据对象。
|
||||
* @param clazz 数据对象的Class类型。
|
||||
* @param <M> 数据对象类型。
|
||||
* @return 当前数据对象中,所有上传文件字段中,文件名属性的集合。
|
||||
*/
|
||||
public static <M> Set<String> extractDownloadFileName(List<M> dataList, Class<M> clazz) {
|
||||
Set<String> resultSet = new HashSet<>();
|
||||
if (CollectionUtils.isEmpty(dataList)) {
|
||||
return resultSet;
|
||||
}
|
||||
dataList.forEach(data -> resultSet.addAll(extractDownloadFileName(data, clazz)));
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
@@ -276,10 +277,11 @@ public class MybatisDataFilterInterceptor implements Interceptor {
|
||||
return;
|
||||
}
|
||||
String dataPermSessionKey = RedisKeyUtil.makeSessionDataPermIdKey(tokenData.getSessionId());
|
||||
String dataPermData = redissonClient.getBucket(dataPermSessionKey).get().toString();
|
||||
if (StringUtils.isBlank(dataPermData)) {
|
||||
Object cachedData = redissonClient.getBucket(dataPermSessionKey).get();
|
||||
if (cachedData == null) {
|
||||
throw new NoDataPermException("No Related DataPerm found for SQL_ID [ " + sqlId + " ].");
|
||||
}
|
||||
String dataPermData = cachedData.toString();
|
||||
Map<Integer, String> dataPermMap = new HashMap<>(8);
|
||||
for (Map.Entry<String, Object> entry : JSON.parseObject(dataPermData).entrySet()) {
|
||||
dataPermMap.put(Integer.valueOf(entry.getKey()), entry.getValue().toString());
|
||||
@@ -290,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);
|
||||
}
|
||||
|
||||
@@ -457,6 +465,7 @@ public class MybatisDataFilterInterceptor implements Interceptor {
|
||||
private String userFilterColumn;
|
||||
private String deptFilterColumn;
|
||||
private String mainTableName;
|
||||
private Boolean mustIncludeUserRule;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -116,6 +116,10 @@ public class OperationLogAspect {
|
||||
if (saveOperationLog) {
|
||||
this.operationLogPostProcess(operationLogAnnotation, respData, operationLog, result);
|
||||
}
|
||||
if (elapse > properties.getSlowLogMs()) {
|
||||
log.warn("耗时较长的请求完成警告, url={},elapse={}ms reqData={} respData={}",
|
||||
request.getRequestURI(), elapse, params, respData);
|
||||
}
|
||||
log.info("请求完成, url={},elapse={}ms, respData={}", request.getRequestURI(), elapse, respData);
|
||||
} catch (Exception e) {
|
||||
if (saveOperationLog) {
|
||||
|
||||
@@ -17,4 +17,8 @@ public class OperationLogProperties {
|
||||
* 是否采集操作日志。
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
/**
|
||||
* 接口调用的毫秒数大于该值后,将输出慢日志警告。
|
||||
*/
|
||||
private long slowLogMs = 50000;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
* 上传。
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.orangeforms.common.redis.cache;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.orangeforms.common.core.object.TokenData;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.Cache;
|
||||
@@ -43,6 +44,28 @@ public class SessionCacheHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存当前Session可以下载的文件集合。
|
||||
*
|
||||
* @param filenameSet 后台服务本地存储的文件名,而不是上传时的原始文件名。
|
||||
*/
|
||||
public void putSessionDownloadableFileNameSet(Set<String> filenameSet) {
|
||||
if (CollUtil.isEmpty(filenameSet)) {
|
||||
return;
|
||||
}
|
||||
Set<String> sessionUploadFileSet = null;
|
||||
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
|
||||
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
|
||||
if (valueWrapper != null) {
|
||||
sessionUploadFileSet = (Set<String>) valueWrapper.get();
|
||||
}
|
||||
if (sessionUploadFileSet == null) {
|
||||
sessionUploadFileSet = new HashSet<>();
|
||||
}
|
||||
sessionUploadFileSet.addAll(filenameSet);
|
||||
cache.put(TokenData.takeFromRequest().getSessionId(), sessionUploadFileSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数中的文件名,是否有当前session上传。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user