diff --git a/pom.xml b/pom.xml
index 00701ba30..d49efbdfb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,8 +49,8 @@
3.4.1
3.13.6
- 1.16.14
- 1.3.0.Final
+ 1.16.14
+ 1.4.1.Final
0.9.1
1.2.75
5.5.6
@@ -252,7 +252,7 @@
org.projectlombok
lombok
- ${org.projectlombok.version}
+ ${lombok.version}
@@ -270,7 +270,7 @@
org.projectlombok
lombok
- ${org.projectlombok.version}
+ ${lombok.version}
@@ -280,11 +280,32 @@
1.21
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+ org.mapstruct
+ mapstruct-jdk8
+ ${mapstruct.version}
+
+
cn.hutool
hutool-core
${hutool.version}
+
+ cn.hutool
+ hutool-extra
+ ${hutool.version}
+
+
+ cn.hutool
+ hutool-captcha
+ ${hutool.version}
+
@@ -303,12 +324,12 @@
org.mapstruct
mapstruct-processor
- ${org.mapstruct.version}
+ ${mapstruct.version}
org.projectlombok
lombok
- ${org.projectlombok.version}
+ ${lombok.version}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaConfig.java b/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaConfig.java
new file mode 100644
index 000000000..b1fb8f28c
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaConfig.java
@@ -0,0 +1,9 @@
+package cn.iocoder.dashboard.framework.captcha.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(CaptchaProperties.class)
+public class CaptchaConfig {
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaProperties.java b/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaProperties.java
new file mode 100644
index 000000000..9530ea1ee
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/captcha/config/CaptchaProperties.java
@@ -0,0 +1,31 @@
+package cn.iocoder.dashboard.framework.captcha.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@ConfigurationProperties(prefix = "yudao.captcha")
+@Validated
+@Data
+public class CaptchaProperties {
+
+ /**
+ * 验证码的过期时间
+ */
+ @NotNull(message = "验证码的过期时间不为空")
+ private Duration timeout;
+ /**
+ * 验证码的高度
+ */
+ @NotNull(message = "验证码的高度不能为空")
+ private Integer height;
+ /**
+ * 验证码的宽度
+ */
+ @NotNull(message = "验证码的宽度不能为空")
+ private Integer width;
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/captcha/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/captcha/package-info.java
new file mode 100644
index 000000000..432e3d449
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/captcha/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 基于 Hutool captcha 库,实现验证码功能
+ */
+package cn.iocoder.dashboard.framework.captcha;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.http b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.http
new file mode 100644
index 000000000..90859ca70
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.http
@@ -0,0 +1,2 @@
+### 请求 /captcha/get-image 接口 => 成功
+GET {{baseUrl}}/captcha/get-image
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.java
new file mode 100644
index 000000000..e7b1b0373
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/SysCaptchaController.java
@@ -0,0 +1,30 @@
+package cn.iocoder.dashboard.modules.system.controller.common;
+
+import cn.iocoder.dashboard.common.pojo.CommonResult;
+import cn.iocoder.dashboard.modules.system.controller.common.vo.SysCaptchaImageRespVO;
+import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.dashboard.common.pojo.CommonResult.success;
+
+@Api(tags = "验证码 API")
+@RestController
+@RequestMapping("/captcha")
+public class SysCaptchaController {
+
+ @Resource
+ private SysCaptchaService captchaService;
+
+ @ApiOperation("生成图片验证码")
+ @GetMapping("/get-image")
+ private CommonResult getCaptchaImage() {
+ return success(captchaService.getCaptchaImage());
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/vo/SysCaptchaImageRespVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/vo/SysCaptchaImageRespVO.java
new file mode 100644
index 000000000..3af4e410c
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/common/vo/SysCaptchaImageRespVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.dashboard.modules.system.controller.common.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@ApiModel("验证码图片 Response VO")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysCaptchaImageRespVO {
+
+ @ApiModelProperty(value = "uuid", required = true, example = "1b3b7d00-83a8-4638-9e37-d67011855968", notes = "通过该 uuid 作为该验证码的标识")
+ private String uuid;
+
+ @ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码")
+ private String img;
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/common/SysCaptchaConvert.java b/src/main/java/cn/iocoder/dashboard/modules/system/convert/common/SysCaptchaConvert.java
new file mode 100644
index 000000000..992c9c9f1
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/common/SysCaptchaConvert.java
@@ -0,0 +1,18 @@
+package cn.iocoder.dashboard.modules.system.convert.common;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.ICaptcha;
+import cn.iocoder.dashboard.modules.system.controller.common.vo.SysCaptchaImageRespVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SysCaptchaConvert {
+
+ SysCaptchaConvert INSTANCE = Mappers.getMapper(SysCaptchaConvert.class);
+
+ default SysCaptchaImageRespVO convert(String uuid, AbstractCaptcha captcha) {
+ return SysCaptchaImageRespVO.builder().uuid(uuid).img(captcha.getImageBase64()).build();
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/package-info.java b/src/main/java/cn/iocoder/dashboard/modules/system/convert/package-info.java
new file mode 100644
index 000000000..89deac94a
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.dashboard.modules.system.convert;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/src/main/java/cn/iocoder/dashboard/modules/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
new file mode 100644
index 000000000..af9fdefc5
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
@@ -0,0 +1 @@
+
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java
new file mode 100644
index 000000000..b21019def
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyConstants.java
@@ -0,0 +1,32 @@
+package cn.iocoder.dashboard.modules.system.dal.redis;
+
+import cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine;
+import cn.iocoder.dashboard.framework.security.core.LoginUser;
+
+import java.time.Duration;
+
+import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
+import static cn.iocoder.dashboard.framework.redis.core.RedisKeyDefine.TIMEOUT_DYNAMIC;
+
+/**
+ * Redis Key 枚举类
+ *
+ * @author 芋道源码
+ */
+public interface RedisKeyConstants {
+
+ /**
+ * {@link LoginUser} 的缓存
+ *
+ * key 的 format 的参数是 sessionId
+ */
+ RedisKeyDefine LOGIN_USER = new RedisKeyDefine("login_user:%s", STRING, LoginUser.class, Duration.ofMinutes(30));
+
+ /**
+ * 验证码的缓存
+ *
+ * key 的 format 的参数是 uuid
+ */
+ RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("captcha_code:%s", STRING, String.class, TIMEOUT_DYNAMIC);
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyContants.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyContants.java
deleted file mode 100644
index cd4a5f17c..000000000
--- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/RedisKeyContants.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package cn.iocoder.dashboard.modules.system.dal.redis;
-
-public class RedisKeyContants {
-}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/auth/SysLoginUserRedisDAO.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/auth/SysLoginUserRedisDAO.java
new file mode 100644
index 000000000..c76807178
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/auth/SysLoginUserRedisDAO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.dashboard.modules.system.dal.redis.dao.auth;
+
+import cn.iocoder.dashboard.framework.security.core.LoginUser;
+import com.alibaba.fastjson.JSON;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.dashboard.modules.system.dal.redis.RedisKeyConstants.LOGIN_USER;
+
+/**
+ * {@link LoginUser} 的 RedisDAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class SysLoginUserRedisDAO {
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ public LoginUser get(String sessionId) {
+ String redisKey = formatKey(sessionId);
+ return JSON.parseObject(stringRedisTemplate.opsForValue().get(redisKey), LoginUser.class);
+ }
+
+ public void set(String sessionId, LoginUser loginUser) {
+ String redisKey = formatKey(sessionId);
+ stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(loginUser), LOGIN_USER.getTimeout());
+ }
+
+ public void delete(String accessToken) {
+ String redisKey = formatKey(accessToken);
+ stringRedisTemplate.delete(redisKey);
+ }
+
+ private static String formatKey(String sessionId) {
+ return String.format(LOGIN_USER.getKeyTemplate(), sessionId);
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/common/SysCaptchaRedisDAO.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/common/SysCaptchaRedisDAO.java
new file mode 100644
index 000000000..8e9d32446
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/common/SysCaptchaRedisDAO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.dashboard.modules.system.dal.redis.dao.common;
+
+import cn.iocoder.dashboard.framework.security.core.LoginUser;
+import com.alibaba.fastjson.JSON;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+
+import java.time.Duration;
+
+import static cn.iocoder.dashboard.modules.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE;
+
+/**
+ * 验证码的 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class SysCaptchaRedisDAO {
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ public String get(String uuid) {
+ String redisKey = formatKey(uuid);
+ return stringRedisTemplate.opsForValue().get(redisKey);
+ }
+
+ public void set(String uuid, String code, Duration timeout) {
+ String redisKey = formatKey(uuid);
+ stringRedisTemplate.opsForValue().set(redisKey, code, timeout);
+ }
+
+ public void delete(String uuid) {
+ String redisKey = formatKey(uuid);
+ stringRedisTemplate.delete(redisKey);
+ }
+
+ private static String formatKey(String uuid) {
+ return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid);
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/package-info.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/package-info.java
new file mode 100644
index 000000000..a8b4123a1
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/dao/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 提供 Redis 访问的 DAO
+ */
+package cn.iocoder.dashboard.modules.system.dal.redis.dao;
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/common/SysCaptchaService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/common/SysCaptchaService.java
new file mode 100644
index 000000000..e7c26c2ca
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/common/SysCaptchaService.java
@@ -0,0 +1,12 @@
+package cn.iocoder.dashboard.modules.system.service.common;
+
+import cn.iocoder.dashboard.modules.system.controller.common.vo.SysCaptchaImageRespVO;
+
+/**
+ * 验证码 Service 接口
+ */
+public interface SysCaptchaService {
+
+ SysCaptchaImageRespVO getCaptchaImage();
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/common/impl/SysCaptchaServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/common/impl/SysCaptchaServiceImpl.java
new file mode 100644
index 000000000..856f50b5c
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/common/impl/SysCaptchaServiceImpl.java
@@ -0,0 +1,38 @@
+package cn.iocoder.dashboard.modules.system.service.common.impl;
+
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.CircleCaptcha;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.dashboard.framework.captcha.config.CaptchaProperties;
+import cn.iocoder.dashboard.modules.system.controller.common.vo.SysCaptchaImageRespVO;
+import cn.iocoder.dashboard.modules.system.convert.common.SysCaptchaConvert;
+import cn.iocoder.dashboard.modules.system.dal.redis.dao.common.SysCaptchaRedisDAO;
+import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 验证码 Service 实现类
+ */
+@Service
+public class SysCaptchaServiceImpl implements SysCaptchaService {
+
+ @Resource
+ private CaptchaProperties captchaProperties;
+
+ @Resource
+ private SysCaptchaRedisDAO captchaRedisDAO;
+
+ @Override
+ public SysCaptchaImageRespVO getCaptchaImage() {
+ // 生成验证码
+ CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
+ // 缓存到 Redis 中
+ String uuid = IdUtil.fastSimpleUUID();
+ captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout());
+ // 返回
+ return SysCaptchaConvert.INSTANCE.convert(uuid, captcha);
+ }
+
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index d1cb9258f..34d01a504 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -1,4 +1,6 @@
spring:
+ application:
+ name: dashboard
# 数据源配置项 TODO 多数据源;TODO 监控配置
datasource:
url: jdbc:mysql://127.0.1:3306/ruoyi-vue-pro?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
@@ -21,9 +23,14 @@ yudao:
security:
token-header: Authorization
token-secret: abcdefghijklmnopqrstuvwxyz
- token-expires: 30m
+ token-timeout: 1d
+ session-timeout: 30m
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: 1.0.0
base-package: cn.iocoder.dashboard.modules
+ captcha:
+ timeout: 5m
+ width: 160
+ height: 60