mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-01-19 05:36:13 +08:00
Compare commits
12 Commits
dev
...
futuer/boo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
161b52d8d7 | ||
|
|
befabc61de | ||
|
|
5b82c12e17 | ||
|
|
145b903185 | ||
|
|
55098339d4 | ||
|
|
469274d9b1 | ||
|
|
596e83701a | ||
|
|
8a87c7aa4e | ||
|
|
b4467aa8e9 | ||
|
|
5de3114f05 | ||
|
|
8d4fdd9fc8 | ||
|
|
2f4e89ee42 |
@@ -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
18
pom.xml
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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=对不起,您的租户已过期,请联系管理员
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
@@ -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=对不起,您的租户已过期,请联系管理员
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
<!--常用工具类 -->
|
<!--常用工具类 -->
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端
|
* 客户端
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ public interface SystemConstants {
|
|||||||
*/
|
*/
|
||||||
Long SUPER_ADMIN_ID = 1L;
|
Long SUPER_ADMIN_ID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超级管理员角色 roleKey
|
||||||
|
*/
|
||||||
|
String SUPER_ADMIN_ROLE_KEY = "superadmin";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根部门祖级列表
|
* 根部门祖级列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程定义编码
|
* 流程定义编码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程定义编码
|
* 流程定义编码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程定义编码
|
* 流程定义编码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码
|
* 验证码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ public interface WorkflowService {
|
|||||||
*/
|
*/
|
||||||
Long getInstanceIdByBusinessId(String businessId);
|
Long getInstanceIdByBusinessId(String businessId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增租户流程定义
|
|
||||||
*
|
|
||||||
* @param tenantId 租户id
|
|
||||||
*/
|
|
||||||
void syncDef(String tenantId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动流程
|
* 启动流程
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,请合理设置该值。
|
||||||
|
// README:https://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 为 BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
|
// 本项目工具使用的 CachePolicy 为 BufferCache,BufferCache会加载整个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实例
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 配置");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户账号
|
* 用户账号
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ public class OperLogEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Long operId;
|
private Long operId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户ID
|
|
||||||
*/
|
|
||||||
private String tenantId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作模块
|
* 操作模块
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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或空
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.dromara.common.mqtt.config.MqttAutoConfiguration
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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());
|
||||||
// 分页插件
|
// 分页插件
|
||||||
|
|||||||
@@ -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 片段
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class OssProperties {
|
public class OssProperties {
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户id
|
|
||||||
*/
|
|
||||||
private String tenantId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 访问站点
|
* 访问站点
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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前缀处理
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查当前用户是否已登录
|
* 检查当前用户是否已登录
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
org.dromara.common.tenant.config.TenantConfig
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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 安全认证 -->
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|||||||
@@ -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("同步租户参数配置成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数主键
|
* 参数主键
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典编码
|
* 字典编码
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user