sendMessageWithAttachments(String to, String subject, String text, String[] paths) {
+ // 使用 Java 8 的 Stream API 对 paths 数组进行处理
+ // Arrays.stream(paths) 将 paths 数组转换为流
+ // .map(File::new) 对流中的每个元素(即文件路径字符串)应用 File 类的构造函数,将其转换为 File 对象
+ // .toArray(File[]::new) 将流中的元素收集到一个新的 File 类型数组中
+ File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
+ // 调用 MailUtils 工具类的 sendText 方法,用于发送带有多个附件的文本邮件
+ // to 参数为收件人的邮箱地址
+ // subject 参数是邮件的主题
+ // text 参数是邮件的正文内容
+ // array 是包含多个附件文件的 File 数组
+ MailUtils.sendText(to, subject, text, array);
+ // 调用 R 类的 ok 方法,将操作成功的默认信息封装到 R 对象中并返回
+ // 表示带有多个附件的邮件发送操作已成功完成
+ return R.ok();
+ }
+}
diff --git a/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
new file mode 100644
index 0000000..7bdca4a
--- /dev/null
+++ b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisCacheController.java
@@ -0,0 +1,98 @@
+package org.dromara.demo.controller;
+
+import cn.hutool.core.thread.ThreadUtil;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+
+/**
+ * spring-cache 演示案例
+ *
+ * @author Lion Li
+ */
+// 类级别 缓存统一配置
+//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE)
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/cache")
+public class RedisCacheController {
+
+ /**
+ * 测试 @Cacheable
+ *
+ * 表示这个方法有了缓存的功能,方法的返回值会被缓存下来
+ * 下一次调用该方法前,会去检查是否缓存中已经有值
+ * 如果有就直接返回,不调用方法
+ * 如果没有,就调用方法,然后把结果缓存起来
+ * 这个注解「一般用在查询方法上」
+ *
+ * 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
+ * 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
+ *
+ * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
+ */
+ @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
+ @GetMapping("/test1")
+ public R test1(String key, String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试 @CachePut
+ *
+ * 加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用
+ * 它「通常用在新增或者实时更新方法上」
+ *
+ * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
+ */
+ @CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
+ @GetMapping("/test2")
+ public R test2(String key, String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试 @CacheEvict
+ *
+ * 使用了CacheEvict注解的方法,会清空指定缓存
+ * 「一般用在删除的方法上」
+ *
+ * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
+ */
+ @CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
+ @GetMapping("/test3")
+ public R test3(String key, String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试设置过期时间
+ * 手动设置过期时间10秒
+ * 11秒后获取 判断是否相等
+ */
+ @GetMapping("/test6")
+ // 定义一个公共方法 test6,返回类型为 R,通常 R 是自定义的统一响应结果类,这里泛型指定为 Boolean 类型,表示返回的结果是布尔值
+ public R test6(String key, String value) {
+ // 调用 RedisUtils 工具类的 setCacheObject 方法,将传入的 key 和 value 存储到 Redis 缓存中
+ RedisUtils.setCacheObject(key, value);
+ // 调用 RedisUtils 工具类的 expire 方法,为刚刚存储到 Redis 中的 key 设置过期时间为 10 秒
+ // 该方法会返回一个布尔值,表示设置过期时间是否成功
+ boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10));
+ // 在控制台输出设置过期时间的结果,方便调试查看
+ System.out.println("***********" + flag);
+ // 使用 ThreadUtil 工具类让当前线程休眠 11 秒,确保 Redis 中的 key 已经过期
+ ThreadUtil.sleep(11 * 1000);
+ // 调用 RedisUtils 工具类的 getCacheObject 方法,尝试从 Redis 中获取指定 key 对应的 value
+ Object obj = RedisUtils.getCacheObject(key);
+ // 将比较结果(即传入的 value 是否和从 Redis 中获取的 obj 相等)封装到自定义的统一响应结果类 R 中并返回
+ return R.ok(value.equals(obj));}
+}
diff --git a/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java
new file mode 100644
index 0000000..67f30bf
--- /dev/null
+++ b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisLockController.java
@@ -0,0 +1,69 @@
+package org.dromara.demo.controller;
+
+import cn.hutool.core.thread.ThreadUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.baomidou.lock.annotation.Lock4j;
+import com.baomidou.lock.executor.RedissonLockExecutor;
+import org.dromara.common.core.domain.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalTime;
+
+
+/**
+ * 测试分布式锁的样例
+ *
+ * @author shenxinquan
+ */
+@Slf4j
+@RestController
+@RequestMapping("/demo/redisLock")
+public class RedisLockController {
+
+ @Autowired
+ private LockTemplate lockTemplate;
+
+ /**
+ * 测试lock4j 注解
+ */
+ @Lock4j(keys = {"#key"})
+ @GetMapping("/testLock4j")
+ // 定义一个公共方法 testLock4j,返回类型为 R。R 通常是自定义的统一响应结果类,泛型指定为 String 类型,表示返回的数据部分为字符串
+ public R testLock4j(String key, String value) {
+ // 在控制台输出方法开始执行的信息,包含传入的 key 和当前的本地时间
+ System.out.println("start:" + key + ",time:" + LocalTime.now());
+ // 调用 ThreadUtil 工具类的 sleep 方法,让当前线程休眠 10000 毫秒(即 10 秒)
+ ThreadUtil.sleep(10000);
+ // 在控制台输出方法执行结束的信息,包含传入的 key 和当前的本地时间
+ System.out.println("end :" + key + ",time:" + LocalTime.now());
+ // 将操作成功的消息和传入的 value 封装到自定义的统一响应结果类 R 中并返回
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试lock4j 工具
+ */
+ @GetMapping("/testLock4jLockTemplate")
+ public R testLock4jLockTemplate(String key, String value) {
+ final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class);
+ if (null == lockInfo) {
+ throw new RuntimeException("业务处理中,请稍后再试");
+ }
+ // 获取锁成功,处理业务
+ try {
+ ThreadUtil.sleep(8000);
+ System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName());
+ } finally {
+ //释放锁
+ lockTemplate.releaseLock(lockInfo);
+ }
+ //结束
+ return R.ok("操作成功", value);
+ }
+
+}
diff --git a/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java
new file mode 100644
index 0000000..ac143c2
--- /dev/null
+++ b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisPubSubController.java
@@ -0,0 +1,62 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Redis 发布订阅 演示案例
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/redis/pubsub")
+public class RedisPubSubController {
+
+ /**
+ * 发布消息
+ *
+ * @param key 通道Key
+ * @param value 发送内容
+ */
+ @GetMapping("/pub")
+ // 定义一个公共方法 pub,返回类型为 R。R 通常是自定义的统一响应结果类,
+// Void 表示该方法不返回具体的数据内容,主要用于返回操作结果信息
+ public R pub(String key, String value) {
+ // 调用 RedisUtils 工具类的 publish 方法,用于在 Redis 中进行消息发布操作
+ // 该方法接收三个参数:key 表示消息发布的通道名称,value 是要发布的消息内容
+ // 第三个参数是一个消费者函数,在消息发布时会执行该函数内的逻辑
+ RedisUtils.publish(key, value, consumer -> {
+ // 当消息发布时,在控制台输出发布的通道名称和发送的消息值
+ System.out.println("发布通道 => " + key + ", 发送值 => " + value);
+ });
+ // 调用 R 类的 ok 方法,将操作成功的信息封装成统一响应结果类 R 的对象并返回
+ return R.ok("操作成功");
+ }
+
+ /**
+ * 订阅消息
+ *
+ * @param key 通道Key
+ */
+ @GetMapping("/sub")
+ // 定义一个公共方法 sub,返回类型为 R。R 通常是自定义的封装响应结果的类,
+// Void 表明该方法的响应结果不携带具体的数据,仅传达操作状态
+ public R sub(String key) {
+ // 调用 RedisUtils 工具类的 subscribe 方法,目的是在 Redis 中订阅指定通道的消息
+ // key 参数代表要订阅的通道名称
+ // String.class 指定了接收到的消息的数据类型为 String
+ // 最后一个参数是一个 Lambda 表达式,作为消息处理的回调函数
+ RedisUtils.subscribe(key, String.class, msg -> {
+ // 当接收到订阅通道的消息时,在控制台输出订阅的通道名称以及接收到的消息内容
+ System.out.println("订阅通道 => " + key + ", 接收值 => " + msg);
+ });
+ // 调用 R 类的 ok 方法,将 "操作成功" 这个消息封装到 R 对象中并返回,
+ // 以此告知调用者订阅操作已经成功完成
+ return R.ok("操作成功");
+ }
+}
diff --git a/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java
new file mode 100644
index 0000000..f8adf7d
--- /dev/null
+++ b/ruoyi-demo/src/main/java/org/dromara/demo/controller/RedisRateLimiterController.java
@@ -0,0 +1,64 @@
+package org.dromara.demo.controller;
+
+import org.dromara.common.core.domain.R;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * 测试分布式限流样例
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestController
+@RequestMapping("/demo/rateLimiter")
+public class RedisRateLimiterController {
+
+ /**
+ * 测试全局限流
+ * 全局影响
+ */
+ @RateLimiter(count = 2, time = 10)
+ @GetMapping("/test")
+ public R test(String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试请求IP限流
+ * 同一IP请求受影响
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
+ @GetMapping("/testip")
+ public R testip(String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试集群实例限流
+ * 启动两个后端服务互不影响
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
+ @GetMapping("/testcluster")
+ public R testcluster(String value) {
+ return R.ok("操作成功", value);
+ }
+
+ /**
+ * 测试请求IP限流(key基于参数获取)
+ * 同一IP请求受影响
+ *
+ * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
+ */
+ @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
+ @GetMapping("/testObj")
+ public R testObj(String value) {
+ return R.ok("操作成功", value);
+ }
+
+}
diff --git a/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java b/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java
new file mode 100644
index 0000000..be36292
--- /dev/null
+++ b/ruoyi-demo/src/main/java/org/dromara/demo/controller/SmsController.java
@@ -0,0 +1,101 @@
+package org.dromara.demo.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.sms4j.api.SmsBlend;
+import org.dromara.sms4j.api.entity.SmsResponse;
+import org.dromara.sms4j.core.factory.SmsFactory;
+import org.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.LinkedHashMap;
+
+/**
+ * 短信演示案例
+ * 请先阅读文档 否则无法使用
+ *
+ * @author Lion Li
+ * @version 4.2.0
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/demo/sms")
+public class SmsController {
+ @GetMapping("/sendAliyun")
+ // 定义一个公共方法 sendAliyun,返回类型为 R