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