发布 v4.2.0

This commit is contained in:
疯狂的狮子Li 2022-06-28 22:51:57 +08:00
parent e0527b3350
commit c5f4dec559
168 changed files with 3232 additions and 2756 deletions

View File

@ -1,4 +1,4 @@
### 更改目的 解决了什么问题
### 更改目的 解决了什么问题(请提交到dev分支)
### 描述 做了哪些改动

View File

@ -4,7 +4,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.1.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-4.2.0-success.svg)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.6-blue.svg)]()
[![JDK-8+](https://img.shields.io/badge/JDK-8-green.svg)]()
[![JDK-11](https://img.shields.io/badge/JDK-11-green.svg)]()
@ -42,6 +42,7 @@
| 分布式任务调度 | Xxl-Job | [Xxl-Job官网](https://www.xuxueli.com/xxl-job/) | 高性能 高可靠 易扩展 |
| 文件存储 | Minio | [Minio文档](https://docs.min.io/) | 本地存储 |
| 文件存储 | 七牛、阿里、腾讯 | [OSS使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4359146&doc_id=1469725) | 云存储 |
| 短信模块 | 阿里、腾讯 | [短信使用文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=5578491&doc_id=1469725) | 短信发送 |
| 监控框架 | SpringBoot-Admin | [SpringBoot-Admin文档](https://codecentric.github.io/spring-boot-admin/current/) | 全方位服务监控 |
| 校验框架 | Validation | [Validation文档](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/) | 增强接口安全性、严谨性 支持国际化 |
| Excel框架 | Alibaba EasyExcel | [EasyExcel文档](https://www.yuque.com/easyexcel/doc/easyexcel) | 性能优异 扩展性强 |

89
pom.xml
View File

@ -6,49 +6,46 @@
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-vue-plus</artifactId>
<version>4.1.0</version>
<version>4.2.0</version>
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus后台管理系统</description>
<properties>
<ruoyi-vue-plus.version>4.1.0</ruoyi-vue-plus.version>
<spring-boot.version>2.6.7</spring-boot.version>
<ruoyi-vue-plus.version>4.2.0</ruoyi-vue-plus.version>
<spring-boot.version>2.6.9</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<spring-boot.mybatis>2.2.0</spring-boot.mybatis>
<druid.version>1.2.8</druid.version>
<spring-boot.mybatis>2.2.2</spring-boot.mybatis>
<druid.version>1.2.11</druid.version>
<knife4j.version>3.0.3</knife4j.version>
<swagger-annotations.version>1.5.22</swagger-annotations.version>
<poi.version>4.1.2</poi.version>
<commons-compress.version>1.21</commons-compress.version>
<easyexcel.version>3.0.5</easyexcel.version>
<poi.version>5.2.2</poi.version>
<easyexcel.version>3.1.1</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.29.0</satoken.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<satoken.version>1.30.0</satoken.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.7.22</hutool.version>
<hutool.version>5.8.3</hutool.version>
<okhttp.version>4.9.3</okhttp.version>
<spring-boot-admin.version>2.6.6</spring-boot-admin.version>
<redisson.version>3.17.0</redisson.version>
<spring-boot-admin.version>2.6.7</spring-boot-admin.version>
<redisson.version>3.17.4</redisson.version>
<lock4j.version>2.2.1</lock4j.version>
<dynamic-ds.version>3.5.1</dynamic-ds.version>
<tlog.version>1.3.6</tlog.version>
<xxl-job.version>2.3.0</xxl-job.version>
<tlog.version>1.4.3</tlog.version>
<xxl-job.version>2.3.1</xxl-job.version>
<!-- jdk11 缺失依赖 jaxb-->
<jaxb.version>3.0.1</jaxb.version>
<!-- 统一 guava 版本 解决隐式漏洞问题 -->
<guava.version>30.0-jre</guava.version>
<!-- OSS 配置 -->
<qiniu.version>7.9.5</qiniu.version>
<aliyun.oss.version>3.14.0</aliyun.oss.version>
<qcloud.cos.version>5.6.72</qcloud.cos.version>
<minio.version>8.3.8</minio.version>
<aws-java-sdk-s3.version>1.12.248</aws-java-sdk-s3.version>
<!-- SMS 配置 -->
<aliyun.sms.version>2.0.9</aliyun.sms.version>
<tencent.sms.version>3.1.537</tencent.sms.version>
<!-- docker 配置 -->
<docker.registry.url>localhost</docker.registry.url>
@ -105,29 +102,21 @@
<version>${swagger-annotations.version}</version>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- 修复poi漏洞 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
@ -155,13 +144,6 @@
<version>${satoken.version}</version>
</dependency>
<!-- jdk11 缺失依赖 jaxb-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
@ -192,6 +174,24 @@
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-java-sdk-s3.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>${aliyun.sms.version}</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>${tencent.sms.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
@ -248,6 +248,13 @@
<version>${tlog.version}</version>
</dependency>
<!-- 统一 guava 版本 解决隐式漏洞问题 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.1.0</version>
<version>4.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-extend</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.1.0</version>
<version>4.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@ -4,7 +4,7 @@
<parent>
<artifactId>ruoyi-extend</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.1.0</version>
<version>4.2.0</version>
</parent>
<artifactId>ruoyi-xxl-job-admin</artifactId>
<packaging>jar</packaging>

View File

@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;

View File

@ -37,7 +37,6 @@ public class CookieInterceptor implements AsyncHandlerInterceptor {
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
}
AsyncHandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}

View File

@ -27,7 +27,7 @@ public class PermissionInterceptor implements AsyncHandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
return true; // proceed with the next interceptor
}
// if need login
@ -53,7 +53,7 @@ public class PermissionInterceptor implements AsyncHandlerInterceptor {
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser);
}
return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
return true; // proceed with the next interceptor
}
}

View File

@ -1,8 +1,8 @@
package com.xxl.job.admin.controller.resolver;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.util.JacksonUtil;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.admin.core.util.JacksonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

View File

@ -32,6 +32,7 @@ public class EmailJobAlarm implements JobAlarm {
*
* @param jobLog
*/
@Override
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog) {
boolean alarmResult = true;

View File

@ -47,7 +47,7 @@ public class XxlJobCompleter {
// 1handle success, to trigger child job
String triggerChildMsg = null;
if (XxlJobContext.HANDLE_COCE_SUCCESS == xxlJobLog.getHandleCode()) {
if (XxlJobContext.HANDLE_CODE_SUCCESS == xxlJobLog.getHandleCode()) {
XxlJobInfo xxlJobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(xxlJobLog.getJobId());
if (xxlJobInfo != null && xxlJobInfo.getChildJobId() != null && xxlJobInfo.getChildJobId().trim().length() > 0) {
triggerChildMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_child_run") + "<<<<<<<<<<< </span><br>";

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
private static XxlJobAdminConfig adminConfig = null;
public static XxlJobAdminConfig getAdminConfig() {
return adminConfig;
}

View File

@ -191,13 +191,11 @@ import java.util.TreeSet;
* </ul>
* </p>
*
*
* @author Sharada Jambula, James House
* @author Contributions from Mads Henderson
* @author Refactoring from CronTrigger to CronExpression by Aaron Craven
*
* <p>
* Borrowed from quartz v2.3.1
*
*/
public final class CronExpression implements Serializable, Cloneable {
@ -217,6 +215,7 @@ public final class CronExpression implements Serializable, Cloneable {
protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
static {
monthMap.put("JAN", 0);
monthMap.put("FEB", 1);
@ -265,8 +264,7 @@ public final class CronExpression implements Serializable, Cloneable {
*
* @param cronExpression String representation of the cron expression the
* new object should represent
* @throws java.text.ParseException
* if the string expression cannot be parsed into a valid
* @throws ParseException if the string expression cannot be parsed into a valid
* <CODE>CronExpression</CODE>
*/
public CronExpression(String cronExpression) throws ParseException {
@ -283,8 +281,7 @@ public final class CronExpression implements Serializable, Cloneable {
* Constructs a new {@code CronExpression} as a copy of an existing
* instance.
*
* @param expression
* The existing cron expression to be copied
* @param expression The existing cron expression to be copied
*/
public CronExpression(CronExpression expression) {
/*
@ -1067,14 +1064,28 @@ public final class CronExpression implements Serializable, Cloneable {
int max = -1;
if (stopAt < startAt) {
switch (type) {
case SECOND : max = 60; break;
case MINUTE : max = 60; break;
case HOUR : max = 24; break;
case MONTH : max = 12; break;
case DAY_OF_WEEK : max = 7; break;
case DAY_OF_MONTH : max = 31; break;
case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
default : throw new IllegalArgumentException("Unexpected type encountered");
case SECOND:
max = 60;
break;
case MINUTE:
max = 60;
break;
case HOUR:
max = 24;
break;
case MONTH:
max = 12;
break;
case DAY_OF_WEEK:
max = 7;
break;
case DAY_OF_MONTH:
max = 31;
break;
case YEAR:
throw new IllegalArgumentException("Start year must be less than stop year");
default:
throw new IllegalArgumentException("Unexpected type encountered");
}
stopAt += max;
}
@ -1280,7 +1291,7 @@ public final class CronExpression implements Serializable, Cloneable {
day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
day -= lastdayOffset;
java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
Calendar tcal = Calendar.getInstance(getTimeZone());
tcal.set(Calendar.SECOND, 0);
tcal.set(Calendar.MINUTE, 0);
tcal.set(Calendar.HOUR_OF_DAY, 0);
@ -1316,7 +1327,7 @@ public final class CronExpression implements Serializable, Cloneable {
t = day;
day = daysOfMonth.first();
java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
Calendar tcal = Calendar.getInstance(getTimeZone());
tcal.set(Calendar.SECOND, 0);
tcal.set(Calendar.MINUTE, 0);
tcal.set(Calendar.HOUR_OF_DAY, 0);
@ -1580,9 +1591,9 @@ public final class CronExpression implements Serializable, Cloneable {
* @param hour the hour to set
*/
protected void setCalendarHour(Calendar cal, int hour) {
cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
cal.set(Calendar.HOUR_OF_DAY, hour);
if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) {
cal.set(Calendar.HOUR_OF_DAY, hour + 1);
}
}

View File

@ -7,6 +7,7 @@ public class XxlJobException extends RuntimeException {
public XxlJobException() {
}
public XxlJobException(String message) {
super(message);
}

View File

@ -19,6 +19,7 @@ public class XxlJobGroup {
// registry list
private List<String> registryList; // 执行器地址列表(系统注册)
public List<String> getRegistryList() {
if (addressList != null && addressList.trim().length() > 0) {
registryList = new ArrayList<String>(Arrays.asList(addressList.split(",")));

View File

@ -1,4 +1,4 @@
//package com.xxl.job.admin.core.jobbean;
package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.jobbean;
//
//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;

View File

@ -1,4 +1,4 @@
//package com.xxl.job.admin.core.schedule;
package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.schedule;
//
//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;

View File

@ -1,4 +1,4 @@
//package com.xxl.job.admin.core.quartz;
package com.xxl.job.admin.core.old;//package com.xxl.job.admin.core.quartz;
//
//import org.quartz.SchedulerConfigException;
//import org.quartz.spi.ThreadPool;

View File

@ -30,6 +30,7 @@ public enum ExecutorRouteStrategyEnum {
public String getTitle() {
return title;
}
public ExecutorRouter getRouter() {
return router;
}

View File

@ -23,6 +23,7 @@ public class ExecutorRouteConsistentHash extends ExecutorRouter {
/**
* get hash code on 2^32 ring (md5散列的方式计算hash值)
*
* @param key
* @return
*/

View File

@ -12,7 +12,7 @@ import java.util.concurrent.ConcurrentMap;
* 单个JOB对应的每个执行器使用频率最低的优先被选举
* a(*)LFU(Least Frequently Used)最不经常使用频率/次数
* bLRU(Least Recently Used)最近最久未使用时间
*
* <p>
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLFU extends ExecutorRouter {

View File

@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentMap;
* 单个JOB对应的每个执行器最久为使用的优先被选举
* aLFU(Least Frequently Used)最不经常使用频率/次数
* b(*)LRU(Least Recently Used)最近最久未使用时间
*
* <p>
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLRU extends ExecutorRouter {

View File

@ -78,6 +78,7 @@ public class XxlJobScheduler {
// ---------------------- executor-client ----------------------
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// valid
if (address == null || address.trim().length() == 0) {

View File

@ -62,7 +62,7 @@ public class JobFailMonitorHelper {
// 2fail alarm monitor
int newAlarmStatus = 0; // 告警状态0-默认-1=锁定状态1-无需告警2-告警成功3-告警失败
if (info != null && info.getAlarmEmail() != null && info.getAlarmEmail().trim().length() > 0) {
if (info != null) {
boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
newAlarmStatus = alarmResult ? 2 : 3;
} else {

View File

@ -20,6 +20,7 @@ public class JobLogReportHelper {
private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private static JobLogReportHelper instance = new JobLogReportHelper();
public static JobLogReportHelper getInstance() {
return instance;
}
@ -27,6 +28,7 @@ public class JobLogReportHelper {
private Thread logrThread;
private volatile boolean toStop = false;
public void start() {
logrThread = new Thread(new Runnable() {

View File

@ -23,6 +23,7 @@ public class JobScheduleHelper {
private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
private static JobScheduleHelper instance = new JobScheduleHelper();
public static JobScheduleHelper getInstance() {
return instance;
}

View File

@ -19,7 +19,9 @@ public enum TriggerTypeEnum {
private TriggerTypeEnum(String title) {
this.title = title;
}
private String title;
public String getTitle() {
return title;
}

View File

@ -30,15 +30,12 @@ public class XxlJobTrigger {
*
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* @param failRetryCount >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* @param executorParam null: use job param
* not null: cover job param
* @param addressList
* null: use executor addressList
* @param addressList null: use executor addressList
* not null: cover
*/
public static void trigger(int jobId,
@ -200,6 +197,7 @@ public class XxlJobTrigger {
/**
* run executor
*
* @param triggerParam
* @param address
* @return

View File

@ -23,6 +23,7 @@ public class I18nUtil {
private static Logger logger = LoggerFactory.getLogger(I18nUtil.class);
private static Properties prop = null;
public static Properties loadI18nProp() {
if (prop != null) {
return prop;

View File

@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentMap;
public class LocalCacheUtil {
private static ConcurrentMap<String, LocalCacheData> cacheRepository = new ConcurrentHashMap<String, LocalCacheData>(); // 类型建议用抽象父类兼容性更好
private static class LocalCacheData {
private String key;
private Object val;

View File

@ -31,6 +31,7 @@ public class LoginService {
String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16);
return tokenHex;
}
private XxlJobUser parseToken(String tokenHex) {
XxlJobUser xxlJobUser = null;
if (tokenHex != null) {
@ -103,8 +104,5 @@ public class LoginService {
return null;
}
public static void main(String[] args) {
System.out.println("121312");
}
}

View File

@ -1,6 +1,6 @@
admin_name=Scheduling Center
admin_name_full=Distributed Task Scheduling Platform XXL-JOB
admin_version=2.3.0
admin_version=2.3.1
admin_i18n=en
## system

View File

@ -1,6 +1,6 @@
admin_name=任务调度中心
admin_name_full=分布式任务调度平台XXL-JOB
admin_version=2.3.0
admin_version=2.3.1
admin_i18n=
## system

View File

@ -1,6 +1,6 @@
admin_name=任務調度中心
admin_name_full=分布式任務調度平臺XXL-JOB
admin_version=2.3.0
admin_version=2.3.1
admin_i18n=
## system

View File

@ -1,6 +1,6 @@
{
"name": "ruoyi-vue-plus",
"version": "4.1.0",
"version": "4.2.0",
"description": "RuoYi-Vue-Plus后台管理系统",
"author": "LionLi",
"license": "MIT",

View File

@ -57,3 +57,15 @@ export function getCodeImg() {
timeout: 20000
})
}
// 短信验证码
export function getCodeSms() {
return request({
url: '/captchaSms',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}

View File

@ -9,6 +9,14 @@ export function listOss(query) {
})
}
// 查询OSS对象基于id串
export function listByIds(ossId) {
return request({
url: '/system/oss/listByIds/' + ossId,
method: 'get'
})
}
// 删除OSS对象存储
export function delOss(ossId) {
return request({

View File

@ -41,7 +41,7 @@
<script>
import { getToken } from "@/utils/auth";
import { delOss } from "@/api/system/oss";
import { listByIds, delOss } from "@/api/system/oss";
export default {
name: "FileUpload",
@ -83,11 +83,21 @@ export default {
},
watch: {
value: {
handler(val) {
async handler(val) {
if (val) {
let temp = 1;
//
const list = Array.isArray(val) ? val : this.value.split(',');
let list;
if (Array.isArray(val)) {
list = val;
} else {
await listByIds(val).then(res => {
list = res.data.map(oss => {
oss = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
return oss;
});
})
}
//
this.fileList = list.map(item => {
item = { name: item.name, url: item.url, ossId: item.ossId };
@ -157,7 +167,7 @@ export default {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.fileList);
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
}
} else {
@ -170,7 +180,7 @@ export default {
let ossId = this.fileList[index].ossId;
delOss(ossId);
this.fileList.splice(index, 1);
this.$emit("input", this.fileList);
this.$emit("input", this.listToString(this.fileList));
},
//
getFileName(name) {
@ -181,6 +191,15 @@ export default {
return name;
}
},
//
listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
strs += list[i].ossId + separator;
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
},
},
};
</script>

View File

@ -12,7 +12,6 @@
</template>
<script>
import { isExternal } from "@/utils/validate";
export default {
name: "ImagePreview",
@ -33,19 +32,13 @@ export default {
computed: {
realSrc() {
let real_src = this.src.split(",")[0];
if (isExternal(real_src)) {
return real_src;
}
return process.env.VUE_APP_BASE_API + real_src;
},
realSrcList() {
let real_src_list = this.src.split(",");
let srcList = [];
real_src_list.forEach(item => {
if (isExternal(item)) {
return srcList.push(item);
}
return srcList.push(process.env.VUE_APP_BASE_API + item);
});
return srcList;
},

View File

@ -44,7 +44,7 @@
<script>
import { getToken } from "@/utils/auth";
import { delOss } from "@/api/system/oss";
import { listByIds, delOss } from "@/api/system/oss";
export default {
props: {
@ -87,10 +87,17 @@ export default {
},
watch: {
value: {
handler(val) {
async handler(val) {
if (val) {
//
const list = Array.isArray(val) ? val : this.value.split(',');
let list;
if (Array.isArray(val)) {
list = val;
} else {
await listByIds(val).then(res => {
list = res.data;
})
}
//
this.fileList = list.map(item => {
// name使ossId
@ -120,7 +127,7 @@ export default {
let ossId = this.fileList[findex].ossId;
delOss(ossId);
this.fileList.splice(findex, 1);
this.$emit("input", this.fileList);
this.$emit("input", this.listToString(this.fileList));
}
},
//
@ -131,7 +138,7 @@ export default {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.fileList);
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
}
} else {
@ -184,6 +191,15 @@ export default {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
//
listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
strs += list[i].ossId + separator;
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
},
}
};
</script>

View File

@ -73,6 +73,9 @@ DictData.install()
* please remove it before going online! ! !
*/
// 修改 el-dialog 默认点击遮照为不关闭
Element.Dialog.props.closeOnClickModal.default = false
Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size
})

View File

@ -11,7 +11,7 @@ import DictOptions from './DictOptions'
export default class DictMeta {
constructor(options) {
this.type = options.type
this.request = options.request,
this.request = options.request
this.responseConverter = options.responseConverter
this.labelField = options.labelField
this.valueField = options.valueField

View File

@ -1,29 +0,0 @@
export default [
{
layout: 'colFormItem',
tagIcon: 'input',
label: '手机号',
vModel: 'mobile',
formId: 6,
tag: 'el-input',
placeholder: '请输入手机号',
defaultValue: '',
span: 24,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false,
required: true,
changeTag: true,
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
}
]

View File

@ -207,10 +207,10 @@ export function tansParams(params) {
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && typeof (value) !== "undefined") {
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && typeof (value[key]) !== 'undefined') {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";

View File

@ -114,7 +114,7 @@ export default {
data() {
return {
//
version: "4.1.0",
version: "4.2.0",
};
},
methods: {

View File

@ -191,7 +191,7 @@
<script>
import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
import { listType, getType } from "@/api/system/dict/type";
import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type";
export default {
name: "Data",
@ -287,8 +287,8 @@ export default {
},
/** 查询字典类型列表 */
getTypeList() {
listType().then(response => {
this.typeOptions = response.rows;
getDictOptionselect().then(response => {
this.typeOptions = response.data;
});
},
/** 查询字典数据列表 */

View File

@ -2,14 +2,13 @@
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="配置key" prop="configKey">
<el-select v-model="queryParams.configKey" placeholder="请选择配置key" clearable size="small">
<el-option
v-for="configKey in configKeyOptions"
:key="configKey.configKey"
:label="configKey.label"
:value="configKey.configKey"
<el-input
v-model="queryParams.configKey"
placeholder="配置key"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>
</el-select>
</el-form-item>
<el-form-item label="桶名称" prop="bucketName">
<el-input
@ -77,6 +76,7 @@
<el-table-column label="主建" align="center" prop="ossConfigId" v-if="false"/>
<el-table-column label="配置key" align="center" prop="configKey" />
<el-table-column label="访问站点" align="center" prop="endpoint" width="200" />
<el-table-column label="自定义域名" align="center" prop="domain" width="200" />
<el-table-column label="桶名称" align="center" prop="bucketName" />
<el-table-column label="前缀" align="center" prop="prefix" />
<el-table-column label="域" align="center" prop="region" />
@ -122,18 +122,14 @@
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="配置key" prop="configKey">
<el-select v-model="form.configKey" placeholder="请选择配置key">
<el-option
v-for="configKey in configKeyOptions"
:key="configKey.configKey"
:label="configKey.label"
:value="configKey.configKey"
/>
</el-select>
<el-input v-model="form.configKey" placeholder="请输入配置key" />
</el-form-item>
<el-form-item label="访问站点" prop="endpoint">
<el-input v-model="form.endpoint" placeholder="请输入访问站点" />
</el-form-item>
<el-form-item label="自定义域名" prop="domain">
<el-input v-model="form.domain" placeholder="请输入自定义域名" />
</el-form-item>
<el-form-item label="accessKey" prop="accessKey">
<el-input v-model="form.accessKey" placeholder="请输入accessKey" />
</el-form-item>
@ -204,14 +200,6 @@ export default {
total: 0,
//
ossConfigList: [],
// configKeyOptions
configKeyOptions: [],
configKeyDatas: [
{ configKey: "minio", label: "Minio" },
{ configKey: "qiniu", label: "七牛云" },
{ configKey: "aliyun", label: "阿里云" },
{ configKey: "qcloud", label: "腾讯云" },
],
//
title: "",
//
@ -276,7 +264,6 @@ export default {
},
created() {
this.getList();
this.configKeyOptions = this.configKeyDatas;
},
methods: {
/** 查询对象存储配置列表 */
@ -303,6 +290,7 @@ export default {
bucketName: undefined,
prefix: undefined,
endpoint: undefined,
domain: undefined,
isHttps: "N",
region: undefined,
status: "1",

View File

@ -21,6 +21,7 @@
:filter-node-method="filterNode"
ref="tree"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</div>

View File

@ -486,7 +486,7 @@
</el-tree>
</template>
<template v-if="activeData.layout === 'colFormItem'">
<template v-if="activeData.layout === 'colFormItem' && activeData.tag !== 'el-button'">
<el-divider>正则校验</el-divider>
<div
v-for="(item, index) in activeData.regList"

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.1.0</version>
<version>4.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
@ -111,21 +111,15 @@
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<!-- jdk11 缺失依赖 jaxb-->
<!-- yml解析器 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- servlet包 -->
@ -179,6 +173,11 @@
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -226,35 +225,26 @@
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<!-- 短信 使用方法查看 SmsConfig 说明 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${qcloud.cos.version}</version>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
</dependencies>

View File

@ -0,0 +1,18 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 匿名访问不鉴权注解
*
* @author ruoyi
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous {
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.common.annotation;
import com.ruoyi.common.excel.CellMergeStrategy;
import java.lang.annotation.*;
/**
* excel 列单元格合并(合并列相同项)
*
* 需搭配 {@link CellMergeStrategy} 策略使用
*
* @author Lion Li
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CellMerge {
/**
* col index
*/
int index() default -1;
}

View File

@ -108,12 +108,6 @@ public class SysUser extends BaseEntity {
)
private String password;
@JsonIgnore
@JsonProperty
public String getPassword() {
return password;
}
/**
* 帐号状态0正常 1停用
*/

View File

@ -0,0 +1,114 @@
package com.ruoyi.common.excel;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.ruoyi.common.annotation.CellMerge;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 列值重复合并策略
*
* @author Lion Li
*/
@AllArgsConstructor
@Slf4j
public class CellMergeStrategy extends AbstractMergeStrategy {
private List<?> list;
private boolean hasTitle;
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
List<CellRangeAddress> cellList = handle(list, hasTitle);
// judge the list is not null
if (CollectionUtils.isNotEmpty(cellList)) {
// the judge is necessary
if (cell.getRowIndex() == 1 && cell.getColumnIndex() == 0) {
for (CellRangeAddress item : cellList) {
sheet.addMergedRegion(item);
}
}
}
}
@SneakyThrows
private static List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
List<CellRangeAddress> cellList = new ArrayList<>();
if (CollectionUtils.isEmpty(list)) {
return cellList;
}
Class<?> clazz = list.get(0).getClass();
Field[] fields = clazz.getDeclaredFields();
// 有注解的字段
List<Field> mergeFields = new ArrayList<>();
List<Integer> mergeFieldsIndex = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (field.isAnnotationPresent(CellMerge.class)) {
CellMerge cm = field.getAnnotation(CellMerge.class);
mergeFields.add(field);
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
}
}
// 行合并开始下标
int rowIndex = hasTitle ? 1 : 0;
Map<Field, RepeatCell> map = new HashMap<>();
// 生成两两合并单元格
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < mergeFields.size(); j++) {
Field field = mergeFields.get(j);
String name = field.getName();
String methodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
Method readMethod = clazz.getMethod(methodName);
Object val = readMethod.invoke(list.get(i));
int colNum = mergeFieldsIndex.get(j);
if (!map.containsKey(field)) {
map.put(field, new RepeatCell(val, i));
} else {
RepeatCell repeatCell = map.get(field);
Object cellValue = repeatCell.getValue();
if (cellValue == null || "".equals(cellValue)) {
// 空值跳过不合并
continue;
}
if (cellValue != val) {
if (i - repeatCell.getCurrent() > 1) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
} else if (i == list.size() - 1) {
if (i > repeatCell.getCurrent()) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
}
}
}
}
}
return cellList;
}
@Data
@AllArgsConstructor
static class RepeatCell {
private Object value;
private int current;
}
}

View File

@ -1,11 +1,11 @@
package com.ruoyi.common.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.ServletUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ -33,11 +33,11 @@ public class DataPermissionHelper {
}
public static Map<String, Object> getContext() {
HttpServletRequest request = ServletUtils.getRequest();
Object attribute = request.getAttribute(DATA_PERMISSION_KEY);
SaStorage saStorage = SaHolder.getStorage();
Object attribute = saStorage.get(DATA_PERMISSION_KEY);
if (ObjectUtil.isNull(attribute)) {
request.setAttribute(DATA_PERMISSION_KEY, new HashMap<>());
attribute = request.getAttribute(DATA_PERMISSION_KEY);
saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
attribute = saStorage.get(DATA_PERMISSION_KEY);
}
if (attribute instanceof Map) {
return (Map<String, Object>) attribute;

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.constant.UserConstants;
@ -29,15 +30,13 @@ public class LoginHelper {
public static final String JOIN_CODE = ":";
public static final String LOGIN_USER_KEY = "loginUser";
private static final ThreadLocal<LoginUser> LOGIN_CACHE = new ThreadLocal<>();
/**
* 登录系统
*
* @param loginUser 登录用户信息
*/
public static void login(LoginUser loginUser) {
LOGIN_CACHE.set(loginUser);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getLoginId());
setLoginUser(loginUser);
}
@ -49,7 +48,7 @@ public class LoginHelper {
* @param loginUser 登录用户信息
*/
public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
LOGIN_CACHE.set(loginUser);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
StpUtil.login(loginUser.getLoginId(), deviceType.getDevice());
setLoginUser(loginUser);
}
@ -65,18 +64,13 @@ public class LoginHelper {
* 获取用户(多级缓存)
*/
public static LoginUser getLoginUser() {
LoginUser loginUser = LOGIN_CACHE.get();
LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY);
if (loginUser != null) {
return loginUser;
}
return (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
}
/**
* 清除一级缓存 防止内存问题
*/
public static void clearCache() {
LOGIN_CACHE.remove();
loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
return loginUser;
}
/**

View File

@ -27,9 +27,9 @@ public class SensitiveJsonSerializer extends JsonSerializer<String> implements C
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
if (sensitiveService.isSensitive()) {
gen.writeString(value);
} else {
gen.writeString(strategy.desensitizer().apply(value));
} else {
gen.writeString(value);
}
}

View File

@ -0,0 +1,468 @@
package com.ruoyi.common.utils.email;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.*;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.mail.Authenticator;
import javax.mail.Session;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 邮件工具类
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MailUtils {
private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class);
/**
* 获取邮件发送实例
*/
public static MailAccount getMailAccount() {
return ACCOUNT;
}
/**
* 获取邮件发送实例 (自定义发送人以及授权码)
*
* @param user 发送人
* @param pass 授权码
*/
public static MailAccount getMailAccount(String from, String user, String pass) {
ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom()));
ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser()));
ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass()));
return ACCOUNT;
}
/**
* 使用配置文件中设置的账户发送文本邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendText(String to, String subject, String content, File... files) {
return send(to, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, File... files) {
return send(to, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送文本邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
*/
public static String sendText(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(to, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送单个或多个收件人<br>
* 多个收件人抄送人密送人可以使用逗号,分隔也可以通过分号;分隔
*
* @param to 收件人可以使用逗号,分隔也可以通过分号;分隔
* @param cc 抄送人可以使用逗号,分隔也可以通过分号;分隔
* @param bcc 密送人可以使用逗号,分隔也可以通过分号;分隔
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(tos, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
/**
* 根据配置文件获取邮件客户端会话
*
* @param mailAccount 邮件账户配置
* @param isSingleton 是否单例全局共享会话
* @return {@link Session}
* @since 5.5.7
*/
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
Authenticator authenticator = null;
if (mailAccount.isAuth()) {
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
}
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
}
// ------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:${cid}
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
// 可选抄送人
if (CollUtil.isNotEmpty(ccs)) {
mail.setCcs(ccs.toArray(new String[0]));
}
// 可选密送人
if (CollUtil.isNotEmpty(bccs)) {
mail.setBccs(bccs.toArray(new String[0]));
}
mail.setTos(tos.toArray(new String[0]));
mail.setTitle(subject);
mail.setContent(content);
mail.setHtml(isHtml);
mail.setFiles(files);
// 图片
if (MapUtil.isNotEmpty(imageMap)) {
for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
}
}
return mail.send();
}
/**
* 将多个联系人转为列表分隔符为逗号或者分号
*
* @param addresses 多个联系人如果为空返回null
* @return 联系人列表
*/
private static List<String> splitAddress(String addresses) {
if (StrUtil.isBlank(addresses)) {
return null;
}
List<String> result;
if (StrUtil.contains(addresses, CharUtil.COMMA)) {
result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
} else if (StrUtil.contains(addresses, ';')) {
result = StrUtil.splitTrim(addresses, ';');
} else {
result = CollUtil.newArrayList(addresses);
}
return result;
}
// ------------------------------------------------------------------------------------------------------------------------ Private method end
}

View File

@ -0,0 +1,40 @@
package com.ruoyi.common.utils.file;
/**
* 媒体类型工具类
*
* @author ruoyi
*/
public class MimeTypeUtils {
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPG = "image/jpg";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_BMP = "image/bmp";
public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
public static final String[] FLASH_EXTENSION = {"swf", "flv"};
public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb"};
public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf"};
}

View File

@ -1,9 +1,17 @@
package com.ruoyi.common.utils.poi;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.common.convert.ExcelBigNumberConvert;
import com.ruoyi.common.excel.CellMergeStrategy;
import com.ruoyi.common.excel.DefaultExcelListener;
import com.ruoyi.common.excel.ExcelListener;
import com.ruoyi.common.excel.ExcelResult;
@ -16,7 +24,10 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Excel相关处理
@ -69,27 +80,125 @@ public class ExcelUtil {
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @return 结果
* @param clazz 实体类
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
exportExcel(list, sheetName, clazz, false, response);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
try {
String filename = encodingFilename(sheetName);
response.reset();
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
EasyExcel.write(os, clazz)
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName).doWrite(list);
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
builder.doWrite(list);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
*/
public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
excelWriter.finish();
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = encodingFilename(sheetName);
response.reset();
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 解析导出值 0=,1=,2=未知
*
@ -103,7 +212,7 @@ public class ExcelUtil {
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) {
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
propertyString.append(itemArray[1] + separator);
@ -132,7 +241,7 @@ public class ExcelUtil {
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) {
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) {
propertyString.append(itemArray[0] + separator);

View File

@ -6,11 +6,11 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.*;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@ -107,7 +107,7 @@ public class RedisUtils {
} catch (Exception e) {
long timeToLive = bucket.remainTimeToLive();
bucket.set(value);
bucket.expire(timeToLive, TimeUnit.MILLISECONDS);
bucket.expire(Duration.ofMillis(timeToLive));
}
} else {
bucket.set(value);
@ -119,13 +119,12 @@ public class RedisUtils {
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
* @param duration 时间
*/
public static <T> void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) {
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
RBucket<T> result = CLIENT.getBucket(key);
result.set(value);
result.expire(timeout, timeUnit);
result.expire(duration);
}
/**
@ -149,20 +148,19 @@ public class RedisUtils {
* @return true=设置成功false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
return expire(key, Duration.ofSeconds(timeout));
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @param duration 超时时间
* @return true=设置成功false=设置失败
*/
public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
public static boolean expire(final String key, final Duration duration) {
RBucket rBucket = CLIENT.getBucket(key);
return rBucket.expire(timeout, unit);
return rBucket.expire(duration);
}
/**
@ -366,6 +364,50 @@ public class RedisUtils {
return rMap.getAll(hKeys);
}
/**
* 设置原子值
*
* @param key Redis键
* @param value
*/
public static void setAtomicValue(String key, long value) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
atomic.set(value);
}
/**
* 获取原子值
*
* @param key Redis键
* @return 当前值
*/
public static long getAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.get();
}
/**
* 递增原子值
*
* @param key Redis键
* @return 当前值
*/
public static long incrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.incrementAndGet();
}
/**
* 递减原子值
*
* @param key Redis键
* @return 当前值
*/
public static long decrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.decrementAndGet();
}
/**
* 获得缓存的基本对象列表
*

View File

@ -0,0 +1,48 @@
package com.ruoyi.demo.controller;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.email.MailUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
/**
* 邮件发送案例
*
* @author Michelle.Chung
*/
@Validated
@Api(value = "邮件发送案例", tags = {"邮件发送案例"})
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/mail")
public class MailController {
@ApiOperation("发送邮件")
@GetMapping("/sendSimpleMessage")
public R<Void> sendSimpleMessage(@ApiParam("接收人") String to,
@ApiParam("标题") String subject,
@ApiParam("内容") String text) {
MailUtils.sendText(to, subject, text);
return R.ok();
}
@ApiOperation("发送邮件(带附件)")
@GetMapping("/sendMessageWithAttachment")
public R<Void> sendMessageWithAttachment(@ApiParam("接收人") String to,
@ApiParam("标题") String subject,
@ApiParam("内容") String text,
@ApiParam("附件路径") String filePath) {
MailUtils.sendText(to, subject, text, new File(filePath));
return R.ok();
}
}

View File

@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
/**
* spring-cache 演示案例
@ -87,7 +87,7 @@ public class RedisCacheController {
@GetMapping("/test6")
public R<Boolean> test6(String key, String value) {
RedisUtils.setCacheObject(key, value);
boolean flag = RedisUtils.expire(key, 10, TimeUnit.SECONDS);
boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10));
System.out.println("***********" + flag);
try {
Thread.sleep(11 * 1000);

View File

@ -0,0 +1,72 @@
package com.ruoyi.demo.controller;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.sms.config.properties.SmsProperties;
import com.ruoyi.sms.core.SmsTemplate;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 短信演示案例
* 请先阅读文档 否则无法使用
*
* @author Lion Li
* @version 4.2.0
*/
@Validated
@Api(value = "短信演示案例", tags = {"短信演示案例"})
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo/sms")
public class SmsController {
private final SmsProperties smsProperties;
// private final SmsTemplate smsTemplate; // 可以使用spring注入
// private final AliyunSmsTemplate smsTemplate; // 也可以注入某个厂家的模板工具
@ApiOperation("发送短信Aliyun")
@GetMapping("/sendAliyun")
public R<Object> sendAliyun(@ApiParam("电话号") String phones,
@ApiParam("模板ID") String templateId) {
if (!smsProperties.getEnabled()) {
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");
Object send = smsTemplate.send(phones, templateId, map);
return R.ok(send);
}
@ApiOperation("发送短信Tencent")
@GetMapping("/sendTencent")
public R<Object> sendTencent(@ApiParam("电话号") String phones,
@ApiParam("模板ID") String templateId) {
if (!smsProperties.getEnabled()) {
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("1", "1234");
Object send = smsTemplate.send(phones, templateId, map);
return R.ok(send);
}
}

View File

@ -29,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -70,7 +71,7 @@ public class TestDemoController extends BaseController {
@ApiOperation("导入测试-校验")
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "导入文件", dataType = "java.io.File", required = true),
@ApiImplicitParam(name = "file", value = "导入文件", paramType = "query", dataTypeClass = File.class, required = true),
})
@Log(title = "测试单表", businessType = BusinessType.IMPORT)
@SaCheckPermission("demo:demo:import")

View File

@ -0,0 +1,102 @@
package com.ruoyi.demo.controller;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 测试Excel功能
*
* @author Lion Li
*/
@Api(value = "测试Excel功能", tags = {"测试Excel功能"})
@RestController
@RequestMapping("/demo/excel")
public class TestExcelController {
/**
* 单列表多数据
*/
@ApiOperation(value = "单列表多数据")
@GetMapping("/exportTemplateOne")
public void exportTemplateOne(HttpServletResponse response) {
Map<String,String> map = new HashMap<>();
map.put("title","单列表多数据");
map.put("test1","数据测试1");
map.put("test2","数据测试2");
map.put("test3","数据测试3");
map.put("test4","数据测试4");
map.put("testTest","666");
List<TestObj> list = new ArrayList<>();
list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4"));
list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8"));
list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12"));
ExcelUtil.exportTemplate(CollUtil.newArrayList(map,list),"单列表.xlsx", "excel/单列表.xlsx", response);
}
/**
* 多列表多数据
*/
@ApiOperation(value = "多列表多数据")
@GetMapping("/exportTemplateMuliti")
public void exportTemplateMuliti(HttpServletResponse response) {
Map<String,String> map = new HashMap<>();
map.put("title1","标题1");
map.put("title2","标题2");
map.put("title3","标题3");
map.put("title4","标题4");
map.put("author","Lion Li");
List<TestObj1> list1 = new ArrayList<>();
list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3"));
list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6"));
list1.add(new TestObj1("list1测试7", "list1测试8", "list1测试9"));
List<TestObj1> list2 = new ArrayList<>();
list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3"));
list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6"));
List<TestObj1> list3 = new ArrayList<>();
list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3"));
List<TestObj1> list4 = new ArrayList<>();
list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3"));
list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6"));
list4.add(new TestObj1("list4测试7", "list4测试8", "list4测试9"));
list4.add(new TestObj1("list4测试10", "list4测试11", "list4测试12"));
Map<String,Object> multiListMap = new HashMap<>();
multiListMap.put("map",map);
multiListMap.put("data1",list1);
multiListMap.put("data2",list2);
multiListMap.put("data3",list3);
multiListMap.put("data4",list4);
ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
}
@Data
@AllArgsConstructor
static class TestObj1 {
private String test1;
private String test2;
private String test3;
}
@Data
@AllArgsConstructor
static class TestObj {
private String name;
private String list1;
private String list2;
private String list3;
private String list4;
}
}

View File

@ -49,7 +49,7 @@ public class TestDemoBo extends BaseEntity {
*/
@ApiModelProperty("排序号")
@NotNull(message = "排序号不能为空", groups = {AddGroup.class, EditGroup.class})
private Long orderNum;
private Integer orderNum;
/**
* key键

View File

@ -48,7 +48,7 @@ public class TestDemoVo {
*/
@ExcelProperty(value = "排序号")
@ApiModelProperty("排序号")
private Long orderNum;
private Integer orderNum;
/**
* key键

View File

@ -54,5 +54,5 @@ public interface TestDemoMapper extends BaseMapperPlus<TestDemoMapper, TestDemo,
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);
int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);
}

View File

@ -25,9 +25,9 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 防止重复提交(参考美团GTIS防重系统)
@ -66,7 +66,7 @@ public class RepeatSubmitAspect {
String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + url + submitKey;
String key = RedisUtils.getCacheObject(cacheRepeatKey);
if (key == null) {
RedisUtils.setCacheObject(cacheRepeatKey, "", interval, TimeUnit.MILLISECONDS);
RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval));
KEY_CACHE.set(cacheRepeatKey);
} else {
String message = repeatSubmit.message();

View File

@ -1,17 +1,14 @@
package com.ruoyi.framework.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
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 com.ruoyi.framework.jackson.BigNumberSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -28,23 +25,22 @@ import java.util.TimeZone;
@Configuration
public class JacksonConfig {
@Primary
@Bean
public ObjectMapper getObjectMapper(Jackson2ObjectMapperBuilder builder, JacksonProperties jacksonProperties) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
simpleModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(jacksonProperties.getDateFormat());
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
objectMapper.registerModule(simpleModule);
objectMapper.setTimeZone(TimeZone.getDefault());
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
builder.modules(javaTimeModule);
builder.timeZone(TimeZone.getDefault());
log.info("初始化 jackson 配置");
return objectMapper;
};
}
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.framework.config;
import cn.hutool.extra.mail.MailAccount;
import com.ruoyi.framework.config.properties.MailProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* JavaMail 配置
*
* @author Michelle.Chung
*/
@Configuration
public class MailConfig {
@Bean
@ConditionalOnProperty(value = "mail.enabled", havingValue = "true")
public MailAccount mailAccount(MailProperties mailProperties) {
MailAccount account = new MailAccount();
account.setHost(mailProperties.getHost());
account.setPort(mailProperties.getPort());
account.setAuth(mailProperties.getAuth());
account.setFrom(mailProperties.getFrom());
account.setUser(mailProperties.getUser());
account.setPass(mailProperties.getPass());
account.setSocketFactoryPort(mailProperties.getPort());
account.setStarttlsEnable(mailProperties.getStarttlsEnable());
account.setSslEnable(mailProperties.getSslEnable());
account.setTimeout(mailProperties.getTimeout());
account.setConnectionTimeout(mailProperties.getConnectionTimeout());
return account;
}
}

View File

@ -1,28 +1,25 @@
package com.ruoyi.framework.config;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.utils.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.framework.config.properties.RedissonProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* redis配置
@ -32,37 +29,25 @@ import java.util.stream.Collectors;
@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Autowired
private RedisProperties redisProperties;
@Autowired
private RedissonProperties redissonProperties;
@Primary
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
String prefix = REDIS_PROTOCOL_PREFIX;
if (redisProperties.isSsl()) {
prefix = REDISS_PROTOCOL_PREFIX;
}
Config config = new Config();
@Autowired
private ObjectMapper objectMapper;
@Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
return config -> {
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE);
.setCodec(new JsonJacksonCodec(objectMapper));
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
@ -73,16 +58,7 @@ public class RedisConfig extends CachingConfigurerSupport {
// 集群配置方式 参考下方注释
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
if (ObjectUtil.isNotNull(clusterServersConfig)) {
// 使用集群模式
String finalPrefix = prefix;
List<String> nodes = redisProperties.getCluster().getNodes()
.stream()
.map(node -> finalPrefix + node)
.collect(Collectors.toList());
config.useClusterServers()
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setPassword(StringUtils.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(clusterServersConfig.getTimeout())
.setClientName(clusterServersConfig.getClientName())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
@ -92,12 +68,10 @@ public class RedisConfig extends CachingConfigurerSupport {
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode())
.setNodeAddresses(nodes);
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置");
return redissonClient;
};
}
/**
@ -112,7 +86,7 @@ public class RedisConfig extends CachingConfigurerSupport {
cacheConfig.setMaxSize(group.getMaxSize());
config.put(group.getGroupId(), cacheConfig);
}
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
return new RedissonSpringCacheManager(redissonClient, config, new JsonJacksonCodec(objectMapper));
}
/**

View File

@ -2,11 +2,12 @@ package com.ruoyi.framework.config;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.interceptor.SaRouteInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.ExcludeUrlProperties;
import com.ruoyi.framework.config.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -15,9 +16,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* sa-token 配置
*
@ -37,12 +35,14 @@ public class SaTokenConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器自定义验证规则
registry.addInterceptor(new SaRouteInterceptor((request, response, handler) -> {
ExcludeUrlProperties excludeUrlProperties = SpringUtils.getBean(ExcludeUrlProperties.class);
// 登录验证 -- 排除多个路径
SaRouter
// 获取所有的
.match("/**")
// 排除下不需要拦截的
.notMatch(securityProperties.getExcludes())
.notMatch(excludeUrlProperties.getExcludes())
// 对未排除的路径进行检查
.check(() -> {
// 检查是否登录 是否有token
@ -55,20 +55,14 @@ public class SaTokenConfig implements WebMvcConfigurer {
// }
});
}) {
@SuppressWarnings("all")
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LoginHelper.clearCache();
}
}).addPathPatterns("/**");
})).addPathPatterns("/**");
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (Style模式)
return new StpLogicJwtForStyle();
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
}

View File

@ -109,7 +109,7 @@ public class SwaggerConfig {
* 安全模式这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
List<SecurityScheme> apiKeyList = new ArrayList<>();
String header = saTokenConfig.getTokenName();
apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
return apiKeyList;

View File

@ -33,8 +33,8 @@ public class ThreadPoolConfig {
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(core);
executor.setCorePoolSize(core * 2);
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

View File

@ -0,0 +1,30 @@
package com.ruoyi.framework.config;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;
/**
* Undertow 自定义配置
*
* @author Lion Li
*/
@Configuration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
/**
* 设置 Undertow websocket 缓冲池
*/
@Override
public void customize(UndertowServletWebServerFactory factory) {
// 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
});
}
}

View File

@ -0,0 +1,61 @@
package com.ruoyi.framework.config.properties;
import cn.hutool.core.util.ReUtil;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.Getter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
import java.util.regex.Pattern;
/**
* 设置注解允许匿名访问的url
*
* @author Lion Li
*/
@Lazy
@Component
public class ExcludeUrlProperties implements InitializingBean {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
@Getter
private final List<String> excludes = new ArrayList<>();
@Override
public void afterPropertiesSet() {
String asterisk = "*";
RequestMappingHandlerMapping mapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> {
Set<String> patterns = info.getPatternsCondition().getPatterns();
patterns.forEach(url -> {
excludes.add(ReUtil.replaceAll(url, PATTERN, asterisk));
});
});
// 获取类上边的注解, 替代path variable *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> {
Set<String> patterns = info.getPatternsCondition().getPatterns();
patterns.forEach(url -> {
excludes.add(ReUtil.replaceAll(url, PATTERN, asterisk));
});
});
});
}
}

View File

@ -0,0 +1,71 @@
package com.ruoyi.framework.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* JavaMail 配置属性
*
* @author Michelle.Chung
*/
@Data
@Component
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
/**
* 过滤开关
*/
private Boolean enabled;
/**
* SMTP服务器域名
*/
private String host;
/**
* SMTP服务端口
*/
private Integer port;
/**
* 是否需要用户名密码验证
*/
private Boolean auth;
/**
* 用户名
*/
private String user;
/**
* 密码
*/
private String pass;
/**
* 发送方遵循RFC-822标准
*/
private String from;
/**
* 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展它将纯文本连接升级为加密连接TLS或SSL 而不是使用一个单独的加密通信端口
*/
private Boolean starttlsEnable;
/**
* 使用 SSL安全连接
*/
private Boolean sslEnable;
/**
* SMTP超时时长单位毫秒缺省值不超时
*/
private Long timeout;
/**
* Socket连接超时值单位毫秒缺省值不超时
*/
private Long connectionTimeout;
}

View File

@ -3,7 +3,6 @@ package com.ruoyi.framework.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.constant.Constants;
@ -18,7 +17,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
/**
* 用户行为 侦听器的实现
@ -36,13 +35,12 @@ public class UserActionListener implements SaTokenListener {
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserType userType = UserType.getUserType(loginId.toString());
if (userType == UserType.SYS_USER) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
LoginUser user = LoginHelper.getLoginUser();
String tokenValue = StpUtil.getTokenValueByLoginId(loginId);
UserOnlineDTO dto = new UserOnlineDTO();
dto.setIpaddr(ip);
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
@ -52,7 +50,7 @@ public class UserActionListener implements SaTokenListener {
dto.setTokenId(tokenValue);
dto.setUserName(user.getUsername());
dto.setDeptName(user.getDeptName());
RedisUtils.setCacheObject(Constants.ONLINE_TOKEN_KEY + tokenValue, dto, tokenConfig.getTimeout(), TimeUnit.SECONDS);
RedisUtils.setCacheObject(Constants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
} else if (userType == UserType.APP_USER) {
// app端 自行根据业务编写

View File

@ -5,10 +5,10 @@ import cn.dev33.satoken.util.SaFoxUtil;
import com.ruoyi.common.utils.redis.RedisUtils;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
@ -38,7 +38,7 @@ public class PlusSaTokenDao implements SaTokenDao {
if (timeout == SaTokenDao.NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, value);
} else {
RedisUtils.setCacheObject(key, value, timeout, TimeUnit.SECONDS);
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
}
}
@ -68,7 +68,8 @@ public class PlusSaTokenDao implements SaTokenDao {
*/
@Override
public long getTimeout(String key) {
return RedisUtils.getTimeToLive(key) / 1000;
long timeout = RedisUtils.getTimeToLive(key);
return timeout < 0 ? timeout : timeout / 1000;
}
/**
@ -87,7 +88,7 @@ public class PlusSaTokenDao implements SaTokenDao {
}
return;
}
RedisUtils.expire(key, timeout, TimeUnit.SECONDS);
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
@ -111,7 +112,7 @@ public class PlusSaTokenDao implements SaTokenDao {
if (timeout == SaTokenDao.NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, object);
} else {
RedisUtils.setCacheObject(key, object, timeout, TimeUnit.SECONDS);
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
}
}
@ -141,7 +142,8 @@ public class PlusSaTokenDao implements SaTokenDao {
*/
@Override
public long getObjectTimeout(String key) {
return RedisUtils.getTimeToLive(key) / 1000;
long timeout = RedisUtils.getTimeToLive(key);
return timeout < 0 ? timeout : timeout / 1000;
}
/**
@ -160,7 +162,7 @@ public class PlusSaTokenDao implements SaTokenDao {
}
return;
}
RedisUtils.expire(key, timeout, TimeUnit.SECONDS);
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}

View File

@ -33,6 +33,7 @@ import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -135,6 +136,7 @@ public class GenTableServiceImpl implements IGenTableService {
* @param genTable 业务信息
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void updateGenTable(GenTable genTable) {
String options = JsonUtils.toJsonString(genTable.getParams());
@ -153,6 +155,7 @@ public class GenTableServiceImpl implements IGenTableService {
* @param tableIds 需要删除的数据ID
* @return 结果
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteGenTableByIds(Long[] tableIds) {
List<Long> ids = Arrays.asList(tableIds);
@ -165,6 +168,7 @@ public class GenTableServiceImpl implements IGenTableService {
*
* @param tableList 导入表列表
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void importGenTable(List<GenTable> tableList) {
String operName = LoginHelper.getUsername();
@ -284,6 +288,7 @@ public class GenTableServiceImpl implements IGenTableService {
*
* @param tableName 表名称
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void synchDb(String tableName) {
GenTable table = baseMapper.selectGenTableByName(tableName);

View File

@ -35,6 +35,11 @@ public interface OssConstant {
*/
List<Integer> SYSTEM_DATA_IDS = Arrays.asList(1, 2, 3, 4);
/**
* 云服务商
*/
String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu"};
/**
* https 状态
*/

View File

@ -0,0 +1,188 @@
package com.ruoyi.oss.core;
import cn.hutool.core.util.IdUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.CreateBucketRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.PolicyType;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* S3 存储协议 所有兼容S3协议的云厂商均支持
* 阿里云 腾讯云 七牛云 minio
*
* @author Lion Li
*/
public class OssClient {
private final String configKey;
private final OssProperties properties;
private final AmazonS3 client;
public OssClient(String configKey, OssProperties ossProperties) {
this.configKey = configKey;
this.properties = ossProperties;
try {
AwsClientBuilder.EndpointConfiguration endpointConfig =
new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
ClientConfiguration clientConfig = new ClientConfiguration();
if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
clientConfig.setProtocol(Protocol.HTTPS);
} else {
clientConfig.setProtocol(Protocol.HTTP);
}
this.client = AmazonS3Client.builder()
.withEndpointConfiguration(endpointConfig)
.withClientConfiguration(clientConfig)
.withCredentials(credentialsProvider)
.disableChunkedEncoding()
.build();
createBucket();
} catch (Exception e) {
if (e instanceof OssException) {
throw e;
}
throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
}
public void createBucket() {
try {
String bucketName = properties.getBucketName();
if (client.doesBucketExistV2(bucketName)) {
return;
}
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
client.createBucket(createBucketRequest);
client.setBucketPolicy(bucketName, getPolicy(bucketName, PolicyType.READ));
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
}
}
public UploadResult upload(byte[] data, String path, String contentType) {
return upload(new ByteArrayInputStream(data), path, contentType);
}
public UploadResult upload(InputStream inputStream, String path, String contentType) {
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(contentType);
metadata.setContentLength(inputStream.available());
client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
} catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
}
public void delete(String path) {
path = path.replace(getUrl() + "/", "");
try {
client.deleteObject(properties.getBucketName(), path);
} catch (Exception e) {
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
}
}
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
public String getUrl() {
String domain = properties.getDomain();
if (StringUtils.isNotBlank(domain)) {
return domain;
}
String endpoint = properties.getEndpoint();
String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
// 云服务商直接返回
if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)){
return header + properties.getBucketName() + "." + endpoint;
}
// minio 单独处理
return header + endpoint + "/" + properties.getBucketName();
}
public String getPath(String prefix, String suffix) {
// 生成uuid
String uuid = IdUtil.fastSimpleUUID();
// 文件路径
String path = DateUtils.datePath() + "/" + uuid;
if (StringUtils.isNotBlank(prefix)) {
path = prefix + "/" + path;
}
return path + suffix;
}
public String getConfigKey() {
return configKey;
}
private static String getPolicy(String bucketName, PolicyType policyType) {
StringBuilder builder = new StringBuilder();
builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
if (policyType == PolicyType.WRITE) {
builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n");
} else if (policyType == PolicyType.READ_WRITE) {
builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n");
} else {
builder.append("\"s3:GetBucketLocation\"\n");
}
builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n},\n");
if (policyType == PolicyType.READ) {
builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n},\n");
}
builder.append("{\n\"Action\": ");
switch (policyType) {
case WRITE:
builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
break;
case READ_WRITE:
builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n");
break;
default:
builder.append("\"s3:GetObject\",\n");
break;
}
builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
return builder.toString();
}
}

View File

@ -1,52 +0,0 @@
package com.ruoyi.oss.enumd;
import com.ruoyi.oss.service.impl.AliyunOssStrategy;
import com.ruoyi.oss.service.impl.MinioOssStrategy;
import com.ruoyi.oss.service.impl.QcloudOssStrategy;
import com.ruoyi.oss.service.impl.QiniuOssStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对象存储服务商枚举
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum OssEnumd {
/**
* 七牛云
*/
QINIU("qiniu", QiniuOssStrategy.class),
/**
* 阿里云
*/
ALIYUN("aliyun", AliyunOssStrategy.class),
/**
* 腾讯云
*/
QCLOUD("qcloud", QcloudOssStrategy.class),
/**
* minio
*/
MINIO("minio", MinioOssStrategy.class);
private final String value;
private final Class<?> beanClass;
public static OssEnumd find(String value) {
for (OssEnumd enumd : values()) {
if (enumd.getValue().equals(value)) {
return enumd;
}
}
return null;
}
}

View File

@ -1,19 +1,3 @@
/*
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the dreamlu.net developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: Chill 庄骞 (smallchill@163.com)
*/
package com.ruoyi.oss.enumd;
import lombok.AllArgsConstructor;

View File

@ -3,15 +3,15 @@ package com.ruoyi.oss.factory;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.IOssStrategy;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 文件上传Factory
*
@ -20,17 +20,19 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class OssFactory {
private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
/**
* 初始化工厂
*/
public static void init() {
log.info("初始化OSS工厂");
RedisUtils.subscribe(OssConstant.CACHE_CONFIG_KEY, String.class, type -> {
AbstractOssStrategy strategy = getStrategy(type);
RedisUtils.subscribe(OssConstant.CACHE_CONFIG_KEY, String.class, configKey -> {
OssClient client = getClient(configKey);
// 未初始化不处理
if (strategy.isInit) {
refresh(type);
log.info("订阅刷新OSS配置 => " + type);
if (client != null) {
refresh(configKey);
log.info("订阅刷新OSS配置 => " + configKey);
}
});
}
@ -38,42 +40,38 @@ public class OssFactory {
/**
* 获取默认实例
*/
public static IOssStrategy instance() {
public static OssClient instance() {
// 获取redis 默认类型
String type = RedisUtils.getCacheObject(OssConstant.CACHE_CONFIG_KEY);
if (StringUtils.isEmpty(type)) {
String configKey = RedisUtils.getCacheObject(OssConstant.CACHE_CONFIG_KEY);
if (StringUtils.isEmpty(configKey)) {
throw new OssException("文件存储服务类型无法找到!");
}
return instance(type);
return instance(configKey);
}
/**
* 根据类型获取实例
*/
public static IOssStrategy instance(String type) {
OssEnumd enumd = OssEnumd.find(type);
if (enumd == null) {
throw new OssException("文件存储服务类型无法找到!");
public static OssClient instance(String configKey) {
OssClient client = getClient(configKey);
if (client == null) {
refresh(configKey);
return getClient(configKey);
}
AbstractOssStrategy strategy = getStrategy(type);
if (!strategy.isInit) {
refresh(type);
}
return strategy;
return client;
}
private static void refresh(String type) {
Object json = RedisUtils.getCacheObject(OssConstant.SYS_OSS_KEY + type);
private static void refresh(String configKey) {
Object json = RedisUtils.getCacheObject(OssConstant.SYS_OSS_KEY + configKey);
OssProperties properties = JsonUtils.parseObject(json.toString(), OssProperties.class);
if (properties == null) {
throw new OssException("系统异常, '" + type + "'配置信息不存在!");
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
}
getStrategy(type).init(properties);
CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
}
private static AbstractOssStrategy getStrategy(String type) {
OssEnumd enumd = OssEnumd.find(type);
return (AbstractOssStrategy) SpringUtils.getBean(enumd.getBeanClass());
private static OssClient getClient(String configKey) {
return CLIENT_CACHE.get(configKey);
}
}

View File

@ -11,10 +11,15 @@ import lombok.Data;
public class OssProperties {
/**
* 域名
* 访问站点
*/
private String endpoint;
/**
* 自定义域名
*/
private String domain;
/**
* 前缀
*/

View File

@ -1,73 +0,0 @@
package com.ruoyi.oss.service;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import java.io.InputStream;
/**
* 对象存储策略
*
* @author Lion Li
*/
public interface IOssStrategy {
/**
* 创建存储桶
*/
void createBucket();
/**
* 获取服务商类型
* @return 对象存储服务商枚举
*/
OssEnumd getServiceType();
/**
* 文件上传
*
* @param data 文件字节数组
* @param path 文件路径包含文件名
* @param contentType 文件类型
* @return 返回http地址
*/
UploadResult upload(byte[] data, String path, String contentType);
/**
* 文件删除
*
* @param path 文件路径包含文件名
*/
void delete(String path);
/**
* 文件上传
*
* @param data 文件字节数组
* @param suffix 后缀
* @param contentType 文件类型
* @return 返回http地址
*/
UploadResult uploadSuffix(byte[] data, String suffix, String contentType);
/**
* 文件上传
*
* @param inputStream 字节流
* @param path 文件路径包含文件名
* @param contentType 文件类型
* @return 返回http地址
*/
UploadResult upload(InputStream inputStream, String path, String contentType);
/**
* 文件上传
*
* @param inputStream 字节流
* @param suffix 后缀
* @param contentType 文件类型
* @return 返回http地址
*/
UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);
}

View File

@ -1,69 +0,0 @@
package com.ruoyi.oss.service.abstractd;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.IOssStrategy;
import java.io.InputStream;
/**
* 对象存储策略(支持七牛阿里云腾讯云minio)
*
* @author Lion Li
*/
public abstract class AbstractOssStrategy implements IOssStrategy {
protected OssProperties properties;
public boolean isInit = false;
public void init(OssProperties properties) {
this.properties = properties;
}
@Override
public abstract void createBucket();
@Override
public abstract OssEnumd getServiceType();
public String getPath(String prefix, String suffix) {
// 生成uuid
String uuid = IdUtil.fastSimpleUUID();
// 文件路径
String path = DateUtils.datePath() + "/" + uuid;
if (StringUtils.isNotBlank(prefix)) {
path = prefix + "/" + path;
}
return path + suffix;
}
@Override
public abstract UploadResult upload(byte[] data, String path, String contentType);
@Override
public abstract void delete(String path);
@Override
public UploadResult upload(InputStream inputStream, String path, String contentType) {
byte[] data = IoUtil.readBytes(inputStream);
return this.upload(data, path, contentType);
}
@Override
public abstract UploadResult uploadSuffix(byte[] data, String suffix, String contentType);
@Override
public abstract UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);
/**
* 获取域名访问链接
*
* @return 域名访问链接
*/
public abstract String getEndpointLink();
}

View File

@ -1,120 +0,0 @@
package com.ruoyi.oss.service.impl;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* 阿里云存储策略
*
* @author Lion Li
*/
@Component
public class AliyunOssStrategy extends AbstractOssStrategy {
private OSSClient client;
@Override
public void init(OssProperties ossProperties) {
super.init(ossProperties);
try {
ClientConfiguration configuration = new ClientConfiguration();
if (OssConstant.IS_HTTPS.equals(ossProperties.getIsHttps())) {
configuration.setProtocol(Protocol.HTTPS);
}
DefaultCredentialProvider credentialProvider = new DefaultCredentialProvider(
properties.getAccessKey(), properties.getSecretKey());
client = new OSSClient(properties.getEndpoint(), credentialProvider, configuration);
createBucket();
} catch (Exception e) {
throw new OssException("阿里云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
isInit = true;
}
@Override
public void createBucket() {
try {
String bucketName = properties.getBucketName();
if (client.doesBucketExist(bucketName)) {
return;
}
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
client.createBucket(createBucketRequest);
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对阿里云配置信息:[" + e.getMessage() + "]");
}
}
@Override
public OssEnumd getServiceType() {
return OssEnumd.ALIYUN;
}
@Override
public UploadResult upload(byte[] data, String path, String contentType) {
return upload(new ByteArrayInputStream(data), path, contentType);
}
@Override
public UploadResult upload(InputStream inputStream, String path, String contentType) {
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(contentType);
client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
} catch (Exception e) {
throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build(); }
@Override
public void delete(String path) {
path = path.replace(getEndpointLink() + "/", "");
try {
client.deleteObject(properties.getBucketName(), path);
} catch (Exception e) {
throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
}
}
@Override
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public String getEndpointLink() {
String endpoint = properties.getEndpoint();
StringBuilder sb = new StringBuilder(endpoint);
if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
sb.insert(7, properties.getBucketName() + ".");
} else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
sb.insert(8, properties.getBucketName() + ".");
} else {
throw new OssException("Endpoint配置错误");
}
return sb.toString();
}
}

View File

@ -1,194 +0,0 @@
package com.ruoyi.oss.service.impl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.enumd.PolicyType;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import io.minio.*;
import io.minio.http.HttpUtils;
import okhttp3.HttpUrl;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* minio存储策略
*
* @author Lion Li
*/
@Component
public class MinioOssStrategy extends AbstractOssStrategy {
private MinioClient minioClient;
@Override
public void init(OssProperties ossProperties) {
super.init(ossProperties);
try {
MinioClient.Builder builder = MinioClient.builder();
if (OssConstant.IS_HTTPS.equals(ossProperties.getIsHttps())) {
HttpUrl url = HttpUtils.getBaseUrl(properties.getEndpoint())
.newBuilder().scheme("https").build();
builder.endpoint(url);
} else {
builder.endpoint(properties.getEndpoint());
}
minioClient = builder.credentials(properties.getAccessKey(), properties.getSecretKey()).build();
createBucket();
} catch (Exception e) {
throw new OssException("Minio存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
isInit = true;
}
@Override
public void createBucket() {
try {
String bucketName = properties.getBucketName();
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (exists) {
return;
}
// 不存在就创建桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(getPolicy(bucketName, PolicyType.READ))
.build());
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对Minio配置信息:[" + e.getMessage() + "]");
}
}
@Override
public OssEnumd getServiceType() {
return OssEnumd.MINIO;
}
@Override
public UploadResult upload(byte[] data, String path, String contentType) {
return upload(new ByteArrayInputStream(data), path, contentType);
}
@Override
public UploadResult upload(InputStream inputStream, String path, String contentType) {
try {
// 解决 inputStream.available() socket 下传输延迟问题 导致获取数值不精确
Thread.sleep(1000);
minioClient.putObject(PutObjectArgs.builder()
.bucket(properties.getBucketName())
.object(path)
.contentType(StringUtils.blankToDefault(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE))
.stream(inputStream, inputStream.available(), -1)
.build());
} catch (Exception e) {
throw new OssException("上传文件失败请核对Minio配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
}
@Override
public void delete(String path) {
path = path.replace(getEndpointLink() + "/", "");
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(properties.getBucketName())
.object(path)
.build());
} catch (Exception e) {
throw new OssException(e.getMessage());
}
}
@Override
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public String getEndpointLink() {
return properties.getEndpoint() + "/" + properties.getBucketName();
}
private String getPolicy(String bucketName, PolicyType policyType) {
StringBuilder builder = new StringBuilder();
builder.append("{\n");
builder.append(" \"Statement\": [\n");
builder.append(" {\n");
builder.append(" \"Action\": [\n");
if (policyType == PolicyType.WRITE) {
builder.append(" \"s3:GetBucketLocation\",\n");
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
} else if (policyType == PolicyType.READ_WRITE) {
builder.append(" \"s3:GetBucketLocation\",\n");
builder.append(" \"s3:ListBucket\",\n");
builder.append(" \"s3:ListBucketMultipartUploads\"\n");
} else {
builder.append(" \"s3:GetBucketLocation\"\n");
}
builder.append(" ],\n");
builder.append(" \"Effect\": \"Allow\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n");
builder.append(" },\n");
if (PolicyType.READ.equals(policyType)) {
builder.append(" {\n");
builder.append(" \"Action\": [\n");
builder.append(" \"s3:ListBucket\"\n");
builder.append(" ],\n");
builder.append(" \"Effect\": \"Deny\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("\"\n");
builder.append(" },\n");
}
builder.append(" {\n");
builder.append(" \"Action\": ");
switch (policyType) {
case WRITE:
builder.append("[\n");
builder.append(" \"s3:AbortMultipartUpload\",\n");
builder.append(" \"s3:DeleteObject\",\n");
builder.append(" \"s3:ListMultipartUploadParts\",\n");
builder.append(" \"s3:PutObject\"\n");
builder.append(" ],\n");
break;
case READ_WRITE:
builder.append("[\n");
builder.append(" \"s3:AbortMultipartUpload\",\n");
builder.append(" \"s3:DeleteObject\",\n");
builder.append(" \"s3:GetObject\",\n");
builder.append(" \"s3:ListMultipartUploadParts\",\n");
builder.append(" \"s3:PutObject\"\n");
builder.append(" ],\n");
break;
default:
builder.append("\"s3:GetObject\",\n");
break;
}
builder.append(" \"Effect\": \"Allow\",\n");
builder.append(" \"Principal\": \"*\",\n");
builder.append(" \"Resource\": \"arn:aws:s3:::");
builder.append(bucketName);
builder.append("/*\"\n");
builder.append(" }\n");
builder.append(" ],\n");
builder.append(" \"Version\": \"2012-10-17\"\n");
builder.append("}\n");
return builder.toString();
}
}

View File

@ -1,125 +0,0 @@
package com.ruoyi.oss.service.impl;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* 腾讯云存储策略
*
* @author Lion Li
*/
@Component
public class QcloudOssStrategy extends AbstractOssStrategy {
private COSClient client;
@Override
public void init(OssProperties ossProperties) {
super.init(ossProperties);
try {
COSCredentials credentials = new BasicCOSCredentials(
properties.getAccessKey(), properties.getSecretKey());
// 初始化客户端配置
ClientConfig clientConfig = new ClientConfig();
// 设置bucket所在的区域华南gz 华北tj 华东sh
clientConfig.setRegion(new Region(properties.getRegion()));
if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
clientConfig.setHttpProtocol(HttpProtocol.https);
} else {
clientConfig.setHttpProtocol(HttpProtocol.http);
}
client = new COSClient(credentials, clientConfig);
createBucket();
} catch (Exception e) {
throw new OssException("腾讯云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
isInit = true;
}
@Override
public void createBucket() {
try {
String bucketName = properties.getBucketName();
if (client.doesBucketExist(bucketName)) {
return;
}
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
client.createBucket(createBucketRequest);
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对腾讯云配置信息:[" + e.getMessage() + "]");
}
}
@Override
public OssEnumd getServiceType() {
return OssEnumd.QCLOUD;
}
@Override
public UploadResult upload(byte[] data, String path, String contentType) {
return upload(new ByteArrayInputStream(data), path, contentType);
}
@Override
public UploadResult upload(InputStream inputStream, String path, String contentType) {
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(contentType);
client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
} catch (Exception e) {
throw new OssException("上传文件失败,请检查腾讯云配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
}
@Override
public void delete(String path) {
path = path.replace(getEndpointLink() + "/", "");
try {
client.deleteObject(new DeleteObjectRequest(properties.getBucketName(), path));
} catch (Exception e) {
throw new OssException("上传文件失败,请检腾讯云查配置信息:[" + e.getMessage() + "]");
}
}
@Override
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public String getEndpointLink() {
String endpoint = properties.getEndpoint();
StringBuilder sb = new StringBuilder(endpoint);
if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
sb.insert(7, properties.getBucketName() + ".");
} else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
sb.insert(8, properties.getBucketName() + ".");
} else {
throw new OssException("Endpoint配置错误");
}
return sb.toString();
}
}

View File

@ -1,127 +0,0 @@
package com.ruoyi.oss.service.impl;
import cn.hutool.core.util.ArrayUtil;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;
import java.io.InputStream;
/**
* 七牛云存储策略
*
* @author Lion Li
*/
@Component
public class QiniuOssStrategy extends AbstractOssStrategy {
private UploadManager uploadManager;
private BucketManager bucketManager;
private Auth auth;
@Override
public void init(OssProperties ossProperties) {
super.init(ossProperties);
try {
Configuration config = new Configuration(getRegion(properties.getRegion()));
// https设置
config.useHttpsDomains = OssConstant.IS_HTTPS.equals(properties.getIsHttps());
uploadManager = new UploadManager(config);
auth = Auth.create(properties.getAccessKey(), properties.getSecretKey());
bucketManager = new BucketManager(auth, config);
createBucket();
} catch (Exception e) {
throw new OssException("七牛云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
}
isInit = true;
}
@Override
public void createBucket() {
try {
String bucketName = properties.getBucketName();
if (ArrayUtil.contains(bucketManager.buckets(), bucketName)) {
return;
}
bucketManager.createBucket(bucketName, properties.getRegion());
} catch (Exception e) {
throw new OssException("创建Bucket失败, 请核对七牛云配置信息:[" + e.getMessage() + "]");
}
}
@Override
public OssEnumd getServiceType() {
return OssEnumd.QINIU;
}
@Override
public UploadResult upload(byte[] data, String path, String contentType) {
try {
String token = auth.uploadToken(properties.getBucketName());
Response res = uploadManager.put(data, path, token, null, contentType, false);
if (!res.isOK()) {
throw new RuntimeException("上传七牛出错:" + res.error);
}
} catch (Exception e) {
throw new OssException("上传文件失败,请核对七牛配置信息:[" + e.getMessage() + "]");
}
return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
}
@Override
public void delete(String path) {
try {
path = path.replace(getEndpointLink() + "/", "");
Response res = bucketManager.delete(properties.getBucketName(), path);
if (!res.isOK()) {
throw new RuntimeException("删除七牛文件出错:" + res.error);
}
} catch (Exception e) {
throw new OssException(e.getMessage());
}
}
@Override
public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
return upload(data, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
}
@Override
public String getEndpointLink() {
return properties.getEndpoint();
}
private Region getRegion(String region) {
switch (region) {
case "z0":
return Region.region0();
case "z1":
return Region.region1();
case "z2":
return Region.region2();
case "na0":
return Region.regionNa0();
case "as0":
return Region.regionAs0();
default:
return Region.autoRegion();
}
}
}

View File

@ -0,0 +1,46 @@
package com.ruoyi.sms.config;
import com.ruoyi.sms.config.properties.SmsProperties;
import com.ruoyi.sms.core.AliyunSmsTemplate;
import com.ruoyi.sms.core.SmsTemplate;
import com.ruoyi.sms.core.TencentSmsTemplate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 短信配置类
* 需要哪个 打开哪个的注释即可
*
* @author Lion Li
* @version 4.2.0
*/
@Configuration
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

@ -0,0 +1,47 @@
package com.ruoyi.sms.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* SMS短信 配置属性
*
* @author Lion Li
* @version 4.2.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
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

@ -0,0 +1,66 @@
package com.ruoyi.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 com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.sms.config.properties.SmsProperties;
import com.ruoyi.sms.entity.SmsResult;
import com.ruoyi.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(resp)
.build();
} catch (Exception e) {
throw new SmsException(e.getMessage());
}
}
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.sms.core;
import com.ruoyi.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

@ -0,0 +1,81 @@
package com.ruoyi.sms.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.sms.config.properties.SmsProperties;
import com.ruoyi.sms.entity.SmsResult;
import com.ruoyi.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(",")).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(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());
}
}
}

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