Merge branch 'dev' into future/flowable

This commit is contained in:
songgaoshuai 2024-01-11 14:42:31 +08:00
commit 5e55435fe9
49 changed files with 516 additions and 295 deletions

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.1.2" />
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-powerjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-powerjob-server:5.1.2" />
<option name="imageTag" value="ruoyi/ruoyi-powerjob-server:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-powerjob-server/Dockerfile" />
</settings>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.1.2" />
<option name="imageTag" value="ruoyi/ruoyi-server:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings>

View File

@ -9,7 +9,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.1.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.1-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@ -23,7 +23,13 @@
> 前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
> 文档地址: [plus-doc](https://plus-doc.dromara.org) - [plus-doc(国内备用)](https://dromara.gitee.io/plus-doc)
> 文档地址: [plus-doc](https://plus-doc.dromara.org)
## 赞助商
MaxKey - https://gitee.com/dromara/MaxKey <br>
CCFlow - https://gitee.com/opencc/RuoYi-JFlow <br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异
@ -115,13 +121,12 @@
>
>[部署项目 必看](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy](https://plus-doc.dromara.org/#/ruoyi-vue-plus/quickstart/deploy)
>
>
>[如何加群](https://plus-doc.dromara.org/#/common/add_group)
>>[https://plus-doc.dromara.org/#/common/add_group](https://plus-doc.dromara.org/#/common/add_group)
>
>[参考文档 Wiki](https://plus-doc.dromara.org)
>>[https://plus-doc.dromara.org](https://plus-doc.dromara.org)
>
>[参考文档(国内备份)](https://dromara.gitee.io/plus-doc)
>>[https://dromara.gitee.io/plus-doc](https://dromara.gitee.io/plus-doc)
## 软件架构图
@ -131,18 +136,6 @@
[参与贡献的方式 https://plus-doc.dromara.org/#/common/contribution](https://plus-doc.dromara.org/#/common/contribution)
### 其他
* 定期同步升级 RuoYi-Vue 有用的更新
* GitHub 地址 [RuoYi-Vue-Plus](https://github.com/dromara/RuoYi-Vue-Plus)
* 微服务 分支 [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus)
* 前端项目 地址 [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
* 用户扩展项目 [扩展项目列表](https://plus-doc.dromara.org/#/ruoyi-vue-plus/extend-project/list)
## 加群与捐献
>[加群与捐献](https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat)
>>[https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat](https://plus-doc.dromara.org/#/ruoyi-vue-plus/other/group_chat)
## 捐献作者
作者为兼职做开源,平时还需要工作,如果帮到了您可以请作者吃个盒饭
<img src="https://foruda.gitee.com/images/1678975784848381069/d8661ed9_1766278.png" width="300px" height="450px" />

16
pom.xml
View File

@ -13,13 +13,13 @@
<description>RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.1.2</revision>
<spring-boot.version>3.1.7</spring-boot.version>
<revision>5.2.0-SNAPSHOT</revision>
<spring-boot.version>3.2.1</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.mybatis>3.0.3</spring-boot.mybatis>
<springdoc.version>2.2.0</springdoc.version>
<springdoc.version>2.3.0</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.3.3</easyexcel.version>
@ -27,12 +27,12 @@
<satoken.version>1.37.0</satoken.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.22</hutool.version>
<hutool.version>5.8.24</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>3.1.8</spring-boot-admin.version>
<redisson.version>3.24.3</redisson.version>
<spring-boot-admin.version>3.2.0</spring-boot-admin.version>
<redisson.version>3.25.2</redisson.version>
<lock4j.version>2.2.5</lock4j.version>
<dynamic-ds.version>4.2.0</dynamic-ds.version>
<dynamic-ds.version>4.3.0</dynamic-ds.version>
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
<powerjob.version>4.3.6</powerjob.version>
<mapstruct-plus.version>1.3.5</mapstruct-plus.version>
@ -223,7 +223,7 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

View File

@ -10,7 +10,6 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.domain.dto.UserOnlineDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
@ -18,6 +17,7 @@ import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Component;
@ -43,7 +43,6 @@ public class UserActionListener implements SaTokenListener {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
LoginUser user = LoginHelper.getLoginUser();
UserOnlineDTO dto = new UserOnlineDTO();
dto.setIpaddr(ip);
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
@ -51,25 +50,29 @@ public class UserActionListener implements SaTokenListener {
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
dto.setUserName(user.getUsername());
dto.setClientKey(user.getClientKey());
dto.setDeviceType(user.getDeviceType());
dto.setDeptName(user.getDeptName());
if(tokenConfig.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
}
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username);
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(tokenConfig.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
}
});
// 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(user.getTenantId());
logininforEvent.setUsername(user.getUsername());
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo(user.getUserId(), ip);
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
}

View File

@ -57,7 +57,7 @@ public class SysLoginService {
private final ISysPermissionService permissionService;
private final ISysSocialService sysSocialService;
private final ISysRoleService roleService;
private final SysDeptMapper deptMapper;
private final ISysDeptService deptService;
private final SysUserMapper userMapper;
@ -145,11 +145,9 @@ public class SysLoginService {
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
SysDeptVo dept = deptMapper.selectVoById(user.getDeptId());
SysDeptVo dept = deptService.selectDeptById(user.getDeptId());
loginUser.setDeptName(ObjectUtil.isNull(dept) ? "" : dept.getDeptName());
List<SysRoleVo> roles = DataPermissionHelper.ignore(() -> {
return roleService.selectRolesByUserId(user.getUserId());
});
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
return loginUser;
}

View File

@ -7,7 +7,6 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
@ -23,8 +22,6 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysUserVo;
@ -84,11 +81,16 @@ public class SocialAuthStrategy implements IAuthStrategy {
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
SysSocialVo social;
if (TenantHelper.isEnable()) {
Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
social = opt.get();
} else {
social = list.get(0);
}
SysSocialVo social = opt.get();
// 查找用户
SysUserVo user = loadUser(social.getTenantId(), social.getUserId());

View File

@ -207,6 +207,13 @@ justauth:
client-id: 876892492581044224
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam
scopes: [openid, email, phone, profile]
qq:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e

View File

@ -61,6 +61,11 @@ user:
spring:
application:
name: ${ruoyi.name}
threads:
# 开启虚拟线程 仅jdk21可用
# 开启后还需更改 UndertowConfig 虚拟线程配置
virtual:
enabled: false
# 资源信息
messages:
# 国际化资源文件路径

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.1.2</revision>
<revision>5.2.0-SNAPSHOT</revision>
</properties>
<dependencyManagement>

View File

@ -1,57 +0,0 @@
package org.dromara.common.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.HashMap;
import java.util.Map;
/**
* 线程持有类
*
* @author Michelle.Chung
*/
public class ThreadLocalHolder {
/**
* 初始化 (支持异步)
*/
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = TransmittableThreadLocal.withInitial(HashMap::new);
/**
* 设置值
*
* @param key
* @param value
*/
public static <T> void set(String key, T value) {
THREAD_LOCAL.get().put(key, value);
}
/**
* 获取值
*
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
return (T) THREAD_LOCAL.get().get(key);
}
/**
* 移除值
*
* @param key
*/
public static void remove(String key) {
THREAD_LOCAL.get().remove(key);
}
/**
* 清空值
*/
public static void clear() {
THREAD_LOCAL.remove();
}
}

View File

@ -4,8 +4,14 @@ import cn.dev33.satoken.SaManager;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
@ -14,13 +20,6 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
@ -37,7 +36,7 @@ import java.util.StringJoiner;
@Aspect
public class RepeatSubmitAspect {
private static final String KEY_CACHE = "keyCache";
private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
@Before("@annotation(repeatSubmit)")
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
@ -60,7 +59,7 @@ public class RepeatSubmitAspect {
// 唯一标识指定key + url + 消息头
String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
ThreadLocalHolder.set(KEY_CACHE, cacheRepeatKey);
KEY_CACHE.set(cacheRepeatKey);
} else {
String message = repeatSubmit.message();
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
@ -83,10 +82,9 @@ public class RepeatSubmitAspect {
if (r.getCode() == R.SUCCESS) {
return;
}
String cacheKey = ThreadLocalHolder.get(KEY_CACHE);
RedisUtils.deleteObject(cacheKey);
RedisUtils.deleteObject(KEY_CACHE.get());
} finally {
ThreadLocalHolder.remove(KEY_CACHE);
KEY_CACHE.remove();
}
}
}
@ -99,9 +97,8 @@ public class RepeatSubmitAspect {
*/
@AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
String cacheKey = ThreadLocalHolder.get(KEY_CACHE);
RedisUtils.deleteObject(cacheKey);
ThreadLocalHolder.remove(KEY_CACHE);
RedisUtils.deleteObject(KEY_CACHE.get());
KEY_CACHE.remove();
}
/**
@ -130,7 +127,7 @@ public class RepeatSubmitAspect {
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {

View File

@ -4,16 +4,6 @@ import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessStatus;
import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.satoken.utils.LoginHelper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@ -23,6 +13,15 @@ import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessStatus;
import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult;
@ -51,7 +50,7 @@ public class LogAspect {
/**
* 计时 key
*/
private static final String LOG_STOP_WATCH_KEY = "logStopwatch";
private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
/**
* 处理请求前执行
@ -59,7 +58,7 @@ public class LogAspect {
@Before(value = "@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, Log controllerLog) {
StopWatch stopWatch = new StopWatch();
ThreadLocalHolder.set(LOG_STOP_WATCH_KEY, stopWatch);
KEY_CACHE.set(stopWatch);
stopWatch.start();
}
@ -112,7 +111,7 @@ public class LogAspect {
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
StopWatch stopWatch = ThreadLocalHolder.get(LOG_STOP_WATCH_KEY);
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop();
operLog.setCostTime(stopWatch.getTime());
// 发布事件保存数据库
@ -122,7 +121,7 @@ public class LogAspect {
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} finally {
ThreadLocalHolder.remove(LOG_STOP_WATCH_KEY);
KEY_CACHE.remove();
}
}
@ -204,7 +203,7 @@ public class LogAspect {
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {

View File

@ -39,7 +39,7 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>

View File

@ -7,7 +7,6 @@ import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.context.ThreadLocalHolder;
import java.util.HashMap;
import java.util.Map;

View File

@ -32,6 +32,11 @@
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,45 @@
package org.dromara.common.redis.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.TimeUnit;
/**
* 缓存配置
*
* @author Lion Li
*/
@AutoConfiguration
@EnableCaching
public class CacheConfig {
/**
* caffeine 本地缓存处理器
*/
@Bean
public Cache<Object, Object> caffeine() {
return Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(30, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
}
/**
* 自定义缓存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new PlusSpringCacheManager();
}
}

View File

@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.redisson.client.codec.StringCodec;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.TypedJsonJacksonCodec;
@ -16,8 +15,6 @@ import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
/**
@ -27,7 +24,6 @@ import org.springframework.context.annotation.Bean;
*/
@Slf4j
@AutoConfiguration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
@ -86,14 +82,6 @@ public class RedisConfig {
};
}
/**
* 自定义缓存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new PlusSpringCacheManager();
}
/**
* redis集群配置 yml
*

View File

@ -0,0 +1,88 @@
package org.dromara.common.redis.manager;
import cn.hutool.core.lang.Console;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
/**
* Cache 装饰器模式(用于扩展 Caffeine 一级缓存)
*
* @author LionLi
*/
public class CaffeineCacheDecorator implements Cache {
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
CAFFEINE = SpringUtils.getBean("caffeine");
private final Cache cache;
public CaffeineCacheDecorator(Cache cache) {
this.cache = cache;
}
@Override
public String getName() {
return cache.getName();
}
@Override
public Object getNativeCache() {
return cache.getNativeCache();
}
@Override
public ValueWrapper get(Object key) {
Object o = CAFFEINE.get(key, k -> cache.get(key));
Console.log("redisson caffeine -> key: " + key + ",value:" + o);
return (ValueWrapper) o;
}
@SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
Object o = CAFFEINE.get(key, k -> cache.get(key, type));
Console.log("redisson caffeine -> key: " + key + ",value:" + o);
return (T) o;
}
@Override
public void put(Object key, Object value) {
cache.put(key, value);
}
public ValueWrapper putIfAbsent(Object key, Object value) {
return cache.putIfAbsent(key, value);
}
@Override
public void evict(Object key) {
evictIfPresent(key);
}
public boolean evictIfPresent(Object key) {
boolean b = cache.evictIfPresent(key);
if (b) {
CAFFEINE.invalidate(key);
}
return b;
}
@Override
public void clear() {
cache.clear();
}
public boolean invalidate() {
return cache.invalidate();
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
Object o = CAFFEINE.get(key, k -> cache.get(key, valueLoader));
Console.log("redisson caffeine -> key: " + key + ",value:" + o);
return (T) o;
}
}

View File

@ -156,7 +156,7 @@ public class PlusSpringCacheManager implements CacheManager {
private Cache createMap(String name, CacheConfig config) {
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
Cache cache = new RedissonCache(map, allowNullValues);
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}
@ -170,7 +170,7 @@ public class PlusSpringCacheManager implements CacheManager {
private Cache createMapCache(String name, CacheConfig config) {
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
Cache cache = new RedissonCache(map, config, allowNullValues);
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}

View File

@ -1 +1,2 @@
org.dromara.common.redis.config.RedisConfig
org.dromara.common.redis.config.CacheConfig

View File

@ -36,6 +36,11 @@
<artifactId>sa-token-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -2,26 +2,43 @@ package org.dromara.common.satoken.core.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.hutool.core.lang.Console;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.utils.RedisUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
* <p>
* 采用 caffeine + redis 多级缓存 优化并发查询效率
*
* @author Lion Li
*/
public class PlusSaTokenDao implements SaTokenDao {
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(5, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
/**
* 获取Value如无返空
*/
@Override
public String get(String key) {
return RedisUtils.getCacheObject(key);
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
Console.log("caffeine -> key:" + key + ",value:" + o);
return (String) o;
}
/**
@ -38,6 +55,7 @@ public class PlusSaTokenDao implements SaTokenDao {
} else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
}
CAFFEINE.put(key, value);
}
/**
@ -47,6 +65,7 @@ public class PlusSaTokenDao implements SaTokenDao {
public void update(String key, String value) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, value, true);
CAFFEINE.put(key, value);
}
}
@ -81,7 +100,9 @@ public class PlusSaTokenDao implements SaTokenDao {
*/
@Override
public Object getObject(String key) {
return RedisUtils.getCacheObject(key);
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
Console.log("caffeine -> key:" + key + ",value:" + o);
return o;
}
/**
@ -98,6 +119,7 @@ public class PlusSaTokenDao implements SaTokenDao {
} else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
}
CAFFEINE.put(key, object);
}
/**
@ -107,6 +129,7 @@ public class PlusSaTokenDao implements SaTokenDao {
public void updateObject(String key, Object object) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, object, true);
CAFFEINE.put(key, object);
}
}
@ -139,10 +162,14 @@ public class PlusSaTokenDao implements SaTokenDao {
/**
* 搜索数据
*/
@SuppressWarnings("unchecked")
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
String keyStr = prefix + "*" + keyword + "*";
return (List<String>) CAFFEINE.get(keyStr, k -> {
Collection<String> keys = RedisUtils.keys(keyStr);
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
});
}
}

View File

@ -9,12 +9,10 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.UserType;
import java.util.Set;
import java.util.function.Supplier;
/**
* 登录鉴权助手
@ -34,9 +32,10 @@ public class LoginHelper {
public static final String LOGIN_USER_KEY = "loginUser";
public static final String TENANT_KEY = "tenantId";
public static final String USER_KEY = "userId";
public static final String USER_NAME_KEY = "userName";
public static final String DEPT_KEY = "deptId";
public static final String DEPT_NAME_KEY = "deptName";
public static final String CLIENT_KEY = "clientid";
public static final String TENANT_ADMIN_KEY = "isTenantAdmin";
/**
* 登录系统 基于 设备类型
@ -46,29 +45,28 @@ public class LoginHelper {
* @param model 配置参数
*/
public static void login(LoginUser loginUser, SaLoginModel model) {
ThreadLocalHolder.set(LOGIN_USER_KEY, loginUser);
ThreadLocalHolder.set(TENANT_KEY, loginUser.getTenantId());
ThreadLocalHolder.set(USER_KEY, loginUser.getUserId());
ThreadLocalHolder.set(DEPT_KEY, loginUser.getDeptId());
model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
StpUtil.login(loginUser.getLoginId(),
model.setExtra(TENANT_KEY, loginUser.getTenantId())
.setExtra(USER_KEY, loginUser.getUserId())
.setExtra(DEPT_KEY, loginUser.getDeptId()));
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
.setExtra(USER_NAME_KEY, loginUser.getUsername())
.setExtra(DEPT_KEY, loginUser.getDeptId())
.setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
);
SaSession tokenSession = StpUtil.getTokenSession();
tokenSession.updateTimeout(model.getTimeout());
tokenSession.set(LOGIN_USER_KEY, loginUser);
}
/**
* 获取用户(多级缓存)
*/
public static LoginUser getLoginUser() {
return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
return null;
}
return session.get(LOGIN_USER_KEY);
});
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
return null;
}
return (LoginUser) session.get(LOGIN_USER_KEY);
}
/**
@ -86,7 +84,7 @@ public class LoginHelper {
* 获取用户id
*/
public static Long getUserId() {
return Convert.toLong(getExtra(USER_KEY));
return Convert.toLong(getExtra(USER_KEY));
}
/**
@ -104,7 +102,12 @@ public class LoginHelper {
}
private static Object getExtra(String key) {
return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key));
try {
return StpUtil.getExtra(key);
} catch (Exception e) {
return null;
}
}
/**
@ -147,26 +150,11 @@ public class LoginHelper {
}
public static boolean isTenantAdmin() {
Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> {
return isTenantAdmin(getLoginUser().getRolePermission());
});
return Convert.toBool(value);
return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
}
public static boolean isLogin() {
return getLoginUser() != null;
}
public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
try {
Object obj = ThreadLocalHolder.get(key);
if (ObjectUtil.isNull(obj)) {
obj = handle.get();
ThreadLocalHolder.set(key, obj);
}
return obj;
} catch (Exception e) {
return null;
}
}
}

View File

@ -4,17 +4,14 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.security.config.properties.SecurityProperties;
import org.dromara.common.security.handler.AllUrlHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@ -62,9 +59,6 @@ public class SecurityConfig implements WebMvcConfigurer {
StpUtil.getTokenValue());
}
// 保存用户信息
ThreadLocalHolder.set(LoginHelper.LOGIN_USER_KEY, LoginHelper.getLoginUser());
// 有效率影响 用于临时测试
// if (log.isDebugEnabled()) {
// log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
@ -72,13 +66,7 @@ public class SecurityConfig implements WebMvcConfigurer {
// }
});
})
{
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalHolder.remove(LoginHelper.LOGIN_USER_KEY);
}
}).addPathPatterns("/**")
})).addPathPatterns("/**")
// 排除不需要拦截的路径
.excludePathPatterns(securityProperties.getExcludes());
}

View File

@ -2,6 +2,8 @@ package org.dromara.common.social.config.properties;
import lombok.Data;
import java.util.List;
/**
* 社交登录配置
*
@ -65,4 +67,9 @@ public class SocialLoginConfigProperties {
*/
private String serverUrl;
/**
* 请求范围
*/
private List<String> scopes;
}

View File

@ -0,0 +1,100 @@
package org.dromara.common.social.topiam;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;
import static org.dromara.common.social.topiam.AuthTopiamSource.TOPIAM;
/**
* TopIAM 认证请求
*
* @author xlsea
* @since 2024-01-06
*/
@Slf4j
public class AuthTopIamRequest extends AuthDefaultRequest {
public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.topiam.server-url");
/**
* 设定归属域
*/
public AuthTopIamRequest(AuthConfig config) {
super(config, TOPIAM);
}
public AuthTopIamRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, TOPIAM, authStateCache);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String body = doPostAuthorizationCode(authCallback.getCode());
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
return AuthToken.builder()
.accessToken(object.getStr("access_token"))
.refreshToken(object.getStr("refresh_token"))
.idToken(object.getStr("id_token"))
.tokenType(object.getStr("token_type"))
.scope(object.getStr("scope"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String body = doGetUserInfo(authToken);
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
return AuthUser.builder()
.uuid(object.getStr("sub"))
.username(object.getStr("preferred_username"))
.nickname(object.getStr("nickname"))
.avatar(object.getStr("picture"))
.email(object.getStr("email"))
.token(authToken)
.source(source.toString())
.build();
}
@Override
protected String doGetUserInfo(AuthToken authToken) {
return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("scope", StrUtil.join("%20", config.getScopes()))
.build();
}
public static void checkResponse(Dict object) {
// oauth/token 验证异常
if (object.containsKey("error")) {
throw new AuthException(object.getStr("error_description"));
}
// user 验证异常
if (object.containsKey("message")) {
throw new AuthException(object.getStr("message"));
}
}
}

View File

@ -0,0 +1,51 @@
package org.dromara.common.social.topiam;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
/**
* Oauth2 默认接口说明
*
* @author xlsea
* @since 2024-01-06
*/
public enum AuthTopiamSource implements AuthSource {
/**
* 测试
*/
TOPIAM {
/**
* 授权的api
*/
@Override
public String authorize() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/auth";
}
/**
* 获取accessToken的api
*/
@Override
public String accessToken() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/token";
}
/**
* 获取用户信息的api
*/
@Override
public String userInfo() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/userinfo";
}
/**
* 平台对应的 AuthRequest 实现类必须继承自 {@link AuthDefaultRequest}
*/
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthTopIamRequest.class;
}
}
}

View File

@ -11,6 +11,7 @@ import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
import org.dromara.common.social.topiam.AuthTopIamRequest;
/**
* 认证授权工具类
@ -38,7 +39,8 @@ public class SocialUtils {
AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
.clientId(obj.getClientId())
.clientSecret(obj.getClientSecret())
.redirectUri(obj.getRedirectUri());
.redirectUri(obj.getRedirectUri())
.scopes(obj.getScopes());
return switch (source.toLowerCase()) {
case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
@ -63,6 +65,7 @@ public class SocialUtils {
case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
default -> throw new AuthException("未获取到有效的Auth配置");
};
}

View File

@ -1,14 +1,15 @@
package org.dromara.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
@ -27,7 +28,7 @@ public class TenantHelper {
private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
private static final String TENANT_ID_KEY = "tempDynamicTenant";
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
/**
* 租户功能是否启用
@ -88,12 +89,12 @@ public class TenantHelper {
return;
}
if (!isLogin()) {
ThreadLocalHolder.set(TENANT_ID_KEY, tenantId);
TEMP_DYNAMIC_TENANT.set(tenantId);
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.setCacheObject(cacheKey, tenantId);
ThreadLocalHolder.set(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
}
/**
@ -106,15 +107,15 @@ public class TenantHelper {
return null;
}
if (!isLogin()) {
return ThreadLocalHolder.get(TENANT_ID_KEY);
return TEMP_DYNAMIC_TENANT.get();
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
String tenantId = ThreadLocalHolder.get(cacheKey);
String tenantId = (String) SaHolder.getStorage().get(cacheKey);
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
tenantId = RedisUtils.getCacheObject(cacheKey);
ThreadLocalHolder.set(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
return tenantId;
}
@ -126,12 +127,12 @@ public class TenantHelper {
return;
}
if (!isLogin()) {
ThreadLocalHolder.remove(TENANT_ID_KEY);
TEMP_DYNAMIC_TENANT.remove();
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.deleteObject(cacheKey);
ThreadLocalHolder.remove(cacheKey);
SaHolder.getStorage().delete(cacheKey);
}
/**

View File

@ -24,6 +24,10 @@ public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServle
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
// 使用虚拟线程 解除下方注释 仅限jdk21
// if (SpringUtils.getProperty("spring.threads.virtual.enabled", Boolean.class, false)) {
// deploymentInfo.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
// }
});
}

View File

@ -2,15 +2,14 @@ package org.dromara.common.web.interceptor;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@ -30,7 +29,7 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
private final String prodProfile = "prod";
private final String STOP_WATCH_KEY = "stopwatch";
private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
@ -56,7 +55,7 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
}
StopWatch stopWatch = new StopWatch();
ThreadLocalHolder.set(STOP_WATCH_KEY, stopWatch);
KEY_CACHE.set(stopWatch);
stopWatch.start();
}
return true;
@ -70,10 +69,10 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
StopWatch stopWatch = ThreadLocalHolder.get(STOP_WATCH_KEY);
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop();
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
ThreadLocalHolder.clear();
KEY_CACHE.remove();
}
}

View File

@ -41,7 +41,6 @@ import java.util.Arrays;
public class SysProfileController extends BaseController {
private final ISysUserService userService;
private final ISysRoleService roleService;
private final ISysOssService ossService;
/**
@ -50,7 +49,6 @@ public class SysProfileController extends BaseController {
@GetMapping
public R<ProfileVo> profile() {
SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
user.setRoles(roleService.selectRolesByUserId(user.getUserId()));
ProfileVo profileVo = new ProfileVo();
profileVo.setUser(user);
profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserId()));

View File

@ -72,9 +72,8 @@ public class SysUserController extends BaseController {
@SaCheckPermission("system:user:export")
@PostMapping("/export")
public void export(SysUserBo user, HttpServletResponse response) {
List<SysUserVo> list = userService.selectUserList(user);
List<SysUserExportVo> listVo = MapstructUtils.convert(list, SysUserExportVo.class);
ExcelUtil.exportExcel(listVo, "用户数据", SysUserExportVo.class, response);
List<SysUserExportVo> list = userService.selectUserExportList(user);
ExcelUtil.exportExcel(list, "用户数据", SysUserExportVo.class, response);
}
/**
@ -116,7 +115,6 @@ public class SysUserController extends BaseController {
if (ObjectUtil.isNull(user)) {
return R.fail("没有权限访问用户数据!");
}
user.setRoles(roleService.selectRolesByUserId(user.getUserId()));
userInfoVo.setUser(user);
userInfoVo.setPermissions(loginUser.getMenuPermission());
userInfoVo.setRoles(loginUser.getRolePermission());

View File

@ -20,7 +20,6 @@ import java.util.Date;
@Data
@NoArgsConstructor
@AutoMapper(target = SysUserVo.class, convertGenerate = false)
public class SysUserExportVo implements Serializable {
@Serial
@ -85,14 +84,12 @@ public class SysUserExportVo implements Serializable {
/**
* 部门名称
*/
@ReverseAutoMapping(target = "deptName", source = "dept.deptName")
@ExcelProperty(value = "部门名称")
private String deptName;
/**
* 负责人
*/
@ReverseAutoMapping(target = "leaderName", source = "dept.leaderName")
@ExcelProperty(value = "部门负责人")
private String leaderName;

View File

@ -114,9 +114,10 @@ public class SysUserVo implements Serializable {
private Date createTime;
/**
* 部门对象
* 部门
*/
private SysDeptVo dept;
@Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
private String deptName;
/**
* 角色对象

View File

@ -8,6 +8,7 @@ import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import java.util.List;
@ -20,7 +21,7 @@ import java.util.List;
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
@DataPermission({
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "deptName", value = "u.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
Page<SysUserVo> selectPageUserList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
@ -35,7 +36,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
List<SysUserExportVo> selectUserExportList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
/**
* 根据条件分页查询已配用户角色列表

View File

@ -3,6 +3,7 @@ package org.dromara.system.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import java.util.List;
@ -23,7 +24,7 @@ public interface ISysUserService {
* @param user 用户信息
* @return 用户信息集合信息
*/
List<SysUserVo> selectUserList(SysUserBo user);
List<SysUserExportVo> selectUserExportList(SysUserBo user);
/**
* 根据条件分页查询已分配用户角色列表

View File

@ -6,9 +6,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.core.constant.CacheConstants;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.context.ThreadLocalHolder;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.MapstructUtils;
@ -26,7 +25,6 @@ import org.dromara.system.domain.vo.SysDictTypeVo;
import org.dromara.system.mapper.SysDictDataMapper;
import org.dromara.system.mapper.SysDictTypeMapper;
import org.dromara.system.service.ISysDictTypeService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -217,16 +215,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
* @param separator 分隔符
* @return 字典标签
*/
@SuppressWarnings("unchecked cast")
@Override
public String getDictLabel(String dictType, String dictValue, String separator) {
// 优先从本地缓存获取
List<SysDictDataVo> datas = ThreadLocalHolder.get(CacheConstants.SYS_DICT_KEY + dictType);
if (ObjectUtil.isNull(datas)) {
datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
ThreadLocalHolder.set(CacheConstants.SYS_DICT_KEY + dictType, datas);
}
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
if (StringUtils.containsAny(dictValue, separator)) {
return Arrays.stream(dictValue.split(separator))
@ -245,16 +236,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
* @param separator 分隔符
* @return 字典值
*/
@SuppressWarnings("unchecked cast")
@Override
public String getDictValue(String dictType, String dictLabel, String separator) {
// 优先从本地缓存获取
List<SysDictDataVo> datas = ThreadLocalHolder.get(CacheConstants.SYS_DICT_KEY + dictType);
if (ObjectUtil.isNull(datas)) {
datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
ThreadLocalHolder.set(CacheConstants.SYS_DICT_KEY + dictType, datas);
}
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
if (StringUtils.containsAny(dictLabel, separator)) {
return Arrays.stream(dictLabel.split(separator))

View File

@ -80,7 +80,6 @@ public class SysRoleServiceImpl implements ISysRoleService {
.between(params.get("beginTime") != null && params.get("endTime") != null,
"r.create_time", params.get("beginTime"), params.get("endTime"))
.orderByAsc("r.role_sort").orderByAsc("r.create_time");
;
return wrapper;
}

View File

@ -29,6 +29,7 @@ import org.dromara.system.domain.SysUserRole;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysUserExportVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.*;
import org.dromara.system.service.ISysUserService;
@ -69,8 +70,8 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
* @return 用户信息集合信息
*/
@Override
public List<SysUserVo> selectUserList(SysUserBo user) {
return baseMapper.selectUserList(this.buildQueryWrapper(user));
public List<SysUserExportVo> selectUserExportList(SysUserBo user) {
return baseMapper.selectUserExportList(this.buildQueryWrapper(user));
}
private Wrapper<SysUser> buildQueryWrapper(SysUserBo user) {
@ -163,7 +164,12 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
*/
@Override
public SysUserVo selectUserById(Long userId) {
return baseMapper.selectVoById(userId);
SysUserVo user = baseMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
return user;
}
user.setRoles(roleMapper.selectRolesByUserId(user.getUserId()));
return user;
}
/**

View File

@ -4,31 +4,21 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.SysUserMapper">
<!-- 多结构嵌套自动映射需带上每个实体的主键id 否则映射会失败 -->
<resultMap type="org.dromara.system.domain.vo.SysUserVo" id="SysUserResult">
<id property="userId" column="user_id"/>
<result property="deptId" column="dept_id"/>
<association property="dept" column="dept_id" resultMap="deptResult"/>
</resultMap>
<resultMap id="deptResult" type="org.dromara.system.domain.vo.SysDeptVo">
<id property="deptId" column="dept_id"/>
<result property="email" column="dept_email"/>
<result property="status" column="dept_status"/>
<result property="createTime" column="dept_create_time"/>
<resultMap type="org.dromara.system.domain.vo.SysUserExportVo" id="SysUserExportResult">
<id property="userId" column="user_id"/>
</resultMap>
<select id="selectPageUserList" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader, u1.user_name as leaderName
u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user u1 on u1.user_id = d.leader
${ew.getCustomSqlSegment}
</select>
<select id="selectUserList" resultMap="SysUserResult">
<select id="selectUserExportList" resultMap="SysUserExportResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader, u1.user_name as leaderName

View File

@ -100,7 +100,7 @@ services:
network_mode: "host"
ruoyi-server1:
image: ruoyi/ruoyi-server:5.1.2
image: ruoyi/ruoyi-server:5.2.0
container_name: ruoyi-server1
environment:
# 时区上海
@ -115,7 +115,7 @@ services:
network_mode: "host"
ruoyi-server2:
image: ruoyi/ruoyi-server:5.1.2
image: ruoyi/ruoyi-server:5.2.0
container_name: ruoyi-server2
environment:
# 时区上海
@ -130,7 +130,7 @@ services:
network_mode: "host"
ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:5.1.2
image: ruoyi/ruoyi-monitor-admin:5.2.0
container_name: ruoyi-monitor-admin
environment:
# 时区上海
@ -142,7 +142,7 @@ services:
network_mode: "host"
ruoyi-powerjob-server:
image: ruoyi/ruoyi-powerjob-server:5.1.2
image: ruoyi/ruoyi-powerjob-server:5.2.0
container_name: ruoyi-powerjob-server
environment:
# 时区上海

View File

@ -108,7 +108,7 @@ http {
}
# 解决 powerjob 代理之后静态文件无法访问的问题 请勿修改乱动
location ~ ^/(js|css|jpg|png|svg|woff|ttf|ico|img)/ {
location ~ ^/(js|img|fonts)/ {
proxy_pass http://powerjob-server;
}

View File

@ -20,7 +20,7 @@ create table sys_social
union_id varchar2(255) default null,
scope varchar2(255) default null,
token_type varchar2(255) default null,
id_token varchar2(255) default null,
id_token varchar2(2000) default null,
mac_algorithm varchar2(255) default null,
mac_key varchar2(255) default null,
code varchar2(255) default null,

View File

@ -20,7 +20,7 @@ create table sys_social
union_id varchar(255) default null::varchar,
scope varchar(255) default null::varchar,
token_type varchar(255) default null::varchar,
id_token varchar(255) default null::varchar,
id_token varchar(2000) default null::varchar,
mac_algorithm varchar(255) default null::varchar,
mac_key varchar(255) default null::varchar,
code varchar(255) default null::varchar,

View File

@ -21,7 +21,7 @@ create table sys_social
union_id varchar(255) default null comment '用户的 unionid',
scope varchar(255) default null comment '授予的权限,部分平台可能没有',
token_type varchar(255) default null comment '个别平台的授权信息,部分平台可能没有',
id_token varchar(255) default null comment 'id token部分平台可能没有',
id_token varchar(2000) default null comment 'id token部分平台可能没有',
mac_algorithm varchar(255) default null comment '小米平台用户的附带属性,部分平台可能没有',
mac_key varchar(255) default null comment '小米平台用户的附带属性,部分平台可能没有',
code varchar(255) default null comment '用户的授权code部分平台可能没有',

View File

@ -17,7 +17,7 @@ create table sys_social
union_id nvarchar(255) NULL,
scope nvarchar(255) NULL,
token_type nvarchar(255) NULL,
id_token nvarchar(255) NULL,
id_token nvarchar(2000) NULL,
mac_algorithm nvarchar(255) NULL,
mac_key nvarchar(255) NULL,
code nvarchar(255) NULL,