commit:同步1.4版本

This commit is contained in:
Jerry
2021-02-03 21:40:27 +08:00
parent 461b7a303b
commit 3a062ad619
290 changed files with 6973 additions and 4845 deletions

View File

@@ -0,0 +1,16 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记数据权限中基于DeptId进行过滤的字段。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeptFilterColumn {
}

View File

@@ -0,0 +1,17 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 作为DisableDataFilterAspect的切点。
* 该注解仅能标记在方法上方法内所有的查询语句均不会被Mybatis拦截器过滤数据。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisableDataFilter {
}

View File

@@ -0,0 +1,28 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 仅用于微服务的多租户项目。
* 用于注解DAO层Mapper对象的租户过滤规则。被包含的方法将不会进行租户Id的过滤。
* 对于tk mapper和mybatis plus中的内置方法可以直接指定方法名即可selectOne。
* 需要说明的是在大多数场景下只要在实体对象中指定了租户Id字段基于该主表的绝大部分增删改操作
* 都需要经过租户Id过滤仅当查询非常复杂或者主表不在SQL语句之中的时候可以通过该注解禁用该SQL
* 并根据需求通过手动的方式实现租户过滤。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisableTenantFilter {
/**
* 包含的方法名称数组。该值不能为空,因为如想取消所有方法的租户过滤,
* 可以通过在实体对象中不指定租户Id字段注解的方式实现。
*
* @return 被包括的方法名称数组。
*/
String[] includeMethodName();
}

View File

@@ -0,0 +1,27 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 用于注解DAO层Mapper对象的数据权限规则。
* 由于框架使用了tk.mapper所以并非所有的Mapper接口均在当前Mapper对象中定义有一部分被tk.mapper封装如selectAll等。
* 如果需要排除tk.mapper中的方法可以直接使用tk.mapper基类所声明的方法名称即可。
* 另外比较特殊的场景是因为tk.mapper是通用框架所以同样的selectAll方法可以获取不同的数据集合因此在service中如果
* 出现两个不同的方法调用Mapper的selectAll方法但是一个需要参与过滤另外一个不需要参与那么就需要修改当前类的Mapper方法
* 将其中一个方法重新定义一个具体的接口方法,并重新设定其是否参与数据过滤。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableDataPerm {
/**
* 排除的方法名称数组。如果为空所有的方法均会被Mybaits拦截注入权限过滤条件。
*
* @return 被排序的方法名称数据。
*/
String[] excluseMethodName() default {};
}

View File

@@ -0,0 +1,16 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记通过租户Id进行过滤的字段。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantFilterColumn {
}

View File

@@ -0,0 +1,16 @@
package com.orange.demo.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记数据权限中基于UserId进行过滤的字段。
*
* @author Jerry
* @date 2020-09-24
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserFilterColumn {
}

View File

@@ -1,90 +0,0 @@
package com.orange.demo.common.core.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/**
* 使用Caffeine作为本地缓存库
*
* @author Jerry
* @date 2020-09-24
*/
@Configuration
@EnableCaching
public class CacheConfig {
private static final int DEFAULT_MAXSIZE = 10000;
private static final int DEFAULT_TTL = 3600;
/**
* 定义cache名称、超时时长秒、最大个数
* 每个cache缺省3600秒过期最大个数1000
*/
public enum CacheEnum {
/**
* 专门存储用户权限的缓存。
*/
USER_PERMISSION_CACHE(1800, 10000),
/**
* session下上传文件名的缓存(时间是24小时)。
*/
UPLOAD_FILENAME_CACHE(86400, 20000),
/**
* 缺省全局缓存(时间是24小时)。
*/
GLOBAL_CACHE(86400, 20000);
CacheEnum() {
}
CacheEnum(int ttl, int maxSize) {
this.ttl = ttl;
this.maxSize = maxSize;
}
/**
* 缓存的最大数量。
*/
private int maxSize = DEFAULT_MAXSIZE;
/**
* 缓存的时长(单位:秒)
*/
private int ttl = DEFAULT_TTL;
public int getMaxSize() {
return maxSize;
}
public int getTtl() {
return ttl;
}
}
/**
* 初始化缓存配置。
*/
@Bean
public CacheManager cacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
// 把各个cache注册到cacheManager中CaffeineCache实现了org.springframework.cache.Cache接口
ArrayList<CaffeineCache> caches = new ArrayList<>();
for (CacheEnum c : CacheEnum.values()) {
caches.add(new CaffeineCache(c.name(),
Caffeine.newBuilder().recordStats()
.expireAfterAccess(c.getTtl(), TimeUnit.SECONDS)
.maximumSize(c.getMaxSize())
.build())
);
}
manager.setCaches(caches);
return manager;
}
}

View File

@@ -1,99 +0,0 @@
package com.orange.demo.common.core.cache;
import com.orange.demo.common.core.object.TokenData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* Session数据缓存辅助类。
*
* @author Jerry
* @date 2020-09-24
*/
@SuppressWarnings("unchecked")
@Component
public class SessionCacheHelper {
@Autowired
private CacheManager cacheManager;
/**
* 缓存当前session内上传过的文件名。
*
* @param filename 通常是本地存储的文件名,而不是上传时的原始文件名。
*/
public void putSessionUploadFile(String filename) {
if (filename != null) {
Set<String> sessionUploadFileSet = null;
Cache cache = cacheManager.getCache(CacheConfig.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.add(filename);
cache.put(TokenData.takeFromRequest().getSessionId(), sessionUploadFileSet);
}
}
/**
* 判断参数中的文件名是否有当前session上传。
*
* @param filename 通常是本地存储的文件名,而不是上传时的原始文件名。
* @return true表示该文件是由当前session上传并存储在本地的否则false。
*/
public boolean existSessionUploadFile(String filename) {
if (filename == null) {
return false;
}
Cache cache = cacheManager.getCache(CacheConfig.CacheEnum.UPLOAD_FILENAME_CACHE.name());
Cache.ValueWrapper valueWrapper = cache.get(TokenData.takeFromRequest().getSessionId());
if (valueWrapper == null) {
return false;
}
return ((Set<String>) valueWrapper.get()).contains(filename);
}
/**
* 存放session的Token数据。
*
* @param sessionId 当前会话的SessionId。
* @param tokenData 当前会话的JWT Token对象。
*/
public void putTokenData(String sessionId, TokenData tokenData) {
if (sessionId == null || tokenData == null) {
return;
}
Cache cache = cacheManager.getCache(CacheConfig.CacheEnum.GLOBAL_CACHE.name());
cache.put(sessionId, tokenData);
}
/**
* 获取session的JWT Token对象。
*
* @param sessionId 当前会话的SessionId。
* @return 当前会话的JWT Token对象。
*/
public TokenData getTokenData(String sessionId) {
Cache cache = cacheManager.getCache(CacheConfig.CacheEnum.GLOBAL_CACHE.name());
return cache.get(sessionId, TokenData.class);
}
/**
* 清除当前session的所有缓存数据。
*
* @param sessionId 当前会话的SessionId。
*/
public void removeAllSessionCache(String sessionId) {
for (CacheConfig.CacheEnum c : CacheConfig.CacheEnum.values()) {
cacheManager.getCache(c.name()).evict(sessionId);
}
}
}

View File

@@ -206,7 +206,7 @@ public class MyRequestArgumentResolver implements HandlerMethodArgumentResolver
}
}
} else if (parameterType == Boolean.class) {
return value.toString();
return value;
} else if (parameterType == Character.class) {
return value.toString().charAt(0);
}

View File

@@ -0,0 +1,52 @@
package com.orange.demo.common.core.object;
import cn.hutool.core.util.BooleanUtil;
/**
* 线程本地化数据管理的工具类。可根据需求自行添加更多的线程本地化变量及其操作方法。
*
* @author Jerry
* @date 2020-09-24
*/
public class GlobalThreadLocal {
/**
* 存储数据权限过滤是否启用的线程本地化对象。
* 目前的过滤条件,包括数据权限和租户过滤。
*/
private static final ThreadLocal<Boolean> DATA_FILTER_ENABLE = ThreadLocal.withInitial(() -> Boolean.TRUE);
/**
* 设置数据过滤是否打开。如果打开当前Servlet线程所执行的SQL操作均会进行数据过滤。
*
* @param enable 打开为true否则false。
* @return 返回之前的状态,便于恢复。
*/
public static boolean setDataFilter(boolean enable) {
boolean oldValue = DATA_FILTER_ENABLE.get();
DATA_FILTER_ENABLE.set(enable);
return oldValue;
}
/**
* 判断当前Servlet线程所执行的SQL操作是否进行数据过滤。
*
* @return true 进行数据权限过滤否则false。
*/
public static boolean enabledDataFilter() {
return BooleanUtil.isTrue(DATA_FILTER_ENABLE.get());
}
/**
* 清空该存储数据,主动释放线程本地化存储资源。
*/
public static void clearDataFilter() {
DATA_FILTER_ENABLE.remove();
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private GlobalThreadLocal() {
}
}

View File

@@ -24,6 +24,16 @@ public class TokenData {
* 用户Id。
*/
private Long userId;
/**
* 用户所在部门Id。
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private Long deptId;
/**
* 租户Id。
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private Long tenantId;
/**
* 是否为超级管理员。
*/
@@ -36,6 +46,11 @@ public class TokenData {
* 标识不同登录的会话Id。
*/
private String sessionId;
/**
* 访问uaa的授权token。
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private String uaaAccessToken;
/**
* 将令牌对象添加到Http请求对象。

View File

@@ -0,0 +1,36 @@
package com.orange.demo.common.core.util;
/**
* Redis 键生成工具类。
*
* @author Jerry
* @date 2020-09-24
*/
public class RedisKeyUtil {
/**
* 计算SessionId关联的权限数据存储于Redis中的键。
*
* @param sessionId 会话Id。
* @return 会话关联的权限数据存储于Redis中的键值。
*/
public static String makeSessionPermIdKeyForRedis(String sessionId) {
return "PERM__" + sessionId;
}
/**
* 计算SessionId关联的数据权限数据存储于Redis中的键。
*
* @param sessionId 会话Id。
* @return 会话关联的数据权限数据存储于Redis中的键值。
*/
public static String makeSessionDataPermIdKeyForRedis(String sessionId) {
return "DATA_PERM__" + sessionId;
}
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private RedisKeyUtil() {
}
}