Merge branch '5.X' of https://gitee.com/dromara/RuoYi-Vue-Plus into JustAuth

# Conflicts:
#	ruoyi-admin/src/main/resources/application-dev.yml
This commit is contained in:
thiszhc 2023-06-16 20:34:23 +08:00
commit aec0e22747
34 changed files with 295 additions and 648 deletions

View File

@ -57,7 +57,7 @@
| 分布式任务调度 | 采用 Xxl-Job 天生支持分布式 统一的管理中心 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 | | 分布式任务调度 | 采用 Xxl-Job 天生支持分布式 统一的管理中心 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 |
| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 | | 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 |
| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 | | 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 |
| 短信 | 支持 阿里、腾讯 只需在yml配置好厂家密钥即可使用 接口化支持扩展其他厂家 | 不支持 | | 短信 | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用 | 不支持 |
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 | | 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 | | 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 | | 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |

17
pom.xml
View File

@ -49,8 +49,7 @@
<!-- OSS 配置 --> <!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version> <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
<!-- SMS 配置 --> <!-- SMS 配置 -->
<aliyun.sms.version>2.0.23</aliyun.sms.version> <sms4j.version>2.1.1</sms4j.version>
<tencent.sms.version>3.1.687</tencent.sms.version>
<!-- 插件版本 --> <!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version> <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@ -242,17 +241,11 @@
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-java-sdk-s3.version}</version> <version>${aws-java-sdk-s3.version}</version>
</dependency> </dependency>
<!--短信sms4j-->
<dependency> <dependency>
<groupId>com.aliyun</groupId> <groupId>org.dromara.sms4j</groupId>
<artifactId>dysmsapi20170525</artifactId> <artifactId>sms4j-spring-boot-starter</artifactId>
<version>${aliyun.sms.version}</version> <version>${sms4j.version}</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<version>${tencent.sms.version}</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -14,11 +14,12 @@ import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mail.config.properties.MailProperties; import org.dromara.common.mail.config.properties.MailProperties;
import org.dromara.common.mail.utils.MailUtils; import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.sms.config.properties.SmsProperties;
import org.dromara.common.sms.core.SmsTemplate;
import org.dromara.common.sms.entity.SmsResult;
import org.dromara.common.web.config.properties.CaptchaProperties; import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.common.web.enums.CaptchaType; import org.dromara.common.web.enums.CaptchaType;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.dromara.web.domain.vo.CaptchaVo; import org.dromara.web.domain.vo.CaptchaVo;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -31,8 +32,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.time.Duration; import java.time.Duration;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Map;
/** /**
* 验证码操作处理 * 验证码操作处理
@ -47,7 +47,6 @@ import java.util.Map;
public class CaptchaController { public class CaptchaController {
private final CaptchaProperties captchaProperties; private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties;
private final MailProperties mailProperties; private final MailProperties mailProperties;
/** /**
@ -57,21 +56,18 @@ public class CaptchaController {
*/ */
@GetMapping("/resource/sms/code") @GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) { public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber; String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4); String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION)); RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可) // 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = ""; String templateId = "";
Map<String, String> map = new HashMap<>(1); LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code); map.put("code", code);
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class); SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
SmsResult result = smsTemplate.send(phonenumber, templateId, map); SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!result.isSuccess()) { if (!"OK".equals(smsResponse.getCode())) {
log.error("验证码短信发送异常 => {}", result); log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(result.getMessage()); return R.fail(smsResponse.getMessage());
} }
return R.ok(); return R.ok();
} }

View File

@ -345,7 +345,7 @@ public class SysLoginService {
private SysUserVo loadUserByEmail(String tenantId, String email) { private SysUserVo loadUserByEmail(String tenantId, String email) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus) .select(SysUser::getEmail, SysUser::getStatus)
.eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId) .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getEmail, email)); .eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) { if (ObjectUtil.isNull(user)) {
@ -414,25 +414,24 @@ public class SysLoginService {
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username; String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL; String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip) // 获取用户登录错误次数默认为0 (可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey); int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
// 锁定时间内登录 则踢出 // 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) { if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} }
if (supplier.get()) { if (supplier.get()) {
// 是否第一次 // 错误次数递增
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1; errorNumber++;
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
// 达到规定错误次数 则锁定登录 // 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) { if (errorNumber >= maxRetryCount) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime)); recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime); throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else { } else {
// 未达到规定错误次数 则递增 // 未达到规定错误次数
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber)); recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber); throw new UserException(loginType.getRetryLimitCount(), errorNumber);
} }

