Commit ec378d75de5660d873514fa16cfabb3671b9eeb4
1 parent
e03a1a8b
v3.7.0 参数管理支持配置验证码开关
Showing
9 changed files
with
81 additions
and
20 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
| 1 | package cn.iocoder.yudao.framework.common.util.validation; | 1 | package cn.iocoder.yudao.framework.common.util.validation; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | import org.springframework.util.StringUtils; | 5 | import org.springframework.util.StringUtils; |
| 5 | 6 | ||
| 7 | +import javax.validation.ConstraintViolation; | ||
| 8 | +import javax.validation.ConstraintViolationException; | ||
| 9 | +import javax.validation.Validator; | ||
| 10 | +import java.util.Set; | ||
| 6 | import java.util.regex.Pattern; | 11 | import java.util.regex.Pattern; |
| 7 | 12 | ||
| 8 | /** | 13 | /** |
| @@ -34,4 +39,11 @@ public class ValidationUtils { | @@ -34,4 +39,11 @@ public class ValidationUtils { | ||
| 34 | && PATTERN_XML_NCNAME.matcher(str).matches(); | 39 | && PATTERN_XML_NCNAME.matcher(str).matches(); |
| 35 | } | 40 | } |
| 36 | 41 | ||
| 42 | + public static void validate(Validator validator, Object reqVO, Class<?>... groups) { | ||
| 43 | + Set<ConstraintViolation<Object>> constraintViolations = validator.validate(reqVO, groups); | ||
| 44 | + if (CollUtil.isNotEmpty(constraintViolations)) { | ||
| 45 | + throw new ConstraintViolationException(constraintViolations); | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 37 | } | 49 | } |
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
| @@ -27,6 +27,7 @@ public interface PayClientConfig { | @@ -27,6 +27,7 @@ public interface PayClientConfig { | ||
| 27 | */ | 27 | */ |
| 28 | Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator); | 28 | Set<ConstraintViolation<PayClientConfig>> verifyParam(Validator validator); |
| 29 | 29 | ||
| 30 | + // TODO @aquan:貌似抽象一个 validation group 就好了! | ||
| 30 | /** | 31 | /** |
| 31 | * 参数校验 | 32 | * 参数校验 |
| 32 | * | 33 | * |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http
| @@ -10,6 +10,16 @@ tenant-id: {{adminTenentId}} | @@ -10,6 +10,16 @@ tenant-id: {{adminTenentId}} | ||
| 10 | "code": "1024" | 10 | "code": "1024" |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | +### 请求 /login 接口 => 成功(无验证码) | ||
| 14 | +POST {{baseUrl}}/system/login | ||
| 15 | +Content-Type: application/json | ||
| 16 | +tenant-id: {{adminTenentId}} | ||
| 17 | + | ||
| 18 | +{ | ||
| 19 | + "username": "admin", | ||
| 20 | + "password": "admin123" | ||
| 21 | +} | ||
| 22 | + | ||
| 13 | ### 请求 /get-permission-info 接口 => 成功 | 23 | ### 请求 /get-permission-info 接口 => 成功 |
| 14 | GET {{baseUrl}}/system/get-permission-info | 24 | GET {{baseUrl}}/system/get-permission-info |
| 15 | Authorization: Bearer {{token}} | 25 | Authorization: Bearer {{token}} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthLoginReqVO.java
| @@ -29,12 +29,17 @@ public class AuthLoginReqVO { | @@ -29,12 +29,17 @@ public class AuthLoginReqVO { | ||
| 29 | @Length(min = 4, max = 16, message = "密码长度为 4-16 位") | 29 | @Length(min = 4, max = 16, message = "密码长度为 4-16 位") |
| 30 | private String password; | 30 | private String password; |
| 31 | 31 | ||
| 32 | - @ApiModelProperty(value = "验证码", required = true, example = "1024") | ||
| 33 | - @NotEmpty(message = "验证码不能为空") | 32 | + @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递") |
| 33 | + @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) | ||
| 34 | private String code; | 34 | private String code; |
| 35 | 35 | ||
| 36 | - @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62") | ||
| 37 | - @NotEmpty(message = "唯一标识不能为空") | 36 | + @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递") |
| 37 | + @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) | ||
| 38 | private String uuid; | 38 | private String uuid; |
| 39 | 39 | ||
| 40 | + /** | ||
| 41 | + * 开启验证码的 Group | ||
| 42 | + */ | ||
| 43 | + public interface CodeEnableGroup {} | ||
| 44 | + | ||
| 40 | } | 45 | } |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java
| @@ -14,11 +14,14 @@ import lombok.NoArgsConstructor; | @@ -14,11 +14,14 @@ import lombok.NoArgsConstructor; | ||
| 14 | @AllArgsConstructor | 14 | @AllArgsConstructor |
| 15 | public class CaptchaImageRespVO { | 15 | public class CaptchaImageRespVO { |
| 16 | 16 | ||
| 17 | - @ApiModelProperty(value = "uuid", required = true, example = "1b3b7d00-83a8-4638-9e37-d67011855968", | ||
| 18 | - notes = "通过该 uuid 作为该验证码的标识") | 17 | + @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false,则关闭验证码功能") |
| 18 | + private Boolean enable; | ||
| 19 | + | ||
| 20 | + @ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968", | ||
| 21 | + notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识") | ||
| 19 | private String uuid; | 22 | private String uuid; |
| 20 | 23 | ||
| 21 | - @ApiModelProperty(value = "图片", required = true, notes = "验证码的图片内容,使用 Base64 编码") | 24 | + @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码") |
| 22 | private String img; | 25 | private String img; |
| 23 | 26 | ||
| 24 | } | 27 | } |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
| @@ -4,8 +4,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | @@ -4,8 +4,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 5 | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; | 5 | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; |
| 6 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | 6 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| 7 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | ||
| 7 | import cn.iocoder.yudao.framework.security.core.LoginUser; | 8 | import cn.iocoder.yudao.framework.security.core.LoginUser; |
| 8 | import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; | 9 | import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; |
| 10 | +import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; | ||
| 9 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; | 11 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; |
| 10 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; | 12 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; |
| 11 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; | 13 | import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; |
| @@ -16,7 +18,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | @@ -16,7 +18,6 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | ||
| 16 | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; | 18 | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; |
| 17 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; | 19 | import cn.iocoder.yudao.module.system.service.common.CaptchaService; |
| 18 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; | 20 | import cn.iocoder.yudao.module.system.service.logger.LoginLogService; |
| 19 | -import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; | ||
| 20 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; | 21 | import cn.iocoder.yudao.module.system.service.permission.PermissionService; |
| 21 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; | 22 | import cn.iocoder.yudao.module.system.service.social.SocialUserService; |
| 22 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; | 23 | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| @@ -35,6 +36,7 @@ import org.springframework.stereotype.Service; | @@ -35,6 +36,7 @@ import org.springframework.stereotype.Service; | ||
| 35 | import org.springframework.util.Assert; | 36 | import org.springframework.util.Assert; |
| 36 | 37 | ||
| 37 | import javax.annotation.Resource; | 38 | import javax.annotation.Resource; |
| 39 | +import javax.validation.Validator; | ||
| 38 | import java.util.Objects; | 40 | import java.util.Objects; |
| 39 | import java.util.Set; | 41 | import java.util.Set; |
| 40 | 42 | ||
| @@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -69,6 +71,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 69 | @Resource | 71 | @Resource |
| 70 | private SocialUserService socialUserService; | 72 | private SocialUserService socialUserService; |
| 71 | 73 | ||
| 74 | + @Resource | ||
| 75 | + private Validator validator; | ||
| 76 | + | ||
| 72 | @Override | 77 | @Override |
| 73 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | 78 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
| 74 | // 获取 username 对应的 AdminUserDO | 79 | // 获取 username 对应的 AdminUserDO |
| @@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -96,7 +101,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 96 | @Override | 101 | @Override |
| 97 | public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { | 102 | public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { |
| 98 | // 判断验证码是否正确 | 103 | // 判断验证码是否正确 |
| 99 | - this.verifyCaptcha(reqVO.getUsername(), reqVO.getUuid(), reqVO.getCode()); | 104 | + this.verifyCaptcha(reqVO); |
| 100 | 105 | ||
| 101 | // 使用账号密码,进行登录 | 106 | // 使用账号密码,进行登录 |
| 102 | LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); | 107 | LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); |
| @@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService { | @@ -105,27 +110,29 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||
| 105 | return userSessionService.createUserSession(loginUser, userIp, userAgent); | 110 | return userSessionService.createUserSession(loginUser, userIp, userAgent); |
| 106 | } | 111 | } |
| 107 | 112 | ||
| 108 | - private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { | 113 | + private void verifyCaptcha(AuthLoginReqVO reqVO) { |
| 109 | // 如果验证码关闭,则不进行校验 | 114 | // 如果验证码关闭,则不进行校验 |
| 110 | if (!captchaService.isCaptchaEnable()) { | 115 | if (!captchaService.isCaptchaEnable()) { |
| 111 | return; | 116 | return; |
| 112 | } | 117 | } |
| 118 | + // 校验验证码 | ||
| 119 | + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); | ||
| 113 | // 验证码不存在 | 120 | // 验证码不存在 |
| 114 | final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; | 121 | final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; |
| 115 | - String code = captchaService.getCaptchaCode(captchaUUID); | 122 | + String code = captchaService.getCaptchaCode(reqVO.getUuid()); |
| 116 | if (code == null) { | 123 | if (code == null) { |
| 117 | // 创建登录失败日志(验证码不存在) | 124 | // 创建登录失败日志(验证码不存在) |
| 118 | - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); | 125 | + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); |
| 119 | throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); | 126 | throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); |
| 120 | } | 127 | } |
| 121 | // 验证码不正确 | 128 | // 验证码不正确 |
| 122 | - if (!code.equals(captchaCode)) { | 129 | + if (!code.equals(reqVO.getCode())) { |
| 123 | // 创建登录失败日志(验证码不正确) | 130 | // 创建登录失败日志(验证码不正确) |
| 124 | - this.createLoginLog(username, logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); | 131 | + this.createLoginLog(reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); |
| 125 | throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); | 132 | throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); |
| 126 | } | 133 | } |
| 127 | // 正确,所以要删除下验证码 | 134 | // 正确,所以要删除下验证码 |
| 128 | - captchaService.deleteCaptchaCode(captchaUUID); | 135 | + captchaService.deleteCaptchaCode(reqVO.getUuid()); |
| 129 | } | 136 | } |
| 130 | 137 | ||
| 131 | private LoginUser login0(String username, String password) { | 138 | private LoginUser login0(String username, String password) { |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java
| @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert; | @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert; | ||
| 7 | import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; | 7 | import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; |
| 8 | import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; | 8 | import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; |
| 9 | import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; | 9 | import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; |
| 10 | +import org.springframework.beans.factory.annotation.Value; | ||
| 10 | import org.springframework.stereotype.Service; | 11 | import org.springframework.stereotype.Service; |
| 11 | 12 | ||
| 12 | import javax.annotation.Resource; | 13 | import javax.annotation.Resource; |
| @@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService { | @@ -20,23 +21,35 @@ public class CaptchaServiceImpl implements CaptchaService { | ||
| 20 | @Resource | 21 | @Resource |
| 21 | private CaptchaProperties captchaProperties; | 22 | private CaptchaProperties captchaProperties; |
| 22 | 23 | ||
| 24 | + /** | ||
| 25 | + * 验证码是否开关 | ||
| 26 | + * | ||
| 27 | + * 虽然 {@link CaptchaProperties#getEnable()} 有该属性,但是 Apollo 在 Spring Boot 下无法刷新 @ConfigurationProperties 注解, | ||
| 28 | + * 所以暂时只能这么处理~ | ||
| 29 | + */ | ||
| 30 | + @Value("${yudao.captcha.enable}") | ||
| 31 | + private Boolean enable; | ||
| 32 | + | ||
| 23 | @Resource | 33 | @Resource |
| 24 | private CaptchaRedisDAO captchaRedisDAO; | 34 | private CaptchaRedisDAO captchaRedisDAO; |
| 25 | 35 | ||
| 26 | @Override | 36 | @Override |
| 27 | public CaptchaImageRespVO getCaptchaImage() { | 37 | public CaptchaImageRespVO getCaptchaImage() { |
| 38 | + if (!Boolean.TRUE.equals(enable)) { | ||
| 39 | + return CaptchaImageRespVO.builder().enable(enable).build(); | ||
| 40 | + } | ||
| 28 | // 生成验证码 | 41 | // 生成验证码 |
| 29 | CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); | 42 | CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); |
| 30 | // 缓存到 Redis 中 | 43 | // 缓存到 Redis 中 |
| 31 | String uuid = IdUtil.fastSimpleUUID(); | 44 | String uuid = IdUtil.fastSimpleUUID(); |
| 32 | captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); | 45 | captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); |
| 33 | // 返回 | 46 | // 返回 |
| 34 | - return CaptchaConvert.INSTANCE.convert(uuid, captcha); | 47 | + return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable); |
| 35 | } | 48 | } |
| 36 | 49 | ||
| 37 | @Override | 50 | @Override |
| 38 | public Boolean isCaptchaEnable() { | 51 | public Boolean isCaptchaEnable() { |
| 39 | - return captchaProperties.getEnable(); | 52 | + return enable; |
| 40 | } | 53 | } |
| 41 | 54 | ||
| 42 | @Override | 55 | @Override |
yudao-ui-admin/src/views/login.vue
| @@ -17,7 +17,7 @@ | @@ -17,7 +17,7 @@ | ||
| 17 | <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> | 17 | <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> |
| 18 | </el-input> | 18 | </el-input> |
| 19 | </el-form-item> | 19 | </el-form-item> |
| 20 | - <el-form-item prop="code"> | 20 | + <el-form-item prop="code" v-if="captchaEnable"> |
| 21 | <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin"> | 21 | <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin"> |
| 22 | <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> | 22 | <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> |
| 23 | </el-input> | 23 | </el-input> |
| @@ -61,6 +61,7 @@ export default { | @@ -61,6 +61,7 @@ export default { | ||
| 61 | data() { | 61 | data() { |
| 62 | return { | 62 | return { |
| 63 | codeUrl: "", | 63 | codeUrl: "", |
| 64 | + captchaEnable: true, | ||
| 64 | loginForm: { | 65 | loginForm: { |
| 65 | username: "admin", | 66 | username: "admin", |
| 66 | password: "admin123", | 67 | password: "admin123", |
| @@ -119,10 +120,18 @@ export default { | @@ -119,10 +120,18 @@ export default { | ||
| 119 | }, | 120 | }, |
| 120 | methods: { | 121 | methods: { |
| 121 | getCode() { | 122 | getCode() { |
| 123 | + // 只有开启的状态,才加载验证码。默认开启 | ||
| 124 | + if (!this.captchaEnable) { | ||
| 125 | + return; | ||
| 126 | + } | ||
| 127 | + // 请求远程,获得验证码 | ||
| 122 | getCodeImg().then(res => { | 128 | getCodeImg().then(res => { |
| 123 | res = res.data; | 129 | res = res.data; |
| 124 | - this.codeUrl = "data:image/gif;base64," + res.img; | ||
| 125 | - this.loginForm.uuid = res.uuid; | 130 | + this.captchaEnable = res.enable; |
| 131 | + if (this.captchaEnable) { | ||
| 132 | + this.codeUrl = "data:image/gif;base64," + res.img; | ||
| 133 | + this.loginForm.uuid = res.uuid; | ||
| 134 | + } | ||
| 126 | }); | 135 | }); |
| 127 | }, | 136 | }, |
| 128 | getCookie() { | 137 | getCookie() { |
更新日志.md
| @@ -37,6 +37,7 @@ | @@ -37,6 +37,7 @@ | ||
| 37 | * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) | 37 | * 【新增】前端的网页标题支持根据选择的菜单,动态展示标题 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/7bf9a85263e0c44b2bc88485b83557c129583f5c) |
| 38 | * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) | 38 | * 【新增】字典标签样式回显,例如说开启的状态展示为 primary 蓝色,禁用的状态为 info 灰色 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/986d1328e0a0d37e2de2fb9d937faeed9d9bee7b) |
| 39 | * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) | 39 | * 【新增】前端的 iframe 组件,方便内嵌网页 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/4a8129bffa9e3928c56333e29f5874f55a079764) |
| 40 | +* 【新增】在基础设施-配置管理菜单,可通过修改 `yudao.captcha.enable` 配置项,动态修改登录是否需要验证码 [commit]() | ||
| 40 | 41 | ||
| 41 | ### 🐞 Bug Fixes | 42 | ### 🐞 Bug Fixes |
| 42 | 43 |