12 Commits

Author SHA1 Message Date
疯狂的狮子Li
161b52d8d7 update 优化 mqtt-server 文档搭建说明 2026-01-16 15:44:42 +08:00
疯狂的狮子Li
befabc61de update 优化 mqtt-server 文档搭建说明 2026-01-16 15:42:02 +08:00
疯狂的狮子Li
5b82c12e17 add 增加 ruoyi-common-mqtt 模块 2026-01-16 15:39:10 +08:00
疯狂的狮子Li
145b903185 [重大更改] 移除多租户相关功能 2026-01-13 16:14:52 +08:00
疯狂的狮子Li
55098339d4 update 优化 兼容path大写开头搜索 2026-01-12 09:28:02 +08:00
疯狂的狮子Li
469274d9b1 update 优化 大家都认可用"账"统一改为账 2026-01-12 09:28:02 +08:00
疯狂的狮子Li
596e83701a update 优化 添加菜单路由地址和名称的校验规则 2026-01-12 09:28:02 +08:00
疯狂的狮子Li
8a87c7aa4e update 优化 添加菜单路由地址和名称的校验规则 2026-01-12 09:28:01 +08:00
疯狂的狮子Li
b4467aa8e9 update 优化 统一用词 2026-01-12 09:28:01 +08:00
AprilWind
5de3114f05 update 优化oss日志侦听器打印级别 2026-01-12 09:28:01 +08:00
ColorDreams
8d4fdd9fc8 update ip2region version to 3.3.2 2026-01-12 09:28:01 +08:00
疯狂的狮子Li
2f4e89ee42 update 不兼容整体升级 springboot 4.X
update springboot 3.5 => 4.0
update springdoc 2.8 => 3.0
update mybatis-plus 3.5.14 => 3.5.15
update redisson 3.52.0 => 4.1.0
update dynamic-ds 4.3.1 => 4.5.0
2026-01-06 17:18:08 +08:00
164 changed files with 1252 additions and 5040 deletions

View File

@@ -35,7 +35,6 @@ MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br> CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br> 数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br> 引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br> Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br> aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br> Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>

18
pom.xml
View File

@@ -14,23 +14,23 @@
<properties> <properties>
<revision>5.5.2</revision> <revision>5.5.2</revision>
<spring-boot.version>3.5.9</spring-boot.version> <spring-boot.version>4.0.1</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version> <java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version> <mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.8.14</springdoc.version> <springdoc.version>3.0.1</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version> <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.3.0</fastexcel.version> <fastexcel.version>1.3.0</fastexcel.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<satoken.version>1.44.0</satoken.version> <satoken.version>1.44.0</satoken.version>
<mybatis-plus.version>3.5.14</mybatis-plus.version> <mybatis-plus.version>3.5.15</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.40</hutool.version> <hutool.version>5.8.40</hutool.version>
<spring-boot-admin.version>3.5.5</spring-boot-admin.version> <spring-boot-admin.version>3.5.6</spring-boot-admin.version>
<redisson.version>3.52.0</redisson.version> <redisson.version>4.1.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version> <dynamic-ds.version>4.5.0</dynamic-ds.version>
<snailjob.version>1.9.0</snailjob.version> <snailjob.version>1.9.0</snailjob.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version> <mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version> <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
@@ -38,7 +38,7 @@
<bouncycastle.version>1.80</bouncycastle.version> <bouncycastle.version>1.80</bouncycastle.version>
<justauth.version>1.16.7</justauth.version> <justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>3.3.1</ip2region.version> <ip2region.version>3.3.2</ip2region.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version> <aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 --> <!-- SMS 配置 -->
@@ -185,7 +185,7 @@
<!-- dynamic-datasource 多数据源--> <!-- dynamic-datasource 多数据源-->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
<version>${dynamic-ds.version}</version> <version>${dynamic-ds.version}</version>
</dependency> </dependency>
@@ -197,7 +197,7 @@
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>

View File

@@ -1,10 +1,7 @@
package org.dromara.web.controller; package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -18,7 +15,10 @@ import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody; import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.RegisterBody; import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.domain.model.SocialLoginBody; import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.utils.*; import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter; import org.dromara.common.ratelimiter.annotation.RateLimiter;
@@ -29,29 +29,20 @@ import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils; import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.sse.dto.SseMessageDto; import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils; import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysClientService; import org.dromara.system.service.ISysClientService;
import org.dromara.system.service.ISysConfigService; import org.dromara.system.service.ISysConfigService;
import org.dromara.system.service.ISysSocialService; import org.dromara.system.service.ISysSocialService;
import org.dromara.system.service.ISysTenantService;
import org.dromara.web.domain.vo.LoginTenantVo; import org.dromara.web.domain.vo.LoginTenantVo;
import org.dromara.web.domain.vo.LoginVo; import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.domain.vo.TenantListVo;
import org.dromara.web.service.IAuthStrategy; import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService; import org.dromara.web.service.SysLoginService;
import org.dromara.web.service.SysRegisterService; import org.dromara.web.service.SysRegisterService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -71,7 +62,6 @@ public class AuthController {
private final SysLoginService loginService; private final SysLoginService loginService;
private final SysRegisterService registerService; private final SysRegisterService registerService;
private final ISysConfigService configService; private final ISysConfigService configService;
private final ISysTenantService tenantService;
private final ISysSocialService socialUserService; private final ISysSocialService socialUserService;
private final ISysClientService clientService; private final ISysClientService clientService;
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
@@ -99,8 +89,6 @@ public class AuthController {
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) { } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked")); return R.fail(MessageUtils.message("auth.grant.type.blocked"));
} }
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
// 登录 // 登录
LoginVo loginVo = IAuthStrategy.login(body, client, grantType); LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
@@ -121,18 +109,13 @@ public class AuthController {
* @return 结果 * @return 结果
*/ */
@GetMapping("/binding/{source}") @GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source, public R<String> authBinding(@PathVariable("source") String source) {
@RequestParam String tenantId, @RequestParam String domain) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source); SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) { if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持"); return R.fail(source + "平台账号暂不支持");
} }
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties); AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
Map<String, String> map = new HashMap<>(); String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
map.put("tenantId", tenantId);
map.put("domain", domain);
map.put("state", AuthStateUtils.createState());
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
return R.ok("操作成功", authorizeUrl); return R.ok("操作成功", authorizeUrl);
} }
@@ -189,7 +172,7 @@ public class AuthController {
@ApiEncrypt @ApiEncrypt
@PostMapping("/register") @PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) { public R<Void> register(@Validated @RequestBody RegisterBody user) {
if (!configService.selectRegisterEnabled(user.getTenantId())) { if (!configService.selectRegisterEnabled()) {
return R.fail("当前系统没有开启注册功能!"); return R.fail("当前系统没有开启注册功能!");
} }
registerService.register(user); registerService.register(user);
@@ -204,39 +187,9 @@ public class AuthController {
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP) @RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
@GetMapping("/tenant/list") @GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception { public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象 // 暂时预留给前端使用 后续删除
LoginTenantVo result = new LoginTenantVo(); LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable(); result.setTenantEnabled(false);
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
// 这里从referer中取值是为了本地使用hosts添加虚拟域名方便本地环境调试
host = referer.split("//")[1].split("/")[0];
} else {
host = new URL(request.getRequestURL().toString()).getHost();
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result); return R.ok(result);
} }

View File

@@ -2,8 +2,6 @@ package org.dromara.web.domain.vo;
import lombok.Data; import lombok.Data;
import java.util.List;
/** /**
* 登录租户对象 * 登录租户对象
* *
@@ -17,9 +15,4 @@ public class LoginTenantVo {
*/ */
private Boolean tenantEnabled; private Boolean tenantEnabled;
/**
* 租户对象列表
*/
private List<TenantListVo> voList;
} }

View File

@@ -1,31 +0,0 @@
package org.dromara.web.domain.vo;
import org.dromara.system.domain.vo.SysTenantVo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
/**
* 租户列表
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysTenantVo.class)
public class TenantListVo {
/**
* 租户编号
*/
private String tenantId;
/**
* 企业名称
*/
private String companyName;
/**
* 域名
*/
private String domain;
}

View File

@@ -1,9 +1,7 @@
package org.dromara.web.listener; package org.dromara.web.listener;
import cn.dev33.satoken.listener.SaTokenListener; import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter; import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -18,7 +16,6 @@ import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent; import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.web.service.SysLoginService; import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -51,21 +48,18 @@ public class UserActionListener implements SaTokenListener {
dto.setLoginTime(System.currentTimeMillis()); dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue); dto.setTokenId(tokenValue);
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY); String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username); dto.setUserName(username);
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY)); dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginParameter.getDeviceType()); dto.setDeviceType(loginParameter.getDeviceType());
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY)); dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> { if (loginParameter.getTimeout() == -1) {
if(loginParameter.getTimeout() == -1) { RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto); } else {
} else { RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout())); }
}
});
// 记录登录日志 // 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent(); LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username); logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS); logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success")); logininforEvent.setMessage(MessageUtils.message("user.login.success"));
@@ -81,10 +75,7 @@ public class UserActionListener implements SaTokenListener {
*/ */
@Override @Override
public void doLogout(String loginType, Object loginId, String tokenValue) { public void doLogout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue); log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
} }
@@ -93,10 +84,7 @@ public class UserActionListener implements SaTokenListener {
*/ */
@Override @Override
public void doKickout(String loginType, Object loginId, String tokenValue) { public void doKickout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue); log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
} }
@@ -105,10 +93,7 @@ public class UserActionListener implements SaTokenListener {
*/ */
@Override @Override
public void doReplaced(String loginType, Object loginId, String tokenValue) { public void doReplaced(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY)); RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue); log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
} }

View File

@@ -12,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants; import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants; import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.PostDTO; import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO; import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
@@ -25,8 +23,6 @@ import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.exception.TenantException;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser; import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysSocialBo; import org.dromara.system.domain.bo.SysSocialBo;
import org.dromara.system.domain.vo.*; import org.dromara.system.domain.vo.*;
@@ -36,7 +32,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Duration; import java.time.Duration;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -56,7 +51,6 @@ public class SysLoginService {
@Value("${user.password.lockTime}") @Value("${user.password.lockTime}")
private Integer lockTime; private Integer lockTime;
private final ISysTenantService tenantService;
private final ISysPermissionService permissionService; private final ISysPermissionService permissionService;
private final ISysSocialService sysSocialService; private final ISysSocialService sysSocialService;
private final ISysRoleService roleService; private final ISysRoleService roleService;
@@ -113,11 +107,7 @@ public class SysLoginService {
if (ObjectUtil.isNull(loginUser)) { if (ObjectUtil.isNull(loginUser)) {
return; return;
} }
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) { recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) { } catch (NotLoginException ignored) {
} finally { } finally {
try { try {
@@ -130,14 +120,12 @@ public class SysLoginService {
/** /**
* 记录登录信息 * 记录登录信息
* *
* @param tenantId 租户ID
* @param username 用户名 * @param username 用户名
* @param status 状态 * @param status 状态
* @param message 消息内容 * @param message 消息内容
*/ */
public void recordLogininfor(String tenantId, String username, String status, String message) { public void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent(); LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username); logininforEvent.setUsername(username);
logininforEvent.setStatus(status); logininforEvent.setStatus(status);
logininforEvent.setMessage(message); logininforEvent.setMessage(message);
@@ -151,7 +139,6 @@ public class SysLoginService {
public LoginUser buildLoginUser(SysUserVo user) { public LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser(); LoginUser loginUser = new LoginUser();
Long userId = user.getUserId(); Long userId = user.getUserId();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(userId); loginUser.setUserId(userId);
loginUser.setDeptId(user.getDeptId()); loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName()); loginUser.setUsername(user.getUserName());
@@ -188,7 +175,7 @@ public class SysLoginService {
/** /**
* 登录校验 * 登录校验
*/ */
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) { public void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username; String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL; String loginFail = Constants.LOGIN_FAIL;
@@ -196,7 +183,7 @@ public class SysLoginService {
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0); int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
// 锁定时间内登录 则踢出 // 锁定时间内登录 则踢出
if (errorNumber >= maxRetryCount) { if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} }
@@ -206,11 +193,11 @@ public class SysLoginService {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime)); RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
// 达到规定错误次数 则锁定登录 // 达到规定错误次数 则锁定登录
if (errorNumber >= maxRetryCount) { if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else { } else {
// 未达到规定错误次数 // 未达到规定错误次数
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber)); recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber); throw new UserException(loginType.getRetryLimitCount(), errorNumber);
} }
} }
@@ -219,33 +206,4 @@ public class SysLoginService {
RedisUtils.deleteObject(errorKey); RedisUtils.deleteObject(errorKey);
} }
/**
* 校验租户
*
* @param tenantId 租户ID
*/
public void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
} }

View File