View File

@ -158,162 +158,29 @@ mail:
# Socket连接超时值单位毫秒缺省值不超时 # Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0 connectionTimeout: 0
--- # sms 短信 --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
sms: sms:
enabled: false
# 阿里云 dysmsapi.aliyuncs.com # 阿里云 dysmsapi.aliyuncs.com
# 腾讯云 sms.tencentcloudapi.com alibaba:
endpoint: "dysmsapi.aliyuncs.com" #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
accessKeyId: xxxxxxx requestUrl: dysmsapi.aliyuncs.com
accessKeySecret: xxxxxx #阿里云的accessKey
signName: 测试 accessKeyId: xxxxxxx
# 腾讯专用 #阿里云的accessKeySecret
sdkAppId: accessKeySecret: xxxxxxx
#短信签名
justauth: signature: 测试
enabled: true tencent:
type: #请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
QQ: requestUrl: sms.tencentcloudapi.com
client-id: 10**********6 #腾讯云的accessKey
client-secret: 1f7d08**********5b7**********29e accessKeyId: xxxxxxx
redirect-uri: http://oauth.xkcoding.com/demo/oauth/qq/callback #腾讯云的accessKeySecret
union-id: false accessKeySecret: xxxxxxx
WEIBO: #短信签名
client-id: 10**********6 signature: 测试
client-secret: 1f7d08**********5b7**********29e #短信sdkAppId
redirect-uri: http://oauth.xkcoding.com/demo/oauth/weibo/callback sdkAppId: appid
gitee: #地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
client-id: 38eaaa1b77b5e064313057a2f5745ce3a9f3e7686d9bd302c7df2f308ef6db81 territory: ap-guangzhou
client-secret: 2e633af8780cb9fe002c4c7291b722db944402e271efb99b062811f52d7da1ff
redirect-uri: http://localhost:8888/social-login?source=gitee
DINGTALK:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/dingtalk/callback
BAIDU:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/baidu/callback
CSDN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/csdn/callback
CODING:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/coding/callback
coding-group-name: xx
OSCHINA:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/oschina/callback
ALIPAY:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/alipay/callback
alipay-public-key: MIIB**************DAQAB
WECHAT_OPEN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_open/callback
WECHAT_MP:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_mp/callback
WECHAT_ENTERPRISE:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_enterprise/callback
agent-id: 1000002
TAOBAO:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/taobao/callback
GOOGLE:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/google/callback
FACEBOOK:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/facebook/callback
DOUYIN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/douyin/callback
LINKEDIN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/linkedin/callback
MICROSOFT:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/microsoft/callback
MI:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/mi/callback
TOUTIAO:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/toutiao/callback
TEAMBITION:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/teambition/callback
RENREN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/renren/callback
PINTEREST:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/pinterest/callback
STACK_OVERFLOW:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/stack_overflow/callback
stack-overflow-key: asd*********asd
HUAWEI:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/huawei/callback
KUJIALE:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/kujiale/callback
GITLAB:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/gitlab/callback
MEITUAN:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/meituan/callback
ELEME:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/eleme/callback
TWITTER:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/twitter/callback
XMLY:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/xmly/callback
# 设备唯一标识ID
device-id: xxxxxxxxxxxxxx
# 客户端操作系统类型1-iOS系统2-Android系统3-Web
client-os-type: 3
# 客户端包名,如果 clientOsType 为1或2时必填。对Android客户端是包名对IOS客户端是Bundle ID
#pack-id: xxxx
FEISHU:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/feishu/callback
JD:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: http://oauth.xkcoding.com/demo/oauth/jd/callback

View File

