commit:同步2.2版本

This commit is contained in:
Jerry
2022-01-23 21:00:54 +08:00
parent ff7e52eedb
commit 35ac62e4d2
1201 changed files with 16301 additions and 161770 deletions

View File

@@ -23,26 +23,21 @@
<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>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

@@ -173,4 +173,14 @@ public interface BaseClient<D, V, K> {
default ResponseResult<MyPageData<V>> listByNotInList(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 根据过滤字段和过滤集合,返回不存在的数据。
*
* @param queryParam 查询参数。
* @return filterSet中在从表中不存在的数据集合。
*/
default ResponseResult<List<?>> notExist(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
}

View File

@@ -100,4 +100,9 @@ public abstract class BaseFallbackFactory<D, V, K, T extends BaseClient<D, V, K>
public ResponseResult<MyPageData<V>> listByNotInList(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<List<?>> notExist(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
}

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;
@@ -51,6 +52,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。
*/
@@ -469,6 +471,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) 条件的所有数据。单表查询,不进行任何数据关联。
*
@@ -671,6 +720,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) {
@@ -757,6 +823,23 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
return this.verifyRemoteRelatedData(data, originalData);
}
@SuppressWarnings("unchecked")
@Override
public CallResult verifyAllRelatedData(M data) {
if (data == null) {
return CallResult.ok();
}
Object id = ReflectUtil.getFieldValue(data, idFieldName);
if (id == null) {
return this.verifyAllRelatedData(data, null);
}
M originalData = this.getById((K) id);
if (originalData == null) {
return CallResult.error("数据验证失败,源数据不存在!");
}
return this.verifyAllRelatedData(data, originalData);
}
@Override
public CallResult verifyAllRelatedData(List<M> dataList) {
CallResult verifyResult = this.verifyRelatedData(dataList);
@@ -766,6 +849,199 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
return this.verifyRemoteRelatedData(dataList);
}
@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.");
}
if (StringUtils.isNotBlank(relationDict.slaveServiceName())) {
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);
}
}
} else {
@SuppressWarnings("unchecked")
BaseClient<Object, Object, Object> client = (BaseClient<Object, Object, Object>)
ApplicationContextHolder.getBean(relationDict.slaveClientClass());
ResponseResult<MyPageData<Object>> responseResult = client.listBy(new MyQueryParam());
if (!responseResult.isSuccess()) {
String errorMessage = "数据验证失败,字段 [" + fieldName + "] 获取关联数据失败!";
return CallResult.error(errorMessage);
}
Set<Object> dictIdSet = responseResult.getData().getDataList().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.isEmpty(idSet)) {
return CallResult.ok();
}
if (StringUtils.isNotBlank(relationDict.slaveServiceName())) {
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);
}
} else {
@SuppressWarnings("unchecked")
BaseClient<Object, Object, Object> client = (BaseClient<Object, Object, Object>)
ApplicationContextHolder.getBean(relationDict.slaveClientClass());
MyQueryParam queryParam = new MyQueryParam();
queryParam.setInFilterField(relationDict.slaveIdField());
queryParam.setInFilterValues(idSet);
ResponseResult<List<?>> responseResult = client.notExist(queryParam);
if (!responseResult.isSuccess()) {
String errorMessage = "数据验证失败,字段 [" + fieldName + "] 获取关联数据失败!";
return CallResult.error(errorMessage);
}
List<?> notExistIdList = responseResult.getData();
if (CollUtil.isNotEmpty(notExistIdList)) {
Object 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.isEmpty(idSet)) {
return CallResult.ok();
}
if (StringUtils.isNotBlank(relationOneToOne.slaveServiceName())) {
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);
}
} else {
@SuppressWarnings("unchecked")
BaseClient<Object, Object, Object> client = (BaseClient<Object, Object, Object>)
ApplicationContextHolder.getBean(relationOneToOne.slaveClientClass());
MyQueryParam queryParam = new MyQueryParam();
queryParam.setInFilterField(relationOneToOne.slaveIdField());
queryParam.setInFilterValues(idSet);
ResponseResult<List<?>> responseResult = client.notExist(queryParam);
if (!responseResult.isSuccess()) {
String errorMessage = "数据验证失败,字段 [" + fieldName + "] 获取关联数据失败!";
return CallResult.error(errorMessage);
}
List<?> notExistIdList = responseResult.getData();
if (CollUtil.isNotEmpty(notExistIdList)) {
Object 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();
}
/**
* 集成所有与主表实体对象相关的关联数据列表。包括本地和远程服务的一对一、字典、一对多和多对多聚合运算等。
* 也可以根据实际需求,单独调用该函数所包含的各个数据集成函数。
@@ -2145,6 +2421,7 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
RelationConstDict relationConstDict = f.getAnnotation(RelationConstDict.class);
if (relationConstDict != null) {
LocalRelationStruct relationStruct = new LocalRelationStruct();
relationStruct.relationConstDict = relationConstDict;
relationStruct.relationField = f;
relationStruct.masterIdField = ReflectUtil.getField(modelClass, relationConstDict.masterIdField());
Field dictMapField = ReflectUtil.getField(relationConstDict.constantDictClass(), "DICT_MAP");
@@ -2605,6 +2882,7 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
private Field equalOneToOneRelationField;
private BaseService<Object, Serializable> localService;
private BaseDaoMapper<Object> manyToManyMapper;
private RelationConstDict relationConstDict;
private Map<Object, String> dictMap;
private RelationDict relationDict;
private RelationOneToOne relationOneToOne;

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,9 +281,18 @@ 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 数据全部正确返回true否则false同时返回具体的错误信息。
@@ -307,6 +328,16 @@ public interface IBaseService<M, K extends Serializable> extends IService<M>{
*/
CallResult verifyAllRelatedData(M data, M originalData);
/**
* 根据最新对象和原有对象的数据对比,判断关联的本地和远程字典数据和多对一主表数据是否都是合法数据。
* NOTE: BaseService中给出了缺省实现。
* 如果data对象中包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对。
*
* @param data 数据对象。
* @return 数据全部正确返回true否则false同时返回具体的错误信息。
*/
CallResult verifyAllRelatedData(M data);
/**
* 根据最新对象列表和原有对象列表的数据对比,判断关联的本地和远程字典数据和多对一主表数据是否都是合法数据。
* NOTE: BaseService中给出了缺省实现。
@@ -316,6 +347,50 @@ public interface IBaseService<M, K extends Serializable> extends IService<M>{
*/
CallResult verifyAllRelatedData(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 2020-08-08
*/
@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

@@ -91,6 +91,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 2020-08-08
*/
@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

@@ -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;
}
@@ -296,7 +312,7 @@ public class MyModelUtil {
}
}
}
/**
* 主Model类型中遍历所有包含RelationConstDict注解的字段并将关联的静态字典中的数据
* 填充到thisModelList集合元素对象的被注解字段中。
@@ -721,6 +737,46 @@ public class MyModelUtil {
}
}
/**
* 获取当前数据对象中,所有上传文件字段的数据,并将上传后的文件名存到集合中并返回。
*
* @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;
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/

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));
@@ -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

View File

@@ -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) {

View File

@@ -17,6 +17,10 @@ public class OperationLogProperties {
* 是否采集操作日志。
*/
private boolean enabled = true;
/**
* 接口调用的毫秒数大于该值后,将输出慢日志警告。
*/
private long slowLogMs = 50000;
/**
* kafka topic
*/

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

@@ -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

@@ -29,6 +29,10 @@ import java.util.stream.Collectors;
@Slf4j
public class RedisTenantDictionaryCache<K, V> implements DictionaryCache<K, V> {
/**
* 字典数据前缀便于Redis工具分组显示。
*/
protected static final String TENANT_DICT_PREFIX = "TENANT-DICT-TABLE:";
/**
* redisson客户端。
*/
@@ -102,8 +106,8 @@ public class RedisTenantDictionaryCache<K, V> implements DictionaryCache<K, V> {
protected RMap<K, String> getTenantDataMap() {
Long tenantId = TokenData.takeFromRequest().getTenantId();
StringBuilder s = new StringBuilder(64);
s.append(dictionaryName).append("-")
.append(tenantId).append(ApplicationConstant.TREE_DICT_CACHE_NAME_SUFFIX);
s.append(TENANT_DICT_PREFIX).append(dictionaryName).append("-")
.append(tenantId).append(ApplicationConstant.DICT_CACHE_NAME_SUFFIX);
return redissonClient.getMap(s.toString());
}

View File

@@ -84,7 +84,7 @@ public class RedisTenantTreeDictionaryCache<K, V> extends RedisTenantDictionaryC
protected RListMultimap<K, String> getTenantTreeDataMap() {
Long tenantId = TokenData.takeFromRequest().getTenantId();
StringBuilder s = new StringBuilder(64);
s.append(dictionaryName).append("-")
s.append(TENANT_DICT_PREFIX).append(dictionaryName).append("-")
.append(tenantId).append(ApplicationConstant.TREE_DICT_CACHE_NAME_SUFFIX);
return redissonClient.getListMultimap(s.toString());
}

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;
}

View File

@@ -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上传。
*