@@ -16,7 +16,6 @@ import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.event.LogininforEvent; import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties; import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser; import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserBo;
@@ -41,7 +40,6 @@ public class SysRegisterService {
* 注册 * 注册
*/ */
public void register(RegisterBody registerBody) { public void register(RegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername(); String username = registerBody.getUsername();
String password = registerBody.getPassword(); String password = registerBody.getPassword();
// 校验用户类型是否存在 // 校验用户类型是否存在
@@ -50,7 +48,7 @@ public class SysRegisterService {
boolean captchaEnabled = captchaProperties.getEnable(); boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关 // 验证码开关
if (captchaEnabled) { if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid()); validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
} }
SysUserBo sysUser = new SysUserBo(); SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username); sysUser.setUserName(username);
@@ -58,18 +56,16 @@ public class SysRegisterService {
sysUser.setPassword(BCrypt.hashpw(password)); sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType); sysUser.setUserType(userType);
boolean exist = TenantHelper.dynamic(tenantId, () -> { boolean exist = userMapper.exists(new LambdaQueryWrapper<SysUser>()
return userMapper.exists(new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUserName, sysUser.getUserName()));
.eq(SysUser::getUserName, sysUser.getUserName()));
});
if (exist) { if (exist) {
throw new UserException("user.register.save.error", username); throw new UserException("user.register.save.error", username);
} }
boolean regFlag = userService.registerUser(sysUser, tenantId); boolean regFlag = userService.registerUser(sysUser);
if (!regFlag) { if (!regFlag) {
throw new UserException("user.register.error"); throw new UserException("user.register.error");
} }
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success")); recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"));
} }
/** /**
@@ -79,16 +75,16 @@ public class SysRegisterService {
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
*/ */
public void validateCaptcha(String tenantId, String username, String code, String uuid) { public void validateCaptcha(String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, ""); String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
if (!StringUtils.equalsIgnoreCase(code, captcha)) { if (!StringUtils.equalsIgnoreCase(code, captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException(); throw new CaptchaException();
} }
} }
@@ -96,15 +92,13 @@ public class SysRegisterService {
/** /**
* 记录登录信息 * 记录登录信息
* *
* @param tenantId 租户ID
* @param username 用户名 * @param username 用户名
* @param status 状态 * @param status 状态
* @param message 消息内容 * @param message 消息内容
* @return * @return
*/ */
private void recordLogininfor(String tenantId, String username, String status, String message) { private void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent(); LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username); logininforEvent.setUsername(username);
logininforEvent.setStatus(status); logininforEvent.setStatus(status);
logininforEvent.setMessage(message); logininforEvent.setMessage(message);

View File

@@ -20,7 +20,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser; import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.vo.SysUserVo;
@@ -47,15 +46,12 @@ public class EmailAuthStrategy implements IAuthStrategy {
public LoginVo login(String body, SysClientVo client) { public LoginVo login(String body, SysClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class); EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody); ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail(); String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode(); String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { SysUserVo user = loadUserByEmail(email);
SysUserVo user = loadUserByEmail(email); loginService.checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode)); // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 LoginUser loginUser = loginService.buildLoginUser(user);
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = new SaLoginParameter();
@@ -78,10 +74,10 @@ public class EmailAuthStrategy implements IAuthStrategy {
/** /**
* 校验邮箱验证码 * 校验邮箱验证码
*/ */
private boolean validateEmailCode(String tenantId, String email, String emailCode) { private boolean validateEmailCode(String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email); String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) { if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); loginService.recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
return code.equals(emailCode); return code.equals(emailCode);

View File

@@ -22,7 +22,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties; import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser; import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
@@ -51,7 +50,6 @@ public class PasswordAuthStrategy implements IAuthStrategy {
public LoginVo login(String body, SysClientVo client) { public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class); PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody); ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername(); String username = loginBody.getUsername();
String password = loginBody.getPassword(); String password = loginBody.getPassword();
String code = loginBody.getCode(); String code = loginBody.getCode();
@@ -60,14 +58,12 @@ public class PasswordAuthStrategy implements IAuthStrategy {
boolean captchaEnabled = captchaProperties.getEnable(); boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关 // 验证码开关
if (captchaEnabled) { if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid); validateCaptcha(username, code, uuid);
} }
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { SysUserVo user = loadUserByUsername(username);
SysUserVo user = loadUserByUsername(username); loginService.checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); // 此处可根据登录用户的数据不同 自行创建 loginUser
// 此处可根据登录用户的数据不同 自行创建 loginUser LoginUser loginUser = loginService.buildLoginUser(user);
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = new SaLoginParameter();
@@ -94,16 +90,16 @@ public class PasswordAuthStrategy implements IAuthStrategy {
* @param code 验证码 * @param code 验证码
* @param uuid 唯一标识 * @param uuid 唯一标识
*/ */
private void validateCaptcha(String tenantId, String username, String code, String uuid) { private void validateCaptcha(String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, ""); String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { if (captcha == null) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); loginService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
if (!StringUtils.equalsIgnoreCase(code, captcha)) { if (!StringUtils.equalsIgnoreCase(code, captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")); loginService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException(); throw new CaptchaException();
} }
} }

View File

@@ -20,7 +20,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser; import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.vo.SysUserVo;
@@ -47,15 +46,12 @@ public class SmsAuthStrategy implements IAuthStrategy {
public LoginVo login(String body, SysClientVo client) { public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class); SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody); ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber(); String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode(); String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { SysUserVo user = loadUserByPhonenumber(phonenumber);
SysUserVo user = loadUserByPhonenumber(phonenumber); loginService.checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 LoginUser loginUser = loginService.buildLoginUser(user);
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = new SaLoginParameter();
@@ -78,10 +74,10 @@ public class SmsAuthStrategy implements IAuthStrategy {
/** /**
* 校验短信验证码 * 校验短信验证码
*/ */
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) { private boolean validateSmsCode(String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber); String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) { if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); loginService.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
return code.equals(smsCode); return code.equals(smsCode);

View File

@@ -13,13 +13,11 @@ import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SocialLoginBody; import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException; import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils; import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialProperties; import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils; import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.vo.SysClientVo; import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysSocialVo; import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.domain.vo.SysUserVo;
@@ -31,7 +29,6 @@ import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* 第三方授权策略 * 第三方授权策略
@@ -70,21 +67,9 @@ public class SocialAuthStrategy implements IAuthStrategy {
if (CollUtil.isEmpty(list)) { if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!"); throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
} }
SysSocialVo social; SysUserVo user = loadUser(list.get(0).getUserId());
if (TenantHelper.isEnable()) { // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId())); LoginUser loginUser = loginService.buildLoginUser(user);
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
social = opt.get();
} else {
social = list.get(0);
}
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
SysUserVo user = loadUser(social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey()); loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); loginUser.setDeviceType(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter(); SaLoginParameter model = new SaLoginParameter();

View File

@@ -67,7 +67,6 @@ public class XcxAuthStrategy implements IAuthStrategy {
SysUserVo user = loadUserByOpenid(openid); SysUserVo user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了 // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
XcxLoginUser loginUser = new XcxLoginUser(); XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId()); loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName()); loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName()); loginUser.setNickname(user.getNickName());

View File

@@ -5,20 +5,15 @@ server:
servlet: servlet:
# 应用的访问路径 # 应用的访问路径
context-path: / context-path: /
# undertow 配置 # jetty 配置
undertow: jetty:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的 # HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1 max-http-form-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads: threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程 # 最小线程数
io: 8 min: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载 # 最大线程数
worker: 256 max: 256
captcha: captcha:
# 是否启用验证码校验 # 是否启用验证码校验
@@ -119,23 +114,6 @@ security:
- /*/api-docs/** - /*/api-docs/**
- /warm-flow-ui/config - /warm-flow-ui/config
# 多租户配置
tenant:
# 是否开启
enable: true
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
- sys_client
- sys_oss_config
- flow_spel
# MyBatisPlus配置 # MyBatisPlus配置
# https://baomidou.com/config/ # https://baomidou.com/config/
mybatis-plus: mybatis-plus:
@@ -263,3 +241,43 @@ warm-flow:
node-tooltip: true node-tooltip: true
# 默认Authorization如果有多个token用逗号分隔 # 默认Authorization如果有多个token用逗号分隔
token-name: ${sa-token.token-name},clientid token-name: ${sa-token.token-name},clientid
--- # mqtt 配置
# 具体配置还需查看文档
# https://mica-mqtt.dreamlu.net/guide/spring/client.html
mqtt.client:
# 是否开启客户端默认true
enabled: false
# 连接的服务端 ip 默认127.0.0.1
ip: 127.0.0.1
# 端口默认1883
port: 1883
# 客户端名称
name: Mqtt-Client
# 客户端Id非常重要一般为设备 sn不可重复
client-id: 000001
username: ruoyi
password: 123456
# 超时时间单位默认5秒
timeout: 5
# 重连时间,默认 5000 毫秒
re-interval: 5000
# mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5默认mqtt_3_1_1
version: mqtt_3_1_1
# 接收数据的 buffer size默认8k
read-buffer-size: 8KB
# 消息解析最大 bytes 长度默认10M
max-bytes-in-message: 10MB
# keep-alive 时间,单位:秒
keep-alive-secs: 60
# 开启保留 session 时session 的有效期
session-expiry-interval-secs: 0
# 工作线程数,如果消息量比较大,例如做 emqx 的转发消息处理,可以调大此参数
biz-thread-pool-size: 2
# 是否开启 ssl
ssl:
enabled: false
keystore-path:
keystore-pass:
truststore-path:
truststore-pass:

View File

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在. user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误 user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次 user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟 user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除 user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员 user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员 role.blocked=角色已封禁,请联系管理员
@@ -47,16 +47,12 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试 rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空 sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次 sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空 email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次 email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟 email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
xcx.code.not.blank=小程序[code]不能为空 xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空 social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空 social.code.not.blank=第三方登录平台[code]不能为空
social.state.not.blank=第三方登录平台[state]不能为空 social.state.not.blank=第三方登录平台[state]不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

View File

@@ -55,8 +55,4 @@ xcx.code.not.blank=Mini program [code] cannot be blank
social.source.not.blank=Social login platform [source] cannot be blank social.source.not.blank=Social login platform [source] cannot be blank
social.code.not.blank=Social login platform [code] cannot be blank social.code.not.blank=Social login platform [code] cannot be blank
social.state.not.blank=Social login platform [state] cannot be blank social.state.not.blank=Social login platform [state] cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
tenant.expired=Sorry, your tenant has expired. Please contact the administrator.

View File

@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
user.not.exists=对不起, 您的账号:{0} 不存在. user.not.exists=对不起, 您的账号:{0} 不存在.
user.password.not.match=用户不存在/密码错误 user.password.not.match=用户不存在/密码错误
user.password.retry.limit.count=密码输入错误{0}次 user.password.retry.limit.count=密码输入错误{0}次
user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟 user.password.retry.limit.exceed=密码输入错误{0}次,户锁定{1}分钟
user.password.delete=对不起,您的账号:{0} 已被删除 user.password.delete=对不起,您的账号:{0} 已被删除
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员 user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
role.blocked=角色已封禁,请联系管理员 role.blocked=角色已封禁,请联系管理员
@@ -47,16 +47,12 @@ repeat.submit.message=不允许重复提交,请稍候再试
rate.limiter.message=访问过于频繁,请稍候再试 rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空 sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次 sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟 sms.code.retry.limit.exceed=短信验证码输入错误{0}次,户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空 email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次 email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟 email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,户锁定{1}分钟
xcx.code.not.blank=小程序[code]不能为空 xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空 social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空 social.code.not.blank=第三方登录平台[code]不能为空
social.state.not.blank=第三方登录平台[state]不能为空 social.state.not.blank=第三方登录平台[state]不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
tenant.blocked=对不起,您的租户已禁用,请联系管理员
tenant.expired=对不起,您的租户已过期,请联系管理员

View File

@@ -31,9 +31,9 @@
<module>ruoyi-common-sensitive</module> <module>ruoyi-common-sensitive</module>
<module>ruoyi-common-json</module> <module>ruoyi-common-json</module>
<module>ruoyi-common-encrypt</module> <module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-websocket</module> <module>ruoyi-common-websocket</module>
<module>ruoyi-common-sse</module> <module>ruoyi-common-sse</module>
<module>ruoyi-common-mqtt</module>
</modules> </modules>
<artifactId>ruoyi-common</artifactId> <artifactId>ruoyi-common</artifactId>

View File

@@ -158,13 +158,6 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 租户模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
<version>${revision}</version>
</dependency>
<!-- WebSocket模块 --> <!-- WebSocket模块 -->
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
@@ -179,6 +172,12 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mqtt</artifactId>
<version>${revision}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@@ -36,7 +36,7 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency> </dependency>
<!--常用工具类 --> <!--常用工具类 -->

View File

@@ -3,7 +3,7 @@ package org.dromara.common.core.config;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

View File

@@ -36,11 +36,6 @@ public interface CacheNames {
*/ */
String SYS_DICT_TYPE = "sys_dict_type"; String SYS_DICT_TYPE = "sys_dict_type";
/**
* 租户
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/** /**
* 客户端 * 客户端
*/ */

View File

@@ -72,6 +72,11 @@ public interface SystemConstants {
*/ */
Long SUPER_ADMIN_ID = 1L; Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
/** /**
* 根部门祖级列表 * 根部门祖级列表
*/ */

View File

@@ -1,35 +0,0 @@
package org.dromara.common.core.constant;
/**
* 租户常量信息
*
* @author Lion Li
*/
public interface TenantConstants {
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "admin";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "管理员";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
}

View File

@@ -61,7 +61,7 @@ public class UserDTO implements Serializable {
private String sex; private String sex;
/** /**
* 号状态0正常 1停用 * 号状态0正常 1停用
*/ */
private String status; private String status;

View File

@@ -16,11 +16,6 @@ public class ProcessDeleteEvent implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 流程定义编码 * 流程定义编码
*/ */

View File

@@ -17,11 +17,6 @@ public class ProcessEvent implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 流程定义编码 * 流程定义编码
*/ */

View File

@@ -17,11 +17,6 @@ public class ProcessTaskEvent implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 流程定义编码 * 流程定义编码
*/ */

View File

@@ -30,11 +30,6 @@ public class LoginBody implements Serializable {
@NotBlank(message = "{auth.grant.type.not.blank}") @NotBlank(message = "{auth.grant.type.not.blank}")
private String grantType; private String grantType;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 验证码 * 验证码
*/ */

View File

@@ -22,11 +22,6 @@ public class LoginUser implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 用户ID * 用户ID
*/ */

View File

@@ -61,13 +61,6 @@ public interface WorkflowService {
*/ */
Long getInstanceIdByBusinessId(String businessId); Long getInstanceIdByBusinessId(String businessId);
/**
* 新增租户流程定义
*
* @param tenantId 租户id
*/
void syncDef(String tenantId);
/** /**
* 启动流程 * 启动流程
* *

View File

@@ -2,7 +2,7 @@ package org.dromara.common.core.utils;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.thread.Threading;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,5 @@
package org.dromara.common.core.utils.ip; package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
@@ -9,7 +8,6 @@ import org.lionsoul.ip2region.service.Config;
import org.lionsoul.ip2region.service.Ip2Region; import org.lionsoul.ip2region.service.Ip2Region;
import org.lionsoul.ip2region.xdb.Util; import org.lionsoul.ip2region.xdb.Util;
import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
@@ -31,6 +29,11 @@ public class RegionUtils {
// 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb // 下载地址https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb"; public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
// 默认缓存切片大小为15MB仅针对BufferCache全量读取有效如果你的xdb数据库很大合理设置该值可以有效提升BufferCache模式下的查询效率具体可以查看Ip2Region的README
// 注意设置过大的值可能会申请内存时因内存不足而导致OOM请合理设置该值。
// READMEhttps://gitee.com/lionsoul/ip2region/tree/master/binding/java
public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
// 未知地址 // 未知地址
public static final String UNKNOWN_ADDRESS = "未知"; public static final String UNKNOWN_ADDRESS = "未知";
@@ -43,20 +46,18 @@ public class RegionUtils {
// 注意Ip2Region 的xdb文件加载策略 CachePolicy 有三种分别是BufferCache全量读取xdb到内存中、VIndexCache默认策略按需读取并缓存、NoCache实时读取 // 注意Ip2Region 的xdb文件加载策略 CachePolicy 有三种分别是BufferCache全量读取xdb到内存中、VIndexCache默认策略按需读取并缓存、NoCache实时读取
// 本项目工具使用的 CachePolicy 为 BufferCacheBufferCache会加载整个xdb文件到内存中setXdbInputStream 仅支持 BufferCache 策略。 // 本项目工具使用的 CachePolicy 为 BufferCacheBufferCache会加载整个xdb文件到内存中setXdbInputStream 仅支持 BufferCache 策略。
// 因为加载整个xdb文件会耗费非常大的内存如果你不希望加载整个xdb到内存中更推荐使用 VIndexCache 或 NoCache即实时读取文件策略和 setXdbPath/setXdbFile 加载方法需要注意的一点setXdbPath 和 setXdbFile 不支持读取ClassPath即源码和resource目录中的文件 // 因为加载整个xdb文件会耗费非常大的内存如果你不希望加载整个xdb到内存中更推荐使用 VIndexCache 或 NoCache即实时读取文件策略和 setXdbPath/setXdbFile 加载方法需要注意的一点setXdbPath 和 setXdbFile 不支持读取ClassPath即源码和resource目录中的文件
// 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 NoCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库 // 一般而言更建议把xdb数据库放到一个指定的文件目录中即不打包进jar包中然后使用 VIndexCache + 配合SearcherPool的并发池读取数据更方便随时更新xdb数据库
// TODO 2025年12月23日 Ip2Region封装的 InputStream 读取函数 Searcher.loadContentFromInputStream 在Linux环境下会申请过大的byte[]空间而导致OOM这里先用临时文件的方案解决等后续 Ip2Region 更新解决方案 InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
// 创建临时文件
File v4TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
// IPv4配置 // IPv4配置
Config v4Config = Config.custom() Config v4Config = Config.custom()
.setCachePolicy(Config.BufferCache) .setCachePolicy(Config.BufferCache)
.setXdbFile(v4TempXdb) //.setXdbFile(v4TempXdb)
// .setXdbInputStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH)) .setXdbInputStream(v4InputStream)
//
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.asV4(); .asV4();
// 删除临时文件
v4TempXdb.delete();
// IPv6配置 // IPv6配置
Config v6Config = null; Config v6Config = null;
@@ -64,17 +65,12 @@ public class RegionUtils {
if (v6XdbInputStream == null) { if (v6XdbInputStream == null) {
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH); log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
} else { } else {
// 创建临时文件
File v6TempXdb = FileUtil.writeFromStream(ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH), FileUtil.createTempFile());
v6Config = Config.custom() v6Config = Config.custom()
.setCachePolicy(Config.BufferCache) .setCachePolicy(Config.BufferCache)
.setXdbFile(v6TempXdb) //.setXdbFile(v6TempXdb)
// .setXdbInputStream(v6XdbInputStream) .setXdbInputStream(v6XdbInputStream)
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
.asV6(); .asV6();
// 删除临时文件
v6TempXdb.delete();
} }
// 初始化Ip2Region实例 // 初始化Ip2Region实例

View File

@@ -21,6 +21,11 @@
<artifactId>ruoyi-common-core</artifactId> <artifactId>ruoyi-common-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-web-server</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>

View File

@@ -20,8 +20,8 @@ import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import java.util.ArrayList; import java.util.ArrayList;

View File

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

View File

@@ -21,15 +21,9 @@
<artifactId>ruoyi-common-core</artifactId> <artifactId>ruoyi-common-core</artifactId>
</dependency> </dependency>
<!-- JSON工具类 -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>spring-boot-starter-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,17 +1,16 @@
package org.dromara.common.json.config; package org.dromara.common.json.config;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.json.handler.BigNumberSerializer; import org.dromara.common.json.handler.BigNumberSerializer;
import org.dromara.common.json.handler.CustomDateDeserializer; import org.dromara.common.json.handler.CustomDateDeserializer;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer;
import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.std.ToStringSerializer;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
@@ -30,24 +29,24 @@ import java.util.TimeZone;
public class JacksonConfig { public class JacksonConfig {
@Bean @Bean
public Module registerJavaTimeModule() { public SimpleModule registerJavaTimeModule() {
// 全局配置序列化返回 JSON 处理 // 全局配置序列化返回 JSON 处理
JavaTimeModule javaTimeModule = new JavaTimeModule(); SimpleModule module = new SimpleModule();
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); module.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); module.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); module.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); module.addSerializer(BigDecimal.class, ToStringSerializer.instance);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer()); module.addDeserializer(Date.class, new CustomDateDeserializer());
return javaTimeModule; return module;
} }
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() { public JsonMapperBuilderCustomizer jsonInitCustomizer() {
return builder -> { return builder -> {
builder.timeZone(TimeZone.getDefault()); builder.defaultTimeZone(TimeZone.getDefault());
log.info("初始化 jackson 配置"); log.info("初始化 jackson 配置");
}; };
} }

View File

@@ -1,11 +1,9 @@
package org.dromara.common.json.handler; package org.dromara.common.json.handler;
import com.fasterxml.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider; import tools.jackson.databind.SerializationContext;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import tools.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer; import tools.jackson.databind.ser.jdk.NumberSerializer;
import java.io.IOException;
/** /**
* 超出 JS 最大最小值 处理 * 超出 JS 最大最小值 处理
@@ -31,7 +29,7 @@ public class BigNumberSerializer extends NumberSerializer {
} }
@Override @Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { public void serialize(Number value, JsonGenerator gen, SerializationContext provider) {
// 超出范围 序列化为字符串 // 超出范围 序列化为字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, provider); super.serialize(value, gen, provider);

View File

@@ -2,12 +2,11 @@ package org.dromara.common.json.handler;
import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.dromara.common.core.utils.ObjectUtils; import org.dromara.common.core.utils.ObjectUtils;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;
import java.io.IOException;
import java.util.Date; import java.util.Date;
/** /**
@@ -15,7 +14,7 @@ import java.util.Date;
* *
* @author AprilWind * @author AprilWind
*/ */
public class CustomDateDeserializer extends JsonDeserializer<Date> { public class CustomDateDeserializer extends ValueDeserializer<Date> {
/** /**
* 反序列化逻辑:将字符串转换为 Date 对象 * 反序列化逻辑:将字符串转换为 Date 对象
@@ -23,10 +22,9 @@ public class CustomDateDeserializer extends JsonDeserializer<Date> {
* @param p JSON 解析器,用于获取字符串值 * @param p JSON 解析器,用于获取字符串值
* @param ctxt 上下文环境(可用于获取更多配置) * @param ctxt 上下文环境(可用于获取更多配置)
* @return 转换后的 Date 对象,若为空字符串返回 null * @return 转换后的 Date 对象,若为空字符串返回 null
* @throws IOException 当字符串格式非法或转换失败时抛出
*/ */
@Override @Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { public Date deserialize(JsonParser p, DeserializationContext ctxt) {
DateTime parse = DateUtil.parse(p.getText()); DateTime parse = DateUtil.parse(p.getText());
if (ObjectUtils.isNull(parse)) { if (ObjectUtils.isNull(parse)) {
return null; return null;

View File

@@ -3,32 +3,29 @@ package org.dromara.common.json.utils;
import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* JSON 工具类 * JSON 工具类
* *
* @author 芋道源码 * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils { public class JsonUtils {
private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class); private static final JsonMapper JSON_MAPPER = SpringUtils.getBean(JsonMapper.class);
public static ObjectMapper getObjectMapper() { public static JsonMapper getJsonMapper() {
return OBJECT_MAPPER; return JSON_MAPPER;
} }
/** /**
@@ -42,11 +39,7 @@ public class JsonUtils {
if (ObjectUtil.isNull(object)) { if (ObjectUtil.isNull(object)) {
return null; return null;
} }
try { return JSON_MAPPER.writeValueAsString(object);
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -62,11 +55,7 @@ public class JsonUtils {
if (StringUtils.isEmpty(text)) { if (StringUtils.isEmpty(text)) {
return null; return null;
} }
try { return JSON_MAPPER.readValue(text, clazz);
return OBJECT_MAPPER.readValue(text, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -82,11 +71,7 @@ public class JsonUtils {
if (ArrayUtil.isEmpty(bytes)) { if (ArrayUtil.isEmpty(bytes)) {
return null; return null;
} }
try { return JSON_MAPPER.readValue(bytes, clazz);
return OBJECT_MAPPER.readValue(bytes, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -102,11 +87,7 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) { if (StringUtils.isBlank(text)) {
return null; return null;
} }
try { return JSON_MAPPER.readValue(text, typeReference);
return OBJECT_MAPPER.readValue(text, typeReference);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -120,14 +101,7 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) { if (StringUtils.isBlank(text)) {
return null; return null;
} }
try { return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructType(Dict.class));
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
} catch (MismatchedInputException e) {
// 类型不匹配说明不是json
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -141,11 +115,7 @@ public class JsonUtils {
if (StringUtils.isBlank(text)) { if (StringUtils.isBlank(text)) {
return null; return null;
} }
try { return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -161,11 +131,7 @@ public class JsonUtils {
if (StringUtils.isEmpty(text)) { if (StringUtils.isEmpty(text)) {
return new ArrayList<>(); return new ArrayList<>();
} }
try { return JSON_MAPPER.readValue(text, JSON_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@@ -179,7 +145,7 @@ public class JsonUtils {
return false; return false;
} }
try { try {
OBJECT_MAPPER.readTree(str); JSON_MAPPER.readTree(str);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@@ -197,7 +163,7 @@ public class JsonUtils {
return false; return false;
} }
try { try {
JsonNode node = OBJECT_MAPPER.readTree(str); JsonNode node = JSON_MAPPER.readTree(str);
return node.isObject(); return node.isObject();
} catch (Exception e) { } catch (Exception e) {
return false; return false;
@@ -215,7 +181,7 @@ public class JsonUtils {
return false; return false;
} }
try { try {
JsonNode node = OBJECT_MAPPER.readTree(str); JsonNode node = JSON_MAPPER.readTree(str);
return node.isArray(); return node.isArray();
} catch (Exception e) { } catch (Exception e) {
return false; return false;

View File

@@ -81,7 +81,6 @@ public class LogAspect {
// *========数据库日志=========*// // *========数据库日志=========*//
OperLogEvent operLog = new OperLogEvent(); OperLogEvent operLog = new OperLogEvent();
operLog.setTenantId(LoginHelper.getTenantId());
operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址 // 请求的地址
String ip = ServletUtils.getClientIP(); String ip = ServletUtils.getClientIP();

View File

@@ -19,11 +19,6 @@ public class LogininforEvent implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 用户账号 * 用户账号
*/ */

View File

@@ -23,11 +23,6 @@ public class OperLogEvent implements Serializable {
*/ */
private Long operId; private Long operId;
/**
* 租户ID
*/
private String tenantId;
/** /**
* 操作模块 * 操作模块
*/ */

View File

@@ -192,7 +192,7 @@ public class MailUtils {
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
* @param mailAccount 邮件户信息 * @param mailAccount 邮件户信息
* @param tos 收件人列表 * @param tos 收件人列表
* @param subject 标题 * @param subject 标题
* @param content 正文 * @param content 正文
@@ -207,7 +207,7 @@ public class MailUtils {
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
* @param mailAccount 邮件户信息 * @param mailAccount 邮件户信息
* @param tos 收件人列表 * @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空 * @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空 * @param bccs 密送人列表可以为null或空
@@ -343,7 +343,7 @@ public class MailUtils {
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
* @param mailAccount 邮件户信息 * @param mailAccount 邮件户信息
* @param tos 收件人列表 * @param tos 收件人列表
* @param subject 标题 * @param subject 标题
* @param content 正文 * @param content 正文
@@ -360,7 +360,7 @@ public class MailUtils {
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
* @param mailAccount 邮件户信息 * @param mailAccount 邮件户信息
* @param tos 收件人列表 * @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空 * @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空 * @param bccs 密送人列表可以为null或空
@@ -400,7 +400,7 @@ public class MailUtils {
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
* @param mailAccount 邮件户信息 * @param mailAccount 邮件户信息
* @param useGlobalSession 是否全局共享Session * @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表 * @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空 * @param ccs 抄送人列表可以为null或空

View File

@@ -9,24 +9,25 @@
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-tenant</artifactId> <artifactId>ruoyi-common-mqtt</artifactId>
<description> <description>
ruoyi-common-tenant 租户模块 ruoyi-common-mqtt 模块
</description> </description>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId> <artifactId>ruoyi-common-core</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId> <artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.mica-mqtt</groupId>
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
<version>2.5.11</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -0,0 +1,32 @@
package org.dromara.common.mqtt.config;
import org.dromara.common.mqtt.listener.MqttClientConnectListener;
import org.dromara.common.mqtt.listener.MqttClientGlobalMessageListener;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
* mqtt客户端配置初始化
* <p>
* 用法文档 <a href="https://mica-mqtt.dreamlu.net/guide/spring/client.html">...</a>
* 测试server搭建:
* 可执行下载其他mqtt服务端搭建
* 也可使用 mica自带的server搭建 <a href="https://mica-mqtt.dreamlu.net/guide/spring/server.html">...</a>
*
* @author Lion Li
*/
@AutoConfiguration
public class MqttAutoConfiguration {
@Bean
public MqttClientConnectListener mqttClientConnectListener(MqttClientCreator mqttClientCreator) {
return new MqttClientConnectListener(mqttClientCreator);
}
@Bean
public MqttClientGlobalMessageListener mqttClientGlobalMessageListener() {
return new MqttClientGlobalMessageListener();
}
}

View File

@@ -0,0 +1,37 @@
package org.dromara.common.mqtt.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.core.client.IMqttClientConnectListener;
import org.dromara.mica.mqtt.core.client.MqttClientCreator;
import org.tio.core.ChannelContext;
/**
* 客户端连接状态监听
*
* @author Lion Li
*/
@Slf4j
public class MqttClientConnectListener implements IMqttClientConnectListener {
//
private final MqttClientCreator mqttClientCreator;
public MqttClientConnectListener(MqttClientCreator mqttClientCreator) {
this.mqttClientCreator = mqttClientCreator;
}
@Override
public void onConnected(ChannelContext context, boolean isReconnect) {
// 创建连接
log.info("MqttConnectedEvent:{}", context);
}
@Override
public void onDisconnect(ChannelContext context, Throwable throwable, String remark, boolean isRemove) {
// 离线时更新重连
log.info("MqttDisconnectEvent:{}", context, throwable);
// 在断线时更新 clientId、username、password
// mqttClientCreator.clientId("newClient" + System.currentTimeMillis())
// .username("newUserName")
// .password("newPassword");
}
}

View File

@@ -0,0 +1,23 @@
package org.dromara.common.mqtt.listener;
import lombok.extern.slf4j.Slf4j;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.client.IMqttClientGlobalMessageListener;
import org.tio.core.ChannelContext;
import java.nio.charset.StandardCharsets;
/**
* 全局消息监听,可以监听到所有订阅消息
*
* @author Lion Li
*/
@Slf4j
public class MqttClientGlobalMessageListener implements IMqttClientGlobalMessageListener {
@Override
public void onMessage(ChannelContext context, String topic, MqttPublishMessage message, byte[] payload) {
log.info("MqttGlobalMessageEvent => topic: {}, msg: {}", topic, new String(payload, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1 @@
org.dromara.common.mqtt.config.MqttAutoConfiguration

View File

@@ -29,12 +29,12 @@
<!-- dynamic-datasource 多数据源--> <!-- dynamic-datasource 多数据源-->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -8,16 +8,13 @@ import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory; import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor; import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler; import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler; import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
@@ -38,12 +35,6 @@ public class MybatisPlusConfig {
@Bean @Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件 必须放到第一位
try {
TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class);
interceptor.addInnerInterceptor(tenant);
} catch (BeansException ignore) {
}
// 数据权限处理 // 数据权限处理
interceptor.addInnerInterceptor(dataPermissionInterceptor()); interceptor.addInnerInterceptor(dataPermissionInterceptor());
// 分页插件 // 分页插件

View File

@@ -66,7 +66,7 @@ public class PlusDataPermissionHandler {
DataPermissionHelper.setVariable("user", currentUser); DataPermissionHelper.setVariable("user", currentUser);
} }
// 如果是超级管理员或租户管理员,则不过滤数据 // 如果是超级管理员或租户管理员,则不过滤数据
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { if (LoginHelper.isSuperAdmin()) {
return where; return where;
} }
// 构造数据过滤条件的 SQL 片段 // 构造数据过滤条件的 SQL 片段

View File

@@ -141,7 +141,8 @@ public class OssClient {
try { try {
// 构建上传请求对象 // 构建上传请求对象
FileUpload fileUpload = transferManager.uploadFile( FileUpload fileUpload = transferManager.uploadFile(
x -> x.putObjectRequest( x -> {
x.source(filePath).putObjectRequest(
y -> y.bucket(properties.getBucketName()) y -> y.bucket(properties.getBucketName())
.key(key) .key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null) .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
@@ -149,10 +150,13 @@ public class OssClient {
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同 // 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置 // 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL()) //.acl(getAccessPolicy().getObjectCannedACL())
.build()) .build()
.addTransferListener(LoggingTransferListener.create()) );
.source(filePath).build()); if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 等待上传完成并获取上传结果 // 等待上传完成并获取上传结果
CompletedFileUpload uploadResult = fileUpload.completionFuture().join(); CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
String eTag = uploadResult.response().eTag(); String eTag = uploadResult.response().eTag();
@@ -192,16 +196,21 @@ public class OssClient {
// 使用 transferManager 进行上传 // 使用 transferManager 进行上传
Upload upload = transferManager.upload( Upload upload = transferManager.upload(
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create()) x -> {
.putObjectRequest( x.requestBody(body).putObjectRequest(
y -> y.bucket(properties.getBucketName()) y -> y.bucket(properties.getBucketName())
.key(key) .key(key)
.contentType(contentType) .contentType(contentType)
// 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同 // 用于设置对象的访问控制列表ACL。不同云厂商对ACL的支持和实现方式有所不同
// 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置 // 因此根据具体的云服务提供商你可能需要进行不同的配置自行开启阿里云有acl权限配置腾讯云没有acl权限配置
//.acl(getAccessPolicy().getObjectCannedACL()) //.acl(getAccessPolicy().getObjectCannedACL())
.build()) .build()
.build()); );
if (log.isDebugEnabled()) {
x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 将输入流写入请求体 // 将输入流写入请求体
body.writeInputStream(inputStream); body.writeInputStream(inputStream);
@@ -229,13 +238,17 @@ public class OssClient {
Path tempFilePath = FileUtils.createTempFile().toPath(); Path tempFilePath = FileUtils.createTempFile().toPath();
// 使用 S3TransferManager 下载文件 // 使用 S3TransferManager 下载文件
FileDownload downloadFile = transferManager.downloadFile( FileDownload downloadFile = transferManager.downloadFile(
x -> x.getObjectRequest( x -> {
x.destination(tempFilePath).getObjectRequest(
y -> y.bucket(properties.getBucketName()) y -> y.bucket(properties.getBucketName())
.key(removeBaseUrl(path)) .key(removeBaseUrl(path))
.build()) .build()
.addTransferListener(LoggingTransferListener.create()) );
.destination(tempFilePath) if (log.isDebugEnabled()) {
.build()); x.addTransferListener(LoggingTransferListener.create());
}
}
);
// 等待文件下载操作完成 // 等待文件下载操作完成
downloadFile.completionFuture().join(); downloadFile.completionFuture().join();
return tempFilePath; return tempFilePath;
@@ -244,8 +257,8 @@ public class OssClient {
/** /**
* 下载文件从 Amazon S3 到 输出流 * 下载文件从 Amazon S3 到 输出流
* *
* @param key 文件在 Amazon S3 中的对象键 * @param key 文件在 Amazon S3 中的对象键
* @param out 输出流 * @param out 输出流
* @param consumer 自定义处理逻辑 * @param consumer 自定义处理逻辑
* @throws OssException 如果下载失败,抛出自定义异常 * @throws OssException 如果下载失败,抛出自定义异常
*/ */
@@ -260,26 +273,24 @@ public class OssClient {
/** /**
* 下载文件从 Amazon S3 到 输出流 * 下载文件从 Amazon S3 到 输出流
* *
* @param key 文件在 Amazon S3 中的对象键 * @param key 文件在 Amazon S3 中的对象键
* @param contentLengthConsumer 文件大小消费者函数 * @param contentLengthConsumer 文件大小消费者函数
* @return 写出订阅器 * @return 写出订阅器
* @throws OssException 如果下载失败,抛出自定义异常 * @throws OssException 如果下载失败,抛出自定义异常
*/ */
public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) { public WriteOutSubscriber<OutputStream> download(String key, Consumer<Long> contentLengthConsumer) {
try { try {
// 构建下载请求 DownloadRequest.TypedBuilder<ResponsePublisher<GetObjectResponse>> typedBuilder = DownloadRequest.builder()
DownloadRequest<ResponsePublisher<GetObjectResponse>> publisherDownloadRequest = DownloadRequest.builder()
// 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName())
.key(key)
.build())
.addTransferListener(LoggingTransferListener.create())
// 使用发布订阅转换器 // 使用发布订阅转换器
.responseTransformer(AsyncResponseTransformer.toPublisher()) .responseTransformer(AsyncResponseTransformer.toPublisher())
.build(); // 文件对象
.getObjectRequest(y -> y.bucket(properties.getBucketName()).key(key).build());
if (log.isDebugEnabled()) {
typedBuilder.addTransferListener(LoggingTransferListener.create());
}
// 使用 S3TransferManager 下载文件 // 使用 S3TransferManager 下载文件
Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(publisherDownloadRequest); Download<ResponsePublisher<GetObjectResponse>> publisherDownload = transferManager.download(typedBuilder.build());
// 获取下载发布订阅转换器 // 获取下载发布订阅转换器
ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result(); ResponsePublisher<GetObjectResponse> publisher = publisherDownload.completionFuture().join().result();
// 执行文件大小消费者函数 // 执行文件大小消费者函数
@@ -289,7 +300,7 @@ public class OssClient {
// 构建写出订阅器对象 // 构建写出订阅器对象
return out -> { return out -> {
// 创建可写入的字节通道 // 创建可写入的字节通道
try(WritableByteChannel channel = Channels.newChannel(out)){ try (WritableByteChannel channel = Channels.newChannel(out)) {
// 订阅数据 // 订阅数据
publisher.subscribe(byteBuffer -> { publisher.subscribe(byteBuffer -> {
while (byteBuffer.hasRemaining()) { while (byteBuffer.hasRemaining()) {
@@ -347,7 +358,7 @@ public class OssClient {
* *
* @param objectKey 对象KEY * @param objectKey 对象KEY
* @param expiredTime 链接授权到期时间 * @param expiredTime 链接授权到期时间
* @param metadata 元数据 * @param metadata 元数据
*/ */
public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) { public String createPresignedPutUrl(String objectKey, Duration expiredTime, Map<String, String> metadata) {
// 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL // 使用 AWS S3 预签名 URL 的生成器 获取上传文件对象的预签名 URL

View File

@@ -48,20 +48,16 @@ public class OssFactory {
} }
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class); OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
// 使用租户标识避免多个租户相同key实例覆盖 // 使用租户标识避免多个租户相同key实例覆盖
String key = configKey; OssClient client = CLIENT_CACHE.get(configKey);
if (StringUtils.isNotBlank(properties.getTenantId())) {
key = properties.getTenantId() + ":" + configKey;
}
OssClient client = CLIENT_CACHE.get(key);
// 客户端不存在或配置不相同则重新构建 // 客户端不存在或配置不相同则重新构建
if (client == null || !client.checkPropertiesSame(properties)) { if (client == null || !client.checkPropertiesSame(properties)) {
LOCK.lock(); LOCK.lock();
try { try {
client = CLIENT_CACHE.get(key); client = CLIENT_CACHE.get(configKey);
if (client == null || !client.checkPropertiesSame(properties)) { if (client == null || !client.checkPropertiesSame(properties)) {
CLIENT_CACHE.put(key, new OssClient(configKey, properties)); CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
log.info("创建OSS实例 key => {}", configKey); log.info("创建OSS实例 key => {}", configKey);
return CLIENT_CACHE.get(key); return CLIENT_CACHE.get(configKey);
} }
} finally { } finally {
LOCK.unlock(); LOCK.unlock();

View File

@@ -10,11 +10,6 @@ import lombok.Data;
@Data @Data
public class OssProperties { public class OssProperties {
/**
* 租户id
*/
private String tenantId;
/** /**
* 访问站点 * 访问站点
*/ */

View File

@@ -28,6 +28,12 @@
<artifactId>redisson-spring-boot-starter</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-cache</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId>

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
@@ -47,7 +48,7 @@ public class RedisConfig {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
ObjectMapper om = new ObjectMapper(); JsonMapper om = new JsonMapper();
om.registerModule(javaTimeModule); om.registerModule(javaTimeModule);
om.setTimeZone(TimeZone.getDefault()); om.setTimeZone(TimeZone.getDefault());
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
@@ -64,6 +65,8 @@ public class RedisConfig {
.setNettyThreads(redissonProperties.getNettyThreads()) .setNettyThreads(redissonProperties.getNettyThreads())
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现) // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
.setUseScriptCache(true) .setUseScriptCache(true)
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setCodec(codec); .setCodec(codec);
if (SpringUtils.isVirtual()) { if (SpringUtils.isVirtual()) {
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-")); config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
@@ -72,8 +75,6 @@ public class RedisConfig {
if (ObjectUtil.isNotNull(singleServerConfig)) { if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式 // 使用单机模式
config.useSingleServer() config.useSingleServer()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(singleServerConfig.getTimeout()) .setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName()) .setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout()) .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
@@ -85,8 +86,6 @@ public class RedisConfig {
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig(); RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) { if (ObjectUtil.isNotNull(clusterServersConfig)) {
config.useClusterServers() config.useClusterServers()
//设置redis key前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setTimeout(clusterServersConfig.getTimeout()) .setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName()) .setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout()) .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())

View File

@@ -1,7 +1,7 @@
package org.dromara.common.redis.handler; package org.dromara.common.redis.handler;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.redisson.api.NameMapper; import org.redisson.config.NameMapper;
/** /**
* redis缓存key前缀处理 * redis缓存key前缀处理

View File

@@ -18,6 +18,7 @@ package org.dromara.common.redis.manager;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RMap; import org.redisson.api.RMap;
import org.redisson.api.RMapCache; import org.redisson.api.RMapCache;
import org.redisson.api.map.event.MapEntryListener;
import org.redisson.spring.cache.CacheConfig; import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonCache; import org.redisson.spring.cache.RedissonCache;
import org.springframework.boot.convert.DurationStyle; import org.springframework.boot.convert.DurationStyle;
@@ -189,6 +190,9 @@ public class PlusSpringCacheManager implements CacheManager {
cache = oldCache; cache = oldCache;
} else { } else {
map.setMaxSize(config.getMaxSize()); map.setMaxSize(config.getMaxSize());
for (MapEntryListener listener : config.getListeners()) {
map.addListener(listener);
}
} }
return cache; return cache;
} }

View File

@@ -3,18 +3,14 @@ package org.dromara.common.satoken.utils;
import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter; import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.UserType; import org.dromara.common.core.enums.UserType;
import java.util.Set;
/** /**
* 登录鉴权助手 * 登录鉴权助手
@@ -32,7 +28,6 @@ import java.util.Set;
public class LoginHelper { public class LoginHelper {
public static final String LOGIN_USER_KEY = "loginUser"; 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_KEY = "userId";
public static final String USER_NAME_KEY = "userName"; public static final String USER_NAME_KEY = "userName";
public static final String DEPT_KEY = "deptId"; public static final String DEPT_KEY = "deptId";
@@ -50,8 +45,7 @@ public class LoginHelper {
public static void login(LoginUser loginUser, SaLoginParameter model) { public static void login(LoginUser loginUser, SaLoginParameter model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter()); model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
StpUtil.login(loginUser.getLoginId(), StpUtil.login(loginUser.getLoginId(),
model.setExtra(TENANT_KEY, loginUser.getTenantId()) model.setExtra(USER_KEY, loginUser.getUserId())
.setExtra(USER_KEY, loginUser.getUserId())
.setExtra(USER_NAME_KEY, loginUser.getUsername()) .setExtra(USER_NAME_KEY, loginUser.getUsername())
.setExtra(DEPT_KEY, loginUser.getDeptId()) .setExtra(DEPT_KEY, loginUser.getDeptId())
.setExtra(DEPT_NAME_KEY, loginUser.getDeptName()) .setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
@@ -105,13 +99,6 @@ public class LoginHelper {
return Convert.toStr(getExtra(USER_NAME_KEY)); return Convert.toStr(getExtra(USER_NAME_KEY));
} }
/**
* 获取租户ID
*/
public static String getTenantId() {
return Convert.toStr(getExtra(TENANT_KEY));
}
/** /**
* 获取部门ID * 获取部门ID
*/ */
@@ -174,32 +161,6 @@ public class LoginHelper {
return isSuperAdmin(getUserId()); return isSuperAdmin(getUserId());
} }
/**
* 是否为租户管理员
*
* @param rolePermission 角色权限标识组
* @return 结果
*/
public static boolean isTenantAdmin(Set<String> rolePermission) {
if (CollUtil.isEmpty(rolePermission)) {
return false;
}
return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
}
/**
* 是否为租户管理员
*
* @return 结果
*/
public static boolean isTenantAdmin() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return false;
}
return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
}
/** /**
* 检查当前用户是否已登录 * 检查当前用户是否已登录
* *

View File

@@ -1,9 +1,9 @@
package org.dromara.common.sensitive.annotation; package org.dromara.common.sensitive.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.dromara.common.sensitive.core.SensitiveStrategy; import org.dromara.common.sensitive.core.SensitiveStrategy;
import org.dromara.common.sensitive.handler.SensitiveHandler; import org.dromara.common.sensitive.handler.SensitiveHandler;
import tools.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -20,6 +20,7 @@ import java.lang.annotation.Target;
@JacksonAnnotationsInside @JacksonAnnotationsInside
@JsonSerialize(using = SensitiveHandler.class) @JsonSerialize(using = SensitiveHandler.class)
public @interface Sensitive { public @interface Sensitive {
SensitiveStrategy strategy(); SensitiveStrategy strategy();
/** /**

View File

@@ -1,20 +1,18 @@
package org.dromara.common.sensitive.handler; package org.dromara.common.sensitive.handler;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonGenerator; import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.sensitive.annotation.Sensitive; import org.dromara.common.sensitive.annotation.Sensitive;
import org.dromara.common.sensitive.core.SensitiveService; import org.dromara.common.sensitive.core.SensitiveService;
import org.dromara.common.sensitive.core.SensitiveStrategy; import org.dromara.common.sensitive.core.SensitiveStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -23,14 +21,14 @@ import java.util.Objects;
* @author Yjoioooo * @author Yjoioooo
*/ */
@Slf4j @Slf4j
public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer { public class SensitiveHandler extends ValueSerializer<String> {
private SensitiveStrategy strategy; private SensitiveStrategy strategy;
private String[] roleKey; private String[] roleKey;
private String[] perms; private String[] perms;
@Override @Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
try { try {
SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class); SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) { if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) {
@@ -45,7 +43,7 @@ public class SensitiveHandler extends JsonSerializer<String> implements Contextu
} }
@Override @Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
Sensitive annotation = property.getAnnotation(Sensitive.class); Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy(); this.strategy = annotation.strategy();
@@ -53,6 +51,6 @@ public class SensitiveHandler extends JsonSerializer<String> implements Contextu
this.perms = annotation.perms(); this.perms = annotation.perms();
return this; return this;
} }
return prov.findValueSerializer(property.getType(), property); return super.createContextual(ctxt, property);
} }
} }

View File

@@ -4,7 +4,7 @@ import org.dromara.common.sms.core.dao.PlusSmsDao;
import org.dromara.common.sms.handler.SmsExceptionHandler; import org.dromara.common.sms.handler.SmsExceptionHandler;
import org.dromara.sms4j.api.dao.SmsDao; import org.dromara.sms4j.api.dao.SmsDao;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Primary;
* *
* @author Feng * @author Feng
*/ */
@AutoConfiguration(after = {RedisAutoConfiguration.class}) @AutoConfiguration(after = {DataRedisAutoConfiguration.class})
public class SmsAutoConfiguration { public class SmsAutoConfiguration {
@Primary @Primary

View File

@@ -1,86 +0,0 @@
package org.dromara.common.tenant.config;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.redis.config.RedisConfig;
import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.tenant.core.TenantSaTokenDao;
import org.dromara.common.tenant.handle.PlusTenantLineHandler;
import org.dromara.common.tenant.handle.TenantKeyPrefixHandler;
import org.dromara.common.tenant.manager.TenantSpringCacheManager;
import org.dromara.common.tenant.properties.TenantProperties;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 租户配置类
*
* @author Lion Li
*/
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
@ConditionalOnClass(TenantLineInnerInterceptor.class)
@AutoConfiguration
static class MybatisPlusConfiguration {
/**
* 多租户插件
*/
@Bean
public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
}
}
@Bean
public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
return config -> {
TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
// 设置多租户 redis key前缀
singleServerConfig.setNameMapper(nameMapper);
}
ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
// 集群配置方式 参考下方注释
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 设置多租户 redis key前缀
clusterServersConfig.setNameMapper(nameMapper);
}
};
}
/**
* 多租户缓存管理器
*/
@Primary
@Bean
public CacheManager tenantCacheManager() {
return new TenantSpringCacheManager();
}
/**
* 多租户鉴权dao实现
*/
@Primary
@Bean
public SaTokenDao tenantSaTokenDao() {
return new TenantSaTokenDao();
}
}

View File

@@ -1,21 +0,0 @@
package org.dromara.common.tenant.core;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 租户基类
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TenantEntity extends BaseEntity {
/**
* 租户编号
*/
private String tenantId;
}

View File

@@ -1,158 +0,0 @@
package org.dromara.common.tenant.core;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
import java.time.Duration;
import java.util.List;
/**
* SaToken 认证数据持久层 适配多租户
*
* @author Lion Li
*/
public class TenantSaTokenDao extends PlusSaTokenDao {
@Override
public String get(String key) {
return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
@Override
public void set(String key, String value, long timeout) {
super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
}
/**
* 修修改指定key-value键值对 (过期时间不变)
*/
@Override
public void update(String key, String value) {
long expire = getTimeout(key);
// -2 = 无此键
if (expire == NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}
/**
* 删除Value
*/
@Override
public void delete(String key) {
super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取Value的剩余存活时间 (单位: 秒)
*/
@Override
public long getTimeout(String key) {
return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 修改Value的剩余存活时间 (单位: 秒)
*/
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == NEVER_EXPIRE) {
long expire = getTimeout(key);
if (expire == NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久那么再次set一次
this.set(key, this.get(key), timeout);
}
return;
}
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
}
/**
* 获取Object如无返空
*/
@Override
public Object getObject(String key) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@Override
public <T> T getObject(String key, Class<T> classType) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
}
/**
* 写入Object并设定存活时间 (单位: 秒)
*/
@Override
public void setObject(String key, Object object, long timeout) {
super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
}
/**
* 更新Object (过期时间不变)
*/
@Override
public void updateObject(String key, Object object) {
long expire = getObjectTimeout(key);
// -2 = 无此键
if (expire == NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}
/**
* 删除Object
*/
@Override
public void deleteObject(String key) {
super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取Object的剩余存活时间 (单位: 秒)
*/
@Override
public long getObjectTimeout(String key) {
return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 修改Object的剩余存活时间 (单位: 秒)
*/
@Override
public void updateObjectTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if (timeout == NEVER_EXPIRE) {
long expire = getObjectTimeout(key);
if (expire == NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久那么再次set一次
this.setObject(key, this.getObject(key), timeout);
}
return;
}
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
}
}

View File

@@ -1,20 +0,0 @@
package org.dromara.common.tenant.exception;
import org.dromara.common.core.exception.base.BaseException;
import java.io.Serial;
/**
* 租户异常类
*
* @author Lion Li
*/
public class TenantException extends BaseException {
@Serial
private static final long serialVersionUID = 1L;
public TenantException(String code, Object... args) {
super("tenant", code, args, null);
}
}

View File

@@ -1,56 +0,0 @@
package org.dromara.common.tenant.handle;
import cn.hutool.core.collection.ListUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.tenant.properties.TenantProperties;
import java.util.List;
/**
* 自定义租户处理器
*
* @author Lion Li
*/
@Slf4j
@AllArgsConstructor
public class PlusTenantLineHandler implements TenantLineHandler {
private final TenantProperties tenantProperties;
@Override
public Expression getTenantId() {
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.error("无法获取有效的租户id -> Null");
return new NullValue();
}
// 返回固定租户
return new StringValue(tenantId);
}
@Override
public boolean ignoreTable(String tableName) {
String tenantId = TenantHelper.getTenantId();
// 判断是否有租户
if (StringUtils.isNotBlank(tenantId)) {
// 不需要过滤租户的表
List<String> excludes = tenantProperties.getExcludes();
// 非业务表
List<String> tables = ListUtil.toList(
"gen_table",
"gen_table_column"
);
tables.addAll(excludes);
return StringUtils.equalsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
}
return true;
}
}

View File

@@ -1,83 +0,0 @@
package org.dromara.common.tenant.handle;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.handler.KeyPrefixHandler;
import org.dromara.common.tenant.helper.TenantHelper;
/**
* 多租户redis缓存key前缀处理
*
* @author Lion Li
*/
@Slf4j
public class TenantKeyPrefixHandler extends KeyPrefixHandler {
public TenantKeyPrefixHandler(String keyPrefix) {
super(keyPrefix);
}
/**
* 增加前缀
*/
@Override
public String map(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
try {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.map(name);
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.map(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.debug("无法获取有效的租户id -> Null");
return super.map(name);
}
if (StringUtils.startsWith(name, tenantId + "")) {
// 如果存在则直接返回
return super.map(name);
}
return super.map(tenantId + ":" + name);
}
/**
* 去除前缀
*/
@Override
public String unmap(String name) {
String unmap = super.unmap(name);
if (StringUtils.isBlank(unmap)) {
return null;
}
try {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return unmap;
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return unmap;
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.debug("无法获取有效的租户id -> Null");
return unmap;
}
if (StringUtils.startsWith(unmap, tenantId + "")) {
// 如果存在则删除
return unmap.substring((tenantId + ":").length());
}
return unmap;
}
}

View File

@@ -1,231 +0,0 @@
package org.dromara.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
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.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import java.util.Stack;
import java.util.function.Supplier;
/**
* 租户助手
*
* @author Lion Li
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TenantHelper {
private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new ThreadLocal<>();
private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
/**
* 租户功能是否启用
*/
public static boolean isEnable() {
return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
}
private static IgnoreStrategy getIgnoreStrategy() {
Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
return ignoreStrategy;
}
}
return null;
}
/**
* 开启忽略租户(开启后需手动调用 {@link #disableIgnore()} 关闭)
*/
private static void enableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNull(ignoreStrategy)) {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
} else {
ignoreStrategy.setTenantLine(true);
}
Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
reentrantStack.push(reentrantStack.size() + 1);
}
/**
* 关闭忽略租户
*/
private static void disableIgnore() {
IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
if (ObjectUtil.isNotNull(ignoreStrategy)) {
boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
&& !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
&& !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
&& !Boolean.TRUE.equals(ignoreStrategy.getDataPermission())
&& CollectionUtil.isEmpty(ignoreStrategy.getOthers());
Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
if (noOtherIgnoreStrategy && empty) {
InterceptorIgnoreHelper.clearIgnoreStrategy();
} else if (empty) {
ignoreStrategy.setTenantLine(false);
}
}
}
/**
* 在忽略租户中执行
*
* @param handle 处理执行方法
*/
public static void ignore(Runnable handle) {
enableIgnore();
try {
handle.run();
} finally {
disableIgnore();
}
}
/**
* 在忽略租户中执行
*
* @param handle 处理执行方法
*/
public static <T> T ignore(Supplier<T> handle) {
enableIgnore();
try {
return handle.get();
} finally {
disableIgnore();
}
}
public static void setDynamic(String tenantId) {
setDynamic(tenantId, false);
}
/**
* 设置动态租户(一直有效 需要手动清理)
* <p>
* 如果为未登录状态下 那么只在当前线程内生效
*
* @param tenantId 租户id
* @param global 是否全局生效
*/
public static void setDynamic(String tenantId, boolean global) {
if (!isEnable()) {
return;
}
if (!LoginHelper.isLogin() || !global) {
TEMP_DYNAMIC_TENANT.set(tenantId);
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.setCacheObject(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
}
/**
* 获取动态租户(一直有效 需要手动清理)
* <p>
* 如果为未登录状态下 那么只在当前线程内生效
*/
public static String getDynamic() {
if (!isEnable()) {
return null;
}
if (!LoginHelper.isLogin()) {
return TEMP_DYNAMIC_TENANT.get();
}
// 如果线程内有值 优先返回
String tenantId = TEMP_DYNAMIC_TENANT.get();
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
SaStorage storage = SaHolder.getStorage();
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
tenantId = storage.getString(cacheKey);
// 如果为 -1 说明已经查过redis并且不存在值 则直接返回null
if (StringUtils.isNotBlank(tenantId)) {
return tenantId.equals("-1") ? null : tenantId;
}
tenantId = RedisUtils.getCacheObject(cacheKey);
storage.set(cacheKey, StringUtils.isBlank(tenantId) ? "-1" : tenantId);
return tenantId;
}
/**
* 清除动态租户
*/
public static void clearDynamic() {
if (!isEnable()) {
return;
}
if (!LoginHelper.isLogin()) {
TEMP_DYNAMIC_TENANT.remove();
return;
}
TEMP_DYNAMIC_TENANT.remove();
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.deleteObject(cacheKey);
SaHolder.getStorage().delete(cacheKey);
}
/**
* 在动态租户中执行
*
* @param handle 处理执行方法
*/
public static void dynamic(String tenantId, Runnable handle) {
setDynamic(tenantId);
try {
handle.run();
} finally {
clearDynamic();
}
}
/**
* 在动态租户中执行
*
* @param handle 处理执行方法
*/
public static <T> T dynamic(String tenantId, Supplier<T> handle) {
setDynamic(tenantId);
try {
return handle.get();
} finally {
clearDynamic();
}
}
/**
* 获取当前租户id(动态租户优先)
*/
public static String getTenantId() {
if (!isEnable()) {
return null;
}
String tenantId = TenantHelper.getDynamic();
if (StringUtils.isBlank(tenantId)) {
tenantId = LoginHelper.getTenantId();
}
return tenantId;
}
}

View File

@@ -1,41 +0,0 @@
package org.dromara.common.tenant.manager;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
import org.dromara.common.tenant.helper.TenantHelper;
import org.springframework.cache.Cache;
/**
* 重写 cacheName 处理方法 支持多租户
*
* @author Lion Li
*/
@Slf4j
public class TenantSpringCacheManager extends PlusSpringCacheManager {
public TenantSpringCacheManager() {
}
@Override
public Cache getCache(String name) {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.getCache(name);
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.getCache(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.error("无法获取有效的租户id -> Null");
}
if (StringUtils.startsWith(name, tenantId)) {
// 如果存在则直接返回
return super.getCache(name);
}
return super.getCache(tenantId + ":" + name);
}
}

View File

@@ -1,27 +0,0 @@
package org.dromara.common.tenant.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* 租户 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
/**
* 是否启用
*/
private Boolean enable;
/**
* 排除表
*/
private List<String> excludes;
}

View File

@@ -1,8 +1,8 @@
package org.dromara.common.translation.annotation; package org.dromara.common.translation.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.dromara.common.translation.core.handler.TranslationHandler; import org.dromara.common.translation.core.handler.TranslationHandler;
import tools.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*; import java.lang.annotation.*;
@@ -11,10 +11,8 @@ import java.lang.annotation.*;
* *
* @author Lion Li * @author Lion Li
*/ */
@Inherited
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD}) @Target({ElementType.FIELD, ElementType.METHOD})
@Documented
@JacksonAnnotationsInside @JacksonAnnotationsInside
@JsonSerialize(using = TranslationHandler.class) @JsonSerialize(using = TranslationHandler.class)
public @interface Translation { public @interface Translation {

View File

@@ -1,14 +1,16 @@
package org.dromara.common.translation.config; package org.dromara.common.translation.config;
import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.translation.annotation.TranslationType; import org.dromara.common.translation.annotation.TranslationType;
import org.dromara.common.translation.core.TranslationInterface; import org.dromara.common.translation.core.TranslationInterface;
import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier; import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier;
import org.dromara.common.translation.core.handler.TranslationHandler; import org.dromara.common.translation.core.handler.TranslationHandler;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.ser.SerializerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -26,9 +28,6 @@ public class TranslationConfig {
@Autowired @Autowired
private List<TranslationInterface<?>> list; private List<TranslationInterface<?>> list;
@Autowired
private ObjectMapper objectMapper;
@PostConstruct @PostConstruct
public void init() { public void init() {
Map<String, TranslationInterface<?>> map = new HashMap<>(list.size()); Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
@@ -41,10 +40,15 @@ public class TranslationConfig {
} }
} }
TranslationHandler.TRANSLATION_MAPPER.putAll(map); TranslationHandler.TRANSLATION_MAPPER.putAll(map);
// 设置 Bean 序列化修改器 }
objectMapper.setSerializerFactory(
objectMapper.getSerializerFactory() @Bean
.withSerializerModifier(new TranslationBeanSerializerModifier())); public JsonMapperBuilderCustomizer translationInitCustomizer() {
return builder -> {
SerializerFactory serializerFactory = builder.serializerFactory();
serializerFactory = serializerFactory.withSerializerModifier(new TranslationBeanSerializerModifier());
builder.serializerFactory(serializerFactory);
};
} }
} }

View File

@@ -1,9 +1,9 @@
package org.dromara.common.translation.core.handler; package org.dromara.common.translation.core.handler;
import com.fasterxml.jackson.databind.BeanDescription; import tools.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.SerializationConfig; import tools.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import tools.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import tools.jackson.databind.ser.ValueSerializerModifier;
import java.util.List; import java.util.List;
@@ -12,10 +12,10 @@ import java.util.List;
* *
* @author Lion Li * @author Lion Li
*/ */
public class TranslationBeanSerializerModifier extends BeanSerializerModifier { public class TranslationBeanSerializerModifier extends ValueSerializerModifier {
@Override @Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
List<BeanPropertyWriter> beanProperties) { List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) { for (BeanPropertyWriter writer : beanProperties) {
// 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理 // 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理
@@ -23,7 +23,7 @@ public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
writer.assignNullSerializer(serializer); writer.assignNullSerializer(serializer);
} }
} }
return beanProperties; return super.changeProperties(config, beanDesc, beanProperties);
} }
} }

View File

@@ -1,19 +1,17 @@
package org.dromara.common.translation.core.handler; package org.dromara.common.translation.core.handler;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonGenerator; import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.translation.annotation.Translation; import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.core.TranslationInterface; import org.dromara.common.translation.core.TranslationInterface;
import lombok.extern.slf4j.Slf4j; import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -24,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author Lion Li * @author Lion Li
*/ */
@Slf4j @Slf4j
public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer { public class TranslationHandler extends ValueSerializer<Object> {
/** /**
* 全局翻译实现类映射器 * 全局翻译实现类映射器
@@ -34,7 +32,7 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
private Translation translation; private Translation translation;
@Override @Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException {
TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type()); TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
if (ObjectUtil.isNotNull(trans)) { if (ObjectUtil.isNotNull(trans)) {
// 如果映射字段不为空 则取映射字段的值 // 如果映射字段不为空 则取映射字段的值
@@ -48,24 +46,24 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
} }
try { try {
Object result = trans.translation(value, translation.other()); Object result = trans.translation(value, translation.other());
gen.writeObject(result); gen.writePOJO(result);
} catch (Exception e) { } catch (Exception e) {
log.error("翻译处理异常type: {}, value: {}", translation.type(), value, e); log.error("翻译处理异常type: {}, value: {}", translation.type(), value, e);
// 出现异常时输出原始值而不是中断序列化 // 出现异常时输出原始值而不是中断序列化
gen.writeObject(value); gen.writePOJO(value);
} }
} else { } else {
gen.writeObject(value); gen.writePOJO(value);
} }
} }
@Override @Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property) {
Translation translation = property.getAnnotation(Translation.class); Translation translation = property.getAnnotation(Translation.class);
if (Objects.nonNull(translation)) { if (Objects.nonNull(translation)) {
this.translation = translation; this.translation = translation;
return this; return this;
} }
return prov.findValueSerializer(property.getType(), property); return super.createContextual(ctxt, property);
} }
} }

View File

@@ -35,7 +35,7 @@
<!-- web 容器使用 undertow 性能更强 --> <!-- web 容器使用 undertow 性能更强 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId> <artifactId>spring-boot-starter-jetty</artifactId>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -2,7 +2,7 @@ package org.dromara.common.web.config;
import org.dromara.common.web.core.I18nLocaleResolver; import org.dromara.common.web.core.I18nLocaleResolver;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;

View File

@@ -1,63 +1,63 @@
package org.dromara.common.web.config; //package org.dromara.common.web.config;
//
import io.undertow.server.DefaultByteBufferPool; //import io.undertow.server.DefaultByteBufferPool;
import io.undertow.server.handlers.DisallowedMethodsHandler; //import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.util.HttpString; //import io.undertow.util.HttpString;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo; //import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.dromara.common.core.utils.SpringUtils; //import org.dromara.common.core.utils.SpringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration; //import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; //import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer; //import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.task.VirtualThreadTaskExecutor; //import org.springframework.core.task.VirtualThreadTaskExecutor;
//
/** ///**
* Undertow 自定义配置 // * Undertow 自定义配置
* // *
* @author Lion Li // * @author Lion Li
*/ // */
@AutoConfiguration //@AutoConfiguration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> { //public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
//
/** // /**
* 自定义 Undertow 配置 // * 自定义 Undertow 配置
* <p> // * <p>
* 主要配置内容包括: // * 主要配置内容包括:
* 1. 配置 WebSocket 部署信息 // * 1. 配置 WebSocket 部署信息
* 2. 在虚拟线程模式下使用虚拟线程池 // * 2. 在虚拟线程模式下使用虚拟线程池
* 3. 禁用不安全的 HTTP 方法,如 CONNECT、TRACE、TRACK // * 3. 禁用不安全的 HTTP 方法,如 CONNECT、TRACE、TRACK
* </p> // * </p>
* // *
* @param factory Undertow 的 Web 服务器工厂 // * @param factory Undertow 的 Web 服务器工厂
*/ // */
@Override // @Override
public void customize(UndertowServletWebServerFactory factory) { // public void customize(UndertowServletWebServerFactory factory) {
factory.addDeploymentInfoCustomizers(deploymentInfo -> { // factory.addDeploymentInfoCustomizers(deploymentInfo -> {
// 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池 // // 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); // WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024)); // webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo); // deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
//
// 如果启用了虚拟线程,配置 Undertow 使用虚拟线程池 // // 如果启用了虚拟线程,配置 Undertow 使用虚拟线程池
if (SpringUtils.isVirtual()) { // if (SpringUtils.isVirtual()) {
// 创建虚拟线程池,线程池前缀为 "undertow-" // // 创建虚拟线程池,线程池前缀为 "undertow-"
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-"); // VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
// 设置虚拟线程池为执行器和异步执行器 // // 设置虚拟线程池为执行器和异步执行器
deploymentInfo.setExecutor(executor); // deploymentInfo.setExecutor(executor);
deploymentInfo.setAsyncExecutor(executor); // deploymentInfo.setAsyncExecutor(executor);
} // }
//
// 配置禁止某些不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK // // 配置禁止某些不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK
deploymentInfo.addInitialHandlerChainWrapper(handler -> { // deploymentInfo.addInitialHandlerChainWrapper(handler -> {
// 禁止三个方法 CONNECT/TRACE/TRACK 也是不安全的 避免爬虫骚扰 // // 禁止三个方法 CONNECT/TRACE/TRACK 也是不安全的 避免爬虫骚扰
HttpString[] disallowedHttpMethods = { // HttpString[] disallowedHttpMethods = {
HttpString.tryFromString("CONNECT"), // HttpString.tryFromString("CONNECT"),
HttpString.tryFromString("TRACE"), // HttpString.tryFromString("TRACE"),
HttpString.tryFromString("TRACK") // HttpString.tryFromString("TRACK")
}; // };
// 使用 DisallowedMethodsHandler 拦截并拒绝这些方法的请求 // // 使用 DisallowedMethodsHandler 拦截并拒绝这些方法的请求
return new DisallowedMethodsHandler(handler, disallowedHttpMethods); // return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
}); // });
}); // });
} // }
//
} //}

View File

@@ -2,7 +2,6 @@ package org.dromara.common.web.handler;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.core.JsonParseException;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
@@ -14,6 +13,7 @@ import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException; import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.springframework.boot.json.JsonParseException;
import org.springframework.context.MessageSourceResolvable; import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.expression.ExpressionException; import org.springframework.expression.ExpressionException;

View File

@@ -4,10 +4,6 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -19,6 +15,10 @@ import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -45,8 +45,8 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
if (request instanceof RepeatedlyRequestWrapper) { if (request instanceof RepeatedlyRequestWrapper) {
jsonParam = IoUtil.read(request.getReader()); jsonParam = IoUtil.read(request.getReader());
if (StringUtils.isNotBlank(jsonParam)) { if (StringUtils.isNotBlank(jsonParam)) {
ObjectMapper objectMapper = JsonUtils.getObjectMapper(); JsonMapper jsonMapper = JsonUtils.getJsonMapper();
JsonNode rootNode = objectMapper.readTree(jsonParam); JsonNode rootNode = jsonMapper.readTree(jsonParam);
removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES); removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
jsonParam = rootNode.toString(); jsonParam = rootNode.toString();
} }
@@ -79,14 +79,14 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
ObjectNode objectNode = (ObjectNode) node; ObjectNode objectNode = (ObjectNode) node;
// 收集要删除的字段名(避免 ConcurrentModification // 收集要删除的字段名(避免 ConcurrentModification
Set<String> fieldsToRemove = new HashSet<>(); Set<String> fieldsToRemove = new HashSet<>();
objectNode.fieldNames().forEachRemaining(fieldName -> { objectNode.propertyNames().forEach(fieldName -> {
if (ArrayUtil.contains(excludeProperties, fieldName)) { if (ArrayUtil.contains(excludeProperties, fieldName)) {
fieldsToRemove.add(fieldName); fieldsToRemove.add(fieldName);
} }
}); });
fieldsToRemove.forEach(objectNode::remove); fieldsToRemove.forEach(objectNode::remove);
// 递归处理子节点 // 递归处理子节点
objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties)); objectNode.values().forEach(child -> removeSensitiveFields(child, excludeProperties));
} else if (node.isArray()) { } else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node; ArrayNode arrayNode = (ArrayNode) node;
for (JsonNode child : arrayNode) { for (JsonNode child : arrayNode) {

View File

@@ -2,4 +2,3 @@ org.dromara.common.web.config.CaptchaConfig
org.dromara.common.web.config.FilterConfig org.dromara.common.web.config.FilterConfig
org.dromara.common.web.config.I18nConfig org.dromara.common.web.config.I18nConfig
org.dromara.common.web.config.ResourcesConfig org.dromara.common.web.config.ResourcesConfig
org.dromara.common.web.config.UndertowConfig

View File

@@ -26,7 +26,7 @@
<!-- web 容器使用 undertow 性能更强 --> <!-- web 容器使用 undertow 性能更强 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId> <artifactId>spring-boot-starter-jetty</artifactId>
</dependency> </dependency>
<!-- spring security 安全认证 --> <!-- spring security 安全认证 -->

View File

@@ -1,146 +0,0 @@
package com.aizuda.snailjob.server.common.register;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.common.core.enums.NodeTypeEnum;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.core.util.NetUtil;
import com.aizuda.snailjob.common.core.util.SnailJobVersion;
import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.common.log.SnailJobLog;
import com.aizuda.snailjob.server.common.cache.CacheConsumerGroup;
import com.aizuda.snailjob.server.common.config.SystemProperties;
import com.aizuda.snailjob.server.common.convert.RegisterNodeInfoConverter;
import com.aizuda.snailjob.server.common.dto.ServerNodeExtAttrs;
import com.aizuda.snailjob.server.common.handler.InstanceManager;
import com.aizuda.snailjob.template.datasource.persistence.po.ServerNode;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 服务端注册
*
* @author opensnail
* @date 2023-06-07
* @since 1.6.0
*/
@Component(ServerRegister.BEAN_NAME)
@RequiredArgsConstructor
public class ServerRegister extends AbstractRegister {
public static final String BEAN_NAME = "serverRegister";
private final ScheduledExecutorService serverRegisterNode = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "server-register-node"));
public static final int DELAY_TIME = 30;
public static final String CURRENT_CID;
public static final String GROUP_NAME = "DEFAULT_SERVER";
public static final String NAMESPACE_ID = "DEFAULT_SERVER_NAMESPACE_ID";
private final InstanceManager instanceManager;
private final SystemProperties systemProperties;
private final ServerProperties serverProperties;
static {
CURRENT_CID = IdUtil.getSnowflakeNextIdStr();
}
@Override
public boolean supports(int type) {
return getNodeType().equals(type);
}
@Override
protected void beforeProcessor(RegisterContext context) {
// 新增扩展参数
ServerNodeExtAttrs serverNodeExtAttrs = new ServerNodeExtAttrs();
serverNodeExtAttrs.setWebPort(serverProperties.getPort());
serverNodeExtAttrs.setSystemVersion(SnailJobVersion.getVersion());
context.setGroupName(GROUP_NAME);
context.setHostId(CURRENT_CID);
String serverHost = systemProperties.getServerHost();
if (StrUtil.isEmptyIfStr(serverHost)) {
serverHost = NetUtil.getLocalIpStr();
}
context.setHostIp(serverHost);
context.setHostPort(systemProperties.getServerPort());
context.setContextPath(Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse(StrUtil.EMPTY));
context.setNamespaceId(NAMESPACE_ID);
context.setExtAttrs(JsonUtil.toJsonString(serverNodeExtAttrs));
}
@Override
protected LocalDateTime getExpireAt() {
return LocalDateTime.now().plusSeconds(DELAY_TIME);
}
@Override
protected boolean doRegister(RegisterContext context, ServerNode serverNode) {
refreshExpireAt(Lists.newArrayList(serverNode));
return Boolean.TRUE;
}
@Override
protected void afterProcessor(final ServerNode serverNode) {
try {
// 同步当前POD消费的组的节点信息
// netty的client只会注册到一个服务端若组分配的和client连接的不是一个POD则会导致当前POD没有其他客户端的注册信息
ConcurrentMap<String /*groupName*/, Set<String>/*namespaceId*/> allConsumerGroupName = CacheConsumerGroup.getAllConsumerGroupName();
if (CollUtil.isNotEmpty(allConsumerGroupName)) {
Set<String> namespaceIdSets = StreamUtils.toSetByFlatMap(allConsumerGroupName.values(), Set::stream);
if (CollUtil.isEmpty(namespaceIdSets)) {
return;
}
List<ServerNode> serverNodes = serverNodeMapper.selectList(
new LambdaQueryWrapper<ServerNode>()
.eq(ServerNode::getNodeType, NodeTypeEnum.CLIENT.getType())
.in(ServerNode::getNamespaceId, namespaceIdSets)
.in(ServerNode::getGroupName, allConsumerGroupName.keySet()));
for (final ServerNode node : serverNodes) {
// 刷新全量本地缓存
instanceManager.registerOrUpdate(RegisterNodeInfoConverter.INSTANCE.toRegisterNodeInfo(node));
// 刷新过期时间
CacheConsumerGroup.addOrUpdate(node.getGroupName(), node.getNamespaceId());
}
}
} catch (Exception e) {
SnailJobLog.LOCAL.error("Client refresh failed", e);
}
}
@Override
protected Integer getNodeType() {
return NodeTypeEnum.SERVER.getType();
}
@Override
public void start() {
SnailJobLog.LOCAL.info("ServerRegister start");
serverRegisterNode.scheduleAtFixedRate(() -> {
try {
this.register(new RegisterContext());
} catch (Exception e) {
SnailJobLog.LOCAL.error("Server-side registration failed", e);
}
}, 0, DELAY_TIME * 2 / 3, TimeUnit.SECONDS);
}
@Override
public void close() {
SnailJobLog.LOCAL.info("ServerRegister close");
}
}

View File

@@ -95,12 +95,12 @@
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId> <artifactId>ruoyi-common-websocket</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-websocket</artifactId> <artifactId>ruoyi-common-mqtt</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,72 @@
package org.dromara.demo.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.demo.domain.TestDemo;
import org.dromara.mica.mqtt.codec.MqttQoS;
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
import org.dromara.mica.mqtt.core.deserialize.MqttJsonDeserializer;
import org.dromara.mica.mqtt.spring.client.MqttClientTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
/**
* mqtt 演示案例
* <p>
* 用法文档 <a href="https://mica-mqtt.dreamlu.net/guide/spring/client.html">...</a>
* 测试server搭建:
* 可执行下载其他mqtt服务端搭建
* 也可使用 mica自带的server搭建 <a href="https://mica-mqtt.dreamlu.net/guide/spring/server.html">...</a>
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/mqtt")
@Slf4j
public class MqttController {
private final MqttClientTemplate client;
@GetMapping("/send")
public boolean send() {
client.publish("/test/client", "测试测试".getBytes(StandardCharsets.UTF_8));
return true;
}
@MqttClientSubscribe("/test/#")
public void subQos0(String topic, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(value = "/qos1/#", qos = MqttQoS.QOS1)
public void subQos1(String topic, byte[] payload) {
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe("/sys/${productKey}/${deviceName}/thing/sub/register")
public void thingSubRegister(String topic, byte[] payload) {
// 1.3.8 开始支持,@MqttClientSubscribe 注解支持 ${} 变量替换,会默认替换成 +
// 注意mica-mqtt 会先从 Spring boot 配置中替换参数 ${},如果存在配置会优先被替换。
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
}
@MqttClientSubscribe(
value = "/test/json",
deserialize = MqttJsonDeserializer.class // 2.4.5 开始支持 自定义序列化,默认 json 序列化
)
public void testJson(String topic, MqttPublishMessage message, TestDemo data) {
// 2.4.5 开始支持,支持 2 到 3 个参数,字段类型映射规则如下
// String 字符串会默认映射到 topic
// MqttPublishMessage 会默认映射到 原始的消息,可以拿到 mqtt5 的 props 参数
// byte[] 会映射到 mqtt 消息内容 payload
// ByteBuffer 会映射到 mqtt 消息内容 payload
// 其他类型会走序列化,确保消息能够序列化,默认为 json 序列化
log.info("topic:{} json data:{}", topic, data);
}
}

View File

@@ -1,9 +1,9 @@
package org.dromara.demo.domain; package org.dromara.demo.domain;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial; import java.io.Serial;
@@ -16,7 +16,7 @@ import java.io.Serial;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("test_demo") @TableName("test_demo")
public class TestDemo extends TenantEntity { public class TestDemo extends BaseEntity {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version; import com.baomidou.mybatisplus.annotation.Version;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial; import java.io.Serial;
@@ -19,7 +19,7 @@ import java.io.Serial;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("test_tree") @TableName("test_tree")
public class TestTree extends TenantEntity { public class TestTree extends BaseEntity {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -1,15 +1,6 @@
package ${packageName}.domain; package ${packageName}.domain;
#foreach ($column in $columns)
#if($column.javaField=='tenantId')
#set($IsTenant=1)
#end
#end
#if($IsTenant==1)
import org.dromara.common.tenant.core.TenantEntity;
#else
import org.dromara.common.mybatis.core.domain.BaseEntity; import org.dromara.common.mybatis.core.domain.BaseEntity;
#end
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -25,11 +16,7 @@ import java.io.Serial;
* @author ${author} * @author ${author}
* @date ${datetime} * @date ${datetime}
*/ */
#if($IsTenant==1)
#set($Entity="TenantEntity")
#else
#set($Entity="BaseEntity") #set($Entity="BaseEntity")
#end
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("${tableName}") @TableName("${tableName}")

View File

@@ -60,11 +60,6 @@
<artifactId>ruoyi-common-sms</artifactId> <artifactId>ruoyi-common-sms</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId> <artifactId>ruoyi-common-security</artifactId>

View File

@@ -6,7 +6,6 @@ import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.Tree;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.idempotent.annotation.RepeatSubmit;
@@ -22,7 +21,6 @@ import org.dromara.system.service.ISysMenuService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -53,8 +51,7 @@ public class SysMenuController extends BaseController {
* 获取菜单列表 * 获取菜单列表
*/ */
@SaCheckRole(value = { @SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY, SystemConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR) }, mode = SaMode.OR)
@SaCheckPermission("system:menu:list") @SaCheckPermission("system:menu:list")
@GetMapping("/list") @GetMapping("/list")
@@ -69,8 +66,7 @@ public class SysMenuController extends BaseController {
* @param menuId 菜单ID * @param menuId 菜单ID
*/ */
@SaCheckRole(value = { @SaCheckRole(value = {
TenantConstants.SUPER_ADMIN_ROLE_KEY, SystemConstants.SUPER_ADMIN_ROLE_KEY,
TenantConstants.TENANT_ADMIN_ROLE_KEY
}, mode = SaMode.OR) }, mode = SaMode.OR)
@SaCheckPermission("system:menu:query") @SaCheckPermission("system:menu:query")
@GetMapping(value = "/{menuId}") @GetMapping(value = "/{menuId}")
@@ -103,31 +99,10 @@ public class SysMenuController extends BaseController {
return R.ok(selectVo); return R.ok(selectVo);
} }
/**
* 加载对应租户套餐菜单列表树
*
* @param packageId 租户套餐ID
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:query")
@GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
List<Tree<Long>> list = menuService.buildMenuTreeSelect(menus);
// 删除租户管理菜单
list.removeIf(menu -> menu.getId() == 6L);
List<Long> ids = new ArrayList<>();
if (packageId > 0L) {
ids = menuService.selectMenuListByPackageId(packageId);
}
MenuTreeSelectVo selectVo = new MenuTreeSelectVo(ids, list);
return R.ok(selectVo);
}
/** /**
* 新增菜单 * 新增菜单
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckRole(SystemConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:add") @SaCheckPermission("system:menu:add")
@Log(title = "菜单管理", businessType = BusinessType.INSERT) @Log(title = "菜单管理", businessType = BusinessType.INSERT)
@RepeatSubmit() @RepeatSubmit()
@@ -137,6 +112,8 @@ public class SysMenuController extends BaseController {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); return R.fail("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
} else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { } else if (SystemConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return R.fail("新增菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} else if (!menuService.checkRouteConfigUnique(menu)) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
} }
return toAjax(menuService.insertMenu(menu)); return toAjax(menuService.insertMenu(menu));
} }
@@ -144,7 +121,7 @@ public class SysMenuController extends BaseController {
/** /**
* 修改菜单 * 修改菜单
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckRole(SystemConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:edit") @SaCheckPermission("system:menu:edit")
@Log(title = "菜单管理", businessType = BusinessType.UPDATE) @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@RepeatSubmit() @RepeatSubmit()
@@ -156,6 +133,8 @@ public class SysMenuController extends BaseController {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头"); return R.fail("修改菜单'" + menu.getMenuName() + "'失败地址必须以http(s)://开头");
} else if (menu.getMenuId().equals(menu.getParentId())) { } else if (menu.getMenuId().equals(menu.getParentId())) {
return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); return R.fail("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
} else if (!menuService.checkRouteConfigUnique(menu)) {
return R.fail("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在");
} }
return toAjax(menuService.updateMenu(menu)); return toAjax(menuService.updateMenu(menu));
} }
@@ -165,7 +144,7 @@ public class SysMenuController extends BaseController {
* *
* @param menuId 菜单ID * @param menuId 菜单ID
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckRole(SystemConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:remove") @SaCheckPermission("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE) @Log(title = "菜单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{menuId}") @DeleteMapping("/{menuId}")
@@ -193,7 +172,7 @@ public class SysMenuController extends BaseController {
* *
* @param menuIds 菜单ID串 * @param menuIds 菜单ID串
*/ */
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY) @SaCheckRole(SystemConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:menu:remove") @SaCheckPermission("system:menu:remove")
@Log(title = "菜单管理", businessType = BusinessType.DELETE) @Log(title = "菜单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/cascade/{menuIds}") @DeleteMapping("/cascade/{menuIds}")

View File

@@ -1,211 +0,0 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.baomidou.lock.annotation.Lock4j;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysTenantService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 租户管理
*
* @author Michelle.Chung
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant")
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class SysTenantController extends BaseController {
private final ISysTenantService tenantService;
/**
* 查询租户列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:list")
@GetMapping("/list")
public TableDataInfo<SysTenantVo> list(SysTenantBo bo, PageQuery pageQuery) {
return tenantService.queryPageList(bo, pageQuery);
}
/**
* 导出租户列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:export")
@Log(title = "租户管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysTenantBo bo, HttpServletResponse response) {
List<SysTenantVo> list = tenantService.queryList(bo);
ExcelUtil.exportExcel(list, "租户", SysTenantVo.class, response);
}
/**
* 获取租户详细信息
*
* @param id 主键
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:query")
@GetMapping("/{id}")
public R<SysTenantVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(tenantService.queryById(id));
}
/**
* 新增租户
*/
@ApiEncrypt
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:add")
@Log(title = "租户管理", businessType = BusinessType.INSERT)
@Lock4j
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantBo bo) {
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("新增租户'" + bo.getCompanyName() + "'失败,企业名称已存在");
}
return toAjax(TenantHelper.ignore(() -> tenantService.insertByBo(bo)));
}
/**
* 修改租户
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
tenantService.checkTenantAllowed(bo.getTenantId());
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("修改租户'" + bo.getCompanyName() + "'失败,公司名称已存在");
}
return toAjax(tenantService.updateByBo(bo));
}
/**
* 状态修改
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
tenantService.checkTenantAllowed(bo.getTenantId());
return toAjax(tenantService.updateTenantStatus(bo));
}
/**
* 删除租户
*
* @param ids 主键串
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:remove")
@Log(title = "租户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(tenantService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 动态切换租户
*
* @param tenantId 租户ID
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@GetMapping("/dynamic/{tenantId}")
public R<Void> dynamicTenant(@NotBlank(message = "租户ID不能为空") @PathVariable String tenantId) {
TenantHelper.setDynamic(tenantId, true);
return R.ok();
}
/**
* 清除动态租户
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@GetMapping("/dynamic/clear")
public R<Void> dynamicClear() {
TenantHelper.clearDynamic();
return R.ok();
}
/**
* 同步租户套餐
*
* @param tenantId 租户id
* @param packageId 套餐id
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@Lock4j
@GetMapping("/syncTenantPackage")
public R<Void> syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId,
@NotNull(message = "套餐ID不能为空") Long packageId) {
return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId)));
}
/**
* 同步租户字典
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@Log(title = "租户管理", businessType = BusinessType.INSERT)
@Lock4j
@GetMapping("/syncTenantDict")
public R<Void> syncTenantDict() {
if (!TenantHelper.isEnable()) {
return R.fail("当前未开启租户模式");
}
tenantService.syncTenantDict();
return R.ok("同步租户字典成功");
}
/**
* 同步租户参数配置
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@Log(title = "租户管理", businessType = BusinessType.INSERT)
@Lock4j
@GetMapping("/syncTenantConfig")
public R<Void> syncTenantConfig() {
if (!TenantHelper.isEnable()) {
return R.fail("当前未开启租户模式");
}
tenantService.syncTenantConfig();
return R.ok("同步租户参数配置成功");
}
}

View File

@@ -1,143 +0,0 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysTenantPackageBo;
import org.dromara.system.domain.vo.SysTenantPackageVo;
import org.dromara.system.service.ISysTenantPackageService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 租户套餐管理
*
* @author Michelle.Chung
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/tenant/package")
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class SysTenantPackageController extends BaseController {
private final ISysTenantPackageService tenantPackageService;
/**
* 查询租户套餐列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:list")
@GetMapping("/list")
public TableDataInfo<SysTenantPackageVo> list(SysTenantPackageBo bo, PageQuery pageQuery) {
return tenantPackageService.queryPageList(bo, pageQuery);
}
/**
* 查询租户套餐下拉选列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:list")
@GetMapping("/selectList")
public R<List<SysTenantPackageVo>> selectList() {
return R.ok(tenantPackageService.selectList());
}
/**
* 导出租户套餐列表
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:export")
@Log(title = "租户套餐", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysTenantPackageBo bo, HttpServletResponse response) {
List<SysTenantPackageVo> list = tenantPackageService.queryList(bo);
ExcelUtil.exportExcel(list, "租户套餐", SysTenantPackageVo.class, response);
}
/**
* 获取租户套餐详细信息
*
* @param packageId 主键
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:query")
@GetMapping("/{packageId}")
public R<SysTenantPackageVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long packageId) {
return R.ok(tenantPackageService.queryById(packageId));
}
/**
* 新增租户套餐
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:add")
@Log(title = "租户套餐", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysTenantPackageBo bo) {
if (!tenantPackageService.checkPackageNameUnique(bo)) {
return R.fail("新增套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
}
return toAjax(tenantPackageService.insertByBo(bo));
}
/**
* 修改租户套餐
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:edit")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantPackageBo bo) {
if (!tenantPackageService.checkPackageNameUnique(bo)) {
return R.fail("修改套餐'" + bo.getPackageName() + "'失败,套餐名称已存在");
}
return toAjax(tenantPackageService.updateByBo(bo));
}
/**
* 状态修改
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:edit")
@Log(title = "租户套餐", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysTenantPackageBo bo) {
return toAjax(tenantPackageService.updatePackageStatus(bo));
}
/**
* 删除租户套餐
*
* @param packageIds 主键串
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenantPackage:remove")
@Log(title = "租户套餐", businessType = BusinessType.DELETE)
@DeleteMapping("/{packageIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] packageIds) {
return toAjax(tenantPackageService.deleteWithValidByIds(List.of(packageIds), true));
}
}

View File

@@ -23,7 +23,6 @@ import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.core.BaseController; import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysDeptBo; import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.bo.SysPostBo; import org.dromara.system.domain.bo.SysPostBo;
@@ -31,7 +30,10 @@ import org.dromara.system.domain.bo.SysRoleBo;
import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.*; import org.dromara.system.domain.vo.*;
import org.dromara.system.listener.SysUserImportListener; import org.dromara.system.listener.SysUserImportListener;
import org.dromara.system.service.*; import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysPostService;
import org.dromara.system.service.ISysRoleService;
import org.dromara.system.service.ISysUserService;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -55,7 +57,6 @@ public class SysUserController extends BaseController {
private final ISysRoleService roleService; private final ISysRoleService roleService;
private final ISysPostService postService; private final ISysPostService postService;
private final ISysDeptService deptService; private final ISysDeptService deptService;
private final ISysTenantService tenantService;
/** /**
* 获取用户列表 * 获取用户列表
@@ -108,10 +109,6 @@ public class SysUserController extends BaseController {
public R<UserInfoVo> getInfo() { public R<UserInfoVo> getInfo() {
UserInfoVo userInfoVo = new UserInfoVo(); UserInfoVo userInfoVo = new UserInfoVo();
LoginUser loginUser = LoginHelper.getLoginUser(); LoginUser loginUser = LoginHelper.getLoginUser();
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 如果重新加载用户信息需清除动态租户
TenantHelper.clearDynamic();
}
SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId())); SysUserVo user = DataPermissionHelper.ignore(() -> userService.selectUserById(loginUser.getUserId()));
if (ObjectUtil.isNull(user)) { if (ObjectUtil.isNull(user)) {
@@ -168,11 +165,6 @@ public class SysUserController extends BaseController {
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
} }
if (TenantHelper.isEnable()) {
if (!tenantService.checkAccountBalance(TenantHelper.getTenantId())) {
return R.fail("当前租户下用户名额不足,请联系管理员");
}
}
user.setPassword(BCrypt.hashpw(user.getPassword())); user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.insertUser(user)); return toAjax(userService.insertUser(user));
} }

View File

@@ -2,9 +2,9 @@ package org.dromara.system.domain;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/** /**
* 参数配置表 sys_config * 参数配置表 sys_config
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_config") @TableName("sys_config")
public class SysConfig extends TenantEntity { public class SysConfig extends BaseEntity {
/** /**
* 参数主键 * 参数主键

View File

@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity; import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial; import java.io.Serial;
import java.util.ArrayList; import java.util.ArrayList;
@@ -21,7 +21,7 @@ import java.util.List;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dept") @TableName("sys_dept")
public class SysDept extends TenantEntity { public class SysDept extends BaseEntity {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.tenant.core.TenantEntity; import org.dromara.common.mybatis.core.domain.BaseEntity;
/** /**
* 字典数据表 sys_dict_data * 字典数据表 sys_dict_data
@@ -16,7 +16,7 @@ import org.dromara.common.tenant.core.TenantEntity;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_data") @TableName("sys_dict_data")
public class SysDictData extends TenantEntity { public class SysDictData extends BaseEntity {
/** /**
* 字典编码 * 字典编码

View File

@@ -2,9 +2,9 @@ package org.dromara.system.domain;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/** /**
* 字典类型表 sys_dict_type * 字典类型表 sys_dict_type
@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@TableName("sys_dict_type") @TableName("sys_dict_type")
public class SysDictType extends TenantEntity { public class SysDictType extends BaseEntity {
/** /**
* 字典主键 * 字典主键

Some files were not shown because too many files have changed in this diff Show More