@ -161,14 +161,29 @@ mail:
# Socket连接超时值单位毫秒缺省值不超时 # Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0 connectionTimeout: 0
--- # sms 短信 --- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
sms: sms:
enabled: false
# 阿里云 dysmsapi.aliyuncs.com # 阿里云 dysmsapi.aliyuncs.com
# 腾讯云 sms.tencentcloudapi.com alibaba:
endpoint: "dysmsapi.aliyuncs.com" #请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
accessKeyId: xxxxxxx requestUrl: dysmsapi.aliyuncs.com
accessKeySecret: xxxxxx #阿里云的accessKey
signName: 测试 accessKeyId: xxxxxxx
# 腾讯专用 #阿里云的accessKeySecret
sdkAppId: accessKeySecret: xxxxxxx
#短信签名
signature: 测试
tencent:
#请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
requestUrl: sms.tencentcloudapi.com
#腾讯云的accessKey
accessKeyId: xxxxxxx
#腾讯云的accessKeySecret
accessKeySecret: xxxxxxx
#短信签名
signature: 测试
#短信sdkAppId
sdkAppId: appid
#地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
territory: ap-guangzhou

View File

@ -96,20 +96,14 @@ spring:
sa-token: sa-token:
# token名称 (同时也是cookie名称) # token名称 (同时也是cookie名称)
token-name: Authorization token-name: Authorization
# token有效期 设为一天 (必定过期) 单位: 秒 # token固定超时 设为七天 (必定过期) 单位: 秒
timeout: 86400 timeout: 604800
# token临时有效期 (指定时间无操作就过期) 单位: 秒 # token活跃超时时间 30分钟(指定时间无操作则过期) 单位: 秒
activity-timeout: 1800 activity-timeout: 1800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) # 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false is-share: false
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥 # jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz jwt-secret-key: abcdefghijklmnopqrstuvwxyz
@ -156,39 +150,12 @@ mybatis-plus:
mapperLocations: classpath*:mapper/**/*Mapper.xml mapperLocations: classpath*:mapper/**/*Mapper.xml
# 实体扫描多个package用逗号或者分号分隔 # 实体扫描多个package用逗号或者分号分隔
typeAliasesPackage: org.dromara.**.domain typeAliasesPackage: org.dromara.**.domain
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config: global-config:
# 是否打印 Logo banner
banner: true
dbConfig: dbConfig:
# 主键类型 # 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
# 如需改为自增 需要将数据库表全部设置为自增
idType: ASSIGN_ID idType: ASSIGN_ID
# 逻辑已删除值
logicDeleteValue: 2
# 逻辑未删除值
logicNotDeleteValue: 0
# 字段验证策略之 insert,在 insert 的时候的字段验证策略
# IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
insertStrategy: NOT_NULL
# 字段验证策略之 update,在 update 的时候的字段验证策略
updateStrategy: NOT_NULL
# 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
where-strategy: NOT_NULL
# 数据加密 # 数据加密
mybatis-encryptor: mybatis-encryptor:
@ -204,8 +171,13 @@ mybatis-encryptor:
publicKey: publicKey:
privateKey: privateKey:
# Swagger配置 springdoc:
swagger: api-docs:
# 是否开启接口文档
enabled: true
swagger-ui:
# 持久化认证数据
persistAuthorization: true
info: info:
# 标题 # 标题
title: '标题:${ruoyi.name}多租户管理系统_接口文档' title: '标题:${ruoyi.name}多租户管理系统_接口文档'
@ -225,14 +197,6 @@ swagger:
type: APIKEY type: APIKEY
in: HEADER in: HEADER
name: ${sa-token.token-name} name: ${sa-token.token-name}
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
swagger-ui:
# 持久化认证数据
persistAuthorization: true
#这里定义了两个分组,可定义多个,也可以不定义 #这里定义了两个分组,可定义多个,也可以不定义
group-configs: group-configs:
- group: 1.演示模块 - group: 1.演示模块

View File

@ -34,6 +34,11 @@
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--常用工具类 --> <!--常用工具类 -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>

View File

@ -2,16 +2,14 @@ package org.dromara.common.core.config;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
/** /**
* 异步配置 * 异步配置
@ -22,16 +20,12 @@ import java.util.concurrent.ScheduledExecutorService;
@AutoConfiguration @AutoConfiguration
public class AsyncConfig implements AsyncConfigurer { public class AsyncConfig implements AsyncConfigurer {
@Autowired
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService;
/** /**
* 自定义 @Async 注解使用系统线程池 * 自定义 @Async 注解使用系统线程池
*/ */
@Override @Override
public Executor getAsyncExecutor() { public Executor getAsyncExecutor() {
return scheduledExecutorService; return SpringUtils.getBean("scheduledExecutorService");
} }
/** /**

View File

@ -22,18 +22,19 @@ public class ValidatorConfig {
*/ */
@Bean @Bean
public Validator validator(MessageSource messageSource) { public Validator validator(MessageSource messageSource) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
// 国际化 // 国际化
factoryBean.setValidationMessageSource(messageSource); factoryBean.setValidationMessageSource(messageSource);
// 设置使用 HibernateValidator 校验器 // 设置使用 HibernateValidator 校验器
factoryBean.setProviderClass(HibernateValidator.class); factoryBean.setProviderClass(HibernateValidator.class);
Properties properties = new Properties(); Properties properties = new Properties();
// 设置 快速异常返回 // 设置 快速异常返回
properties.setProperty("hibernate.validator.fail_fast", "true"); properties.setProperty("hibernate.validator.fail_fast", "true");
factoryBean.setValidationProperties(properties); factoryBean.setValidationProperties(properties);
// 加载配置 // 加载配置
factoryBean.afterPropertiesSet(); factoryBean.afterPropertiesSet();
return factoryBean.getValidator(); return factoryBean.getValidator();
}
} }
} }

View File

@ -0,0 +1,31 @@
package org.dromara.common.core.factory;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
/**
* yml 配置源工厂
*
* @author Lion Li
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}

View File

@ -3,6 +3,7 @@ package org.dromara.common.core.utils;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
/** /**
@ -23,6 +24,10 @@ public class MessageUtils {
* @return 获取国际化翻译值 * @return 获取国际化翻译值
*/ */
public static String message(String code, Object... args) { public static String message(String code, Object... args) {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale()); try {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
} catch (NoSuchMessageException e) {
return code;
}
} }
} }

View File

@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpSession;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
@ -19,6 +20,7 @@ import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -101,14 +103,22 @@ public class ServletUtils extends JakartaServletUtil {
* 获取request * 获取request
*/ */
public static HttpServletRequest getRequest() { public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest(); try {
return getRequestAttributes().getRequest();
} catch (Exception e) {
return null;
}
} }
/** /**
* 获取response * 获取response
*/ */
public static HttpServletResponse getResponse() { public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse(); try {
return getRequestAttributes().getResponse();
} catch (Exception e) {
return null;
}
} }
/** /**
@ -119,8 +129,33 @@ public class ServletUtils extends JakartaServletUtil {
} }
public static ServletRequestAttributes getRequestAttributes() { public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); try {
return (ServletRequestAttributes) attributes; RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
} catch (Exception e) {
return null;
}
}
public static String getHeader(HttpServletRequest request, String name) {
String value = request.getHeader(name);
if (StringUtils.isEmpty(value)) {
return StringUtils.EMPTY;
}
return urlDecode(value);
}
public static Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
}
return map;
} }
/** /**

View File

@ -1,13 +1,13 @@
package org.dromara.common.doc.config; package org.dromara.common.doc.config;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.doc.config.properties.SwaggerProperties;
import org.dromara.common.doc.handler.OpenApiHandler;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.doc.config.properties.SpringDocProperties;
import org.dromara.common.doc.handler.OpenApiHandler;
import org.springdoc.core.configuration.SpringDocConfiguration; import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer; import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.OpenApiCustomizer; import org.springdoc.core.customizers.OpenApiCustomizer;
@ -36,18 +36,18 @@ import java.util.Set;
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@AutoConfiguration(before = SpringDocConfiguration.class) @AutoConfiguration(before = SpringDocConfiguration.class)
@EnableConfigurationProperties(SwaggerProperties.class) @EnableConfigurationProperties(SpringDocProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
public class SwaggerConfig { public class SpringDocConfig {
private final ServerProperties serverProperties; private final ServerProperties serverProperties;
@Bean @Bean
@ConditionalOnMissingBean(OpenAPI.class) @ConditionalOnMissingBean(OpenAPI.class)
public OpenAPI openApi(SwaggerProperties swaggerProperties) { public OpenAPI openApi(SpringDocProperties swaggerProperties) {
OpenAPI openApi = new OpenAPI(); OpenAPI openApi = new OpenAPI();
// 文档基本信息 // 文档基本信息
SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo(); SpringDocProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
Info info = convertInfo(infoProperties); Info info = convertInfo(infoProperties);
openApi.info(info); openApi.info(info);
// 扩展文档信息 // 扩展文档信息
@ -65,7 +65,7 @@ public class SwaggerConfig {
return openApi; return openApi;
} }
private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) { private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
Info info = new Info(); Info info = new Info();
info.setTitle(infoProperties.getTitle()); info.setTitle(infoProperties.getTitle());
info.setDescription(infoProperties.getDescription()); info.setDescription(infoProperties.getDescription());

View File

@ -18,8 +18,8 @@ import java.util.List;
* @author Lion Li * @author Lion Li
*/ */
@Data @Data
@ConfigurationProperties(prefix = "swagger") @ConfigurationProperties(prefix = "springdoc")
public class SwaggerProperties { public class SpringDocProperties {
/** /**
* 文档基本信息 * 文档基本信息

View File

@ -1 +1 @@
org.dromara.common.doc.config.SwaggerConfig org.dromara.common.doc.config.SpringDocConfig

View File

@ -7,11 +7,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 org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
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.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
/** /**
@ -22,6 +24,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement(proxyTargetClass = true) @EnableTransactionManagement(proxyTargetClass = true)
@AutoConfiguration @AutoConfiguration
@MapperScan("${mybatis-plus.mapperPackage}") @MapperScan("${mybatis-plus.mapperPackage}")
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
public class MybatisPlusConfig { public class MybatisPlusConfig {
@Bean @Bean

View File

@ -0,0 +1,33 @@
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
# 是否打印 Logo banner
banner: true
dbConfig:
# 主键类型
# AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
idType: ASSIGN_ID
# 逻辑已删除值(框架表均使用此值 禁止随意修改)
logicDeleteValue: 2
# 逻辑未删除值
logicNotDeleteValue: 0
insertStrategy: NOT_NULL
updateStrategy: NOT_NULL
whereStrategy: NOT_NULL

View File

@ -4,10 +4,12 @@ import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple; import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpLogic;
import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.satoken.core.dao.PlusSaTokenDao; import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
import org.dromara.common.satoken.core.service.SaPermissionImpl; import org.dromara.common.satoken.core.service.SaPermissionImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** /**
@ -16,6 +18,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
* @author Lion Li * @author Lion Li
*/ */
@AutoConfiguration @AutoConfiguration
@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
public class SaTokenConfig implements WebMvcConfigurer { public class SaTokenConfig implements WebMvcConfigurer {
@Bean @Bean

View File

@ -0,0 +1,11 @@
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
# Sa-Token配置
sa-token:
# 允许从 请求参数 读取 token
is-read-body: true
# 允许从 header 读取 token
is-read-header: true
# 关闭 cookie 鉴权 从根源杜绝 csrf 漏洞风险
is-read-cookie: false
# token前缀
token-prefix: "Bearer"

View File

@ -16,22 +16,19 @@
</description> </description>
<dependencies> <dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.aliyun</groupId> <groupId>org.dromara.sms4j</groupId>
<artifactId>dysmsapi20170525</artifactId> <artifactId>sms4j-spring-boot-starter</artifactId>
<optional>true</optional> <exclusions>
<!-- 排除京东短信内存在的fastjson等待作者后续修复 -->
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-sms</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,15 +1,6 @@
package org.dromara.common.sms.config; package org.dromara.common.sms.config;
import org.dromara.common.sms.config.properties.SmsProperties;
import org.dromara.common.sms.core.AliyunSmsTemplate;
import org.dromara.common.sms.core.SmsTemplate;
import org.dromara.common.sms.core.TencentSmsTemplate;
import org.springframework.boot.autoconfigure.AutoConfiguration; 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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** /**
* 短信配置类 * 短信配置类
@ -18,31 +9,7 @@ import org.springframework.context.annotation.Configuration;
* @version 4.2.0 * @version 4.2.0
*/ */
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(SmsProperties.class) //@EnableConfigurationProperties(SmsProperties.class)
public class SmsConfig { public class SmsConfig {
@Configuration
@ConditionalOnProperty(value = "sms.enabled", havingValue = "true")
@ConditionalOnClass(com.aliyun.dysmsapi20170525.Client.class)
static class AliyunSmsConfig {
@Bean
public SmsTemplate aliyunSmsTemplate(SmsProperties smsProperties) {
return new AliyunSmsTemplate(smsProperties);
}
}
@Configuration
@ConditionalOnProperty(value = "sms.enabled", havingValue = "true")
@ConditionalOnClass(com.tencentcloudapi.sms.v20190711.SmsClient.class)
static class TencentSmsConfig {
@Bean
public SmsTemplate tencentSmsTemplate(SmsProperties smsProperties) {
return new TencentSmsTemplate(smsProperties);
}
}
} }

View File

@ -1,45 +1,19 @@
package org.dromara.common.sms.config.properties; //package org.dromara.common.sms.config.properties;
//
import lombok.Data; //import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; //import org.springframework.boot.context.properties.ConfigurationProperties;
//
/** ///**
* SMS短信 配置属性 // * SMS短信 配置属性
* // *
* @author Lion Li // * @author Lion Li
* @version 4.2.0 // * @version 4.2.0
*/ // */
@Data //@Data
@ConfigurationProperties(prefix = "sms") //@ConfigurationProperties(prefix = "sms")
public class SmsProperties { //public class SmsProperties {
//
private Boolean enabled; // private Boolean enabled;
//
/** //
* 配置节点 //}
* 阿里云 dysmsapi.aliyuncs.com
* 腾讯云 sms.tencentcloudapi.com
*/
private String endpoint;
/**
* key
*/
private String accessKeyId;
/**
* 密匙
*/
private String accessKeySecret;
/*
* 短信签名
*/
private String signName;
/**
* 短信应用ID (腾讯专属)
*/
private String sdkAppId;
}

View File

@ -1,66 +0,0 @@
package org.dromara.common.sms.core;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.sms.config.properties.SmsProperties;
import org.dromara.common.sms.entity.SmsResult;
import org.dromara.common.sms.exception.SmsException;
import lombok.SneakyThrows;
import java.util.Map;
/**
* Aliyun 短信模板
*
* @author Lion Li
* @version 4.2.0
*/
public class AliyunSmsTemplate implements SmsTemplate {
private SmsProperties properties;
private Client client;
@SneakyThrows(Exception.class)
public AliyunSmsTemplate(SmsProperties smsProperties) {
this.properties = smsProperties;
Config config = new Config()
// 您的AccessKey ID
.setAccessKeyId(smsProperties.getAccessKeyId())
// 您的AccessKey Secret
.setAccessKeySecret(smsProperties.getAccessKeySecret())
// 访问的域名
.setEndpoint(smsProperties.getEndpoint());
this.client = new Client(config);
}
@Override
public SmsResult send(String phones, String templateId, Map<String, String> param) {
if (StringUtils.isBlank(phones)) {
throw new SmsException("手机号不能为空");
}
if (StringUtils.isBlank(templateId)) {
throw new SmsException("模板ID不能为空");
}
SendSmsRequest req = new SendSmsRequest()
.setPhoneNumbers(phones)
.setSignName(properties.getSignName())
.setTemplateCode(templateId)
.setTemplateParam(JsonUtils.toJsonString(param));
try {
SendSmsResponse resp = client.sendSms(req);
return SmsResult.builder()
.isSuccess("OK".equals(resp.getBody().getCode()))
.message(resp.getBody().getMessage())
.response(JsonUtils.toJsonString(resp))
.build();
} catch (Exception e) {
throw new SmsException(e.getMessage());
}
}
}

View File

@ -1,26 +0,0 @@
package org.dromara.common.sms.core;
import org.dromara.common.sms.entity.SmsResult;
import java.util.Map;
/**
* 短信模板
*
* @author Lion Li
* @version 4.2.0
*/
public interface SmsTemplate {
/**
* 发送短信
*
* @param phones 电话号(多个逗号分割)
* @param templateId 模板id
* @param param 模板对应参数
* 阿里 需使用 模板变量名称对应内容 例如: code=1234
* 腾讯 需使用 模板变量顺序对应内容 例如: 1=1234, 1为模板内第一个参数
*/
SmsResult send(String phones, String templateId, Map<String, String> param);
}

View File

@ -1,82 +0,0 @@
package org.dromara.common.sms.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.sms.config.properties.SmsProperties;
import org.dromara.common.sms.entity.SmsResult;
import org.dromara.common.sms.exception.SmsException;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20190711.SmsClient;
import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
import com.tencentcloudapi.sms.v20190711.models.SendStatus;
import lombok.SneakyThrows;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Tencent 短信模板
*
* @author Lion Li
* @version 4.2.0
*/
public class TencentSmsTemplate implements SmsTemplate {
private SmsProperties properties;
private SmsClient client;
@SneakyThrows(Exception.class)
public TencentSmsTemplate(SmsProperties smsProperties) {
this.properties = smsProperties;
Credential credential = new Credential(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint(smsProperties.getEndpoint());
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
this.client = new SmsClient(credential, "", clientProfile);
}
@Override
public SmsResult send(String phones, String templateId, Map<String, String> param) {
if (StringUtils.isBlank(phones)) {
throw new SmsException("手机号不能为空");
}
if (StringUtils.isBlank(templateId)) {
throw new SmsException("模板ID不能为空");
}
SendSmsRequest req = new SendSmsRequest();
Set<String> set = Arrays.stream(phones.split(StringUtils.SEPARATOR)).map(p -> "+86" + p).collect(Collectors.toSet());
req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class));
if (CollUtil.isNotEmpty(param)) {
req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class));
}
req.setTemplateID(templateId);
req.setSign(properties.getSignName());
req.setSmsSdkAppid(properties.getSdkAppId());
try {
SendSmsResponse resp = client.SendSms(req);
SmsResult.SmsResultBuilder builder = SmsResult.builder()
.isSuccess(true)
.message("send success")
.response(JsonUtils.toJsonString(resp));
for (SendStatus sendStatus : resp.getSendStatusSet()) {
if (!"Ok".equals(sendStatus.getCode())) {
builder.isSuccess(false).message(sendStatus.getMessage());
break;
}
}
return builder.build();
} catch (Exception e) {
throw new SmsException(e.getMessage());
}
}
}

View File

@ -1,31 +0,0 @@
package org.dromara.common.sms.entity;
import lombok.Builder;
import lombok.Data;
/**
* 上传返回体
*
* @author Lion Li
*/
@Data
@Builder
public class SmsResult {
/**
* 是否成功
*/
private boolean isSuccess;
/**
* 响应消息
*/
private String message;
/**
* 实际响应体
* <p>
* 可自行转换为 SDK 对应的 SendSmsResponse
*/
private String response;
}

View File

@ -1,19 +0,0 @@
package org.dromara.common.sms.exception;
import java.io.Serial;
/**
* Sms异常类
*
* @author Lion Li
*/
public class SmsException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
public SmsException(String msg) {
super(msg);
}
}

View File

@ -97,16 +97,6 @@
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-websocket</artifactId> <artifactId>ruoyi-common-websocket</artifactId>
</dependency> </dependency>
<!-- 短信 用哪个导入哪个依赖 -->
<!-- <dependency>-->
<!-- <groupId>com.aliyun</groupId>-->
<!-- <artifactId>dysmsapi20170525</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.tencentcloudapi</groupId>-->
<!-- <artifactId>tencentcloud-sdk-java-sms</artifactId>-->
<!-- </dependency>-->
</dependencies> </dependencies>

View File

@ -1,17 +1,17 @@
package org.dromara.demo.controller; package org.dromara.demo.controller;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.sms.config.properties.SmsProperties;
import org.dromara.common.sms.core.SmsTemplate;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.sms4j.provider.enumerate.SupplierType;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Map;
/** /**
* 短信演示案例 * 短信演示案例
@ -26,10 +26,6 @@ import java.util.Map;
@RequestMapping("/demo/sms") @RequestMapping("/demo/sms")
public class SmsController { public class SmsController {
private final SmsProperties smsProperties;
// private final SmsTemplate smsTemplate; // 可以使用spring注入
// private final AliyunSmsTemplate smsTemplate; // 也可以注入某个厂家的模板工具
/** /**
* 发送短信Aliyun * 发送短信Aliyun
* *
@ -38,17 +34,11 @@ public class SmsController {
*/ */
@GetMapping("/sendAliyun") @GetMapping("/sendAliyun")
public R<Object> sendAliyun(String phones, String templateId) { public R<Object> sendAliyun(String phones, String templateId) {
if (!smsProperties.getEnabled()) { LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
return R.fail("当前系统没有开启短信功能!");
}
if (!SpringUtils.containsBean("aliyunSmsTemplate")) {
return R.fail("阿里云依赖未引入!");
}
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
Map<String, String> map = new HashMap<>(1);
map.put("code", "1234"); map.put("code", "1234");
Object send = smsTemplate.send(phones, templateId, map); SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
return R.ok(send); SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
return R.ok(smsResponse);
} }
/** /**
@ -59,18 +49,12 @@ public class SmsController {
*/ */
@GetMapping("/sendTencent") @GetMapping("/sendTencent")
public R<Object> sendTencent(String phones, String templateId) { public R<Object> sendTencent(String phones, String templateId) {
if (!smsProperties.getEnabled()) { LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
return R.fail("当前系统没有开启短信功能!");
}
if (!SpringUtils.containsBean("tencentSmsTemplate")) {
return R.fail("腾讯云依赖未引入!");
}
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
Map<String, String> map = new HashMap<>(1);
// map.put("2", "测试测试"); // map.put("2", "测试测试");
map.put("1", "1234"); map.put("1", "1234");
Object send = smsTemplate.send(phones, templateId, map); SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.TENCENT);
return R.ok(send); SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
return R.ok(smsResponse);
} }
} }

View File

@ -2,6 +2,7 @@ package org.dromara.generator.mapper;
import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTableColumn; import org.dromara.generator.domain.GenTableColumn;
@ -22,6 +23,6 @@ public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, Gen
* @return 列信息 * @return 列信息
*/ */
@DS("#dataName") @DS("#dataName")
List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName); List<GenTableColumn> selectDbTableColumnsByName(@Param("tableName") String tableName, String dataName);
} }

View File

@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole; import cn.dev33.satoken.annotation.SaCheckRole;
import com.baomidou.lock.annotation.Lock4j; import com.baomidou.lock.annotation.Lock4j;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -174,7 +175,8 @@ public class SysTenantController extends BaseController {
@SaCheckPermission("system:tenant:edit") @SaCheckPermission("system:tenant:edit")
@Log(title = "租户", businessType = BusinessType.UPDATE) @Log(title = "租户", businessType = BusinessType.UPDATE)
@GetMapping("/syncTenantPackage") @GetMapping("/syncTenantPackage")
public R<Void> syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId, @NotBlank(message = "套餐ID不能为空") Long packageId) { public R<Void> syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId,
@NotNull(message = "套餐ID不能为空") Long packageId) {
return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId))); return toAjax(TenantHelper.ignore(() -> tenantService.syncTenantPackage(tenantId, packageId)));
} }

View File

@ -94,7 +94,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
* @return 用户对象信息 * @return 用户对象信息
*/ */
@InterceptorIgnore(tenantLine = "true") @InterceptorIgnore(tenantLine = "true")
SysUserVo selectTenantUserByUserName(String userName, String tenantId); SysUserVo selectTenantUserByUserName(@Param("userName") String userName, @Param("tenantId") String tenantId);
/** /**
* 通过手机号查询用户(不走租户插件) * 通过手机号查询用户(不走租户插件)
@ -104,7 +104,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
* @return 用户对象信息 * @return 用户对象信息
*/ */
@InterceptorIgnore(tenantLine = "true") @InterceptorIgnore(tenantLine = "true")
SysUserVo selectTenantUserByPhonenumber(String phonenumber, String tenantId); SysUserVo selectTenantUserByPhonenumber(@Param("phonenumber") String phonenumber, @Param("tenantId") String tenantId);
/** /**
* 通过邮箱查询用户(不走租户插件) * 通过邮箱查询用户(不走租户插件)
@ -114,7 +114,8 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
* @return 用户对象信息 * @return 用户对象信息
*/ */
@InterceptorIgnore(tenantLine = "true") @InterceptorIgnore(tenantLine = "true")
SysUserVo selectTenantUserByEmail(String email, String tenantId); SysUserVo selectTenantUserByEmail(@Param("email") String email, @Param("tenantId") String tenantId);
/** /**
* 通过用户ID查询用户 * 通过用户ID查询用户

View File

@ -22,7 +22,7 @@ public class SysSensitiveServiceImpl implements SensitiveService {
@Override @Override
public boolean isSensitive() { public boolean isSensitive() {
if (TenantHelper.isEnable()) { if (TenantHelper.isEnable()) {
return !LoginHelper.isSuperAdmin() || !LoginHelper.isTenantAdmin(); return !LoginHelper.isSuperAdmin() && !LoginHelper.isTenantAdmin();
} }
return !LoginHelper.isSuperAdmin(); return !LoginHelper.isSuperAdmin();
} }