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