diff --git a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java index bf4fc986..a734a676 100644 --- a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java +++ b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java @@ -9,40 +9,32 @@ package com.youlai.boot.common.constant; public interface RedisConstants { /** - * 系统配置Redis-key + * 系统配置 Redis 键 */ String SYSTEM_CONFIG_KEY = "system:config"; /** - * IP限流Redis-key + * IP 限流 Redis 键 */ - String IP_RATE_LIMITER_KEY = "ip:rate:limiter:"; + String IP_RATE_LIMITER_KEY = "rate:limiter:ip:"; /** - * 防重复提交Redis-key + * 防重复提交 Redis 键前缀 */ - String RESUBMIT_LOCK_PREFIX = "resubmit:lock:"; + String RESUBMIT_LOCK_PREFIX = "lock:resubmit:"; /** - * 单个IP请求的最大每秒查询数(QPS)阈值Key + * 登录手机验证码 Redis 键前缀 */ - String IP_QPS_THRESHOLD_LIMIT_KEY = "IP_QPS_THRESHOLD_LIMIT"; + String SMS_LOGIN_CODE_PREFIX= "code:sms:login:"; /** - * 手机验证码缓存前缀 + * 绑定或更换手机号验证码 Redis 键前缀 */ - String SMS_LOGIN_VERIFY_CODE_PREFIX = "sms_login:mobile:"; + String SMS_CHANGE_CODE_PREFIX = "code:sms:change:"; /** - * 重置密码验证码缓存前缀 + * 绑定或更换邮箱验证码 Redis 键前缀 */ - - String SMS_RESET_PASSWORD_VERIFY_CODE_PREFIX = "sms_reset_password:mobile:"; - - - /** - * 邮箱验证码缓存前缀 - */ - String EMAIL_VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE:EMAIL:"; - + String EMAIL_CHANGE_CODE_PREFIX = "code:email:change:"; } diff --git a/src/main/java/com/youlai/boot/common/constant/SystemConstants.java b/src/main/java/com/youlai/boot/common/constant/SystemConstants.java index 321ea651..43489745 100644 --- a/src/main/java/com/youlai/boot/common/constant/SystemConstants.java +++ b/src/main/java/com/youlai/boot/common/constant/SystemConstants.java @@ -3,7 +3,7 @@ package com.youlai.boot.common.constant; /** * 系统常量 * - * @author haoxr + * @author Ray.Hao * @since 1.0.0 */ public interface SystemConstants { @@ -24,5 +24,9 @@ public interface SystemConstants { String ROOT_ROLE_CODE = "ROOT"; + /** + * 系统配置 IP的QPS限流的KEY + */ + String SYSTEM_CONFIG_IP_QPS_LIMIT_KEY = "IP_QPS_THRESHOLD_LIMIT"; } diff --git a/src/main/java/com/youlai/boot/common/result/Result.java b/src/main/java/com/youlai/boot/common/result/Result.java index 855361da..55fee091 100644 --- a/src/main/java/com/youlai/boot/common/result/Result.java +++ b/src/main/java/com/youlai/boot/common/result/Result.java @@ -1,5 +1,6 @@ package com.youlai.boot.common.result; +import cn.hutool.core.util.StrUtil; import lombok.Data; import java.io.Serializable; @@ -52,7 +53,7 @@ public class Result implements Serializable { } public static Result failed(IResultCode resultCode, String msg) { - return result(resultCode.getCode(), msg, null); + return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null); } private static Result result(IResultCode resultCode, T data) { diff --git a/src/main/java/com/youlai/boot/common/util/ResponseUtils.java b/src/main/java/com/youlai/boot/common/util/ResponseUtils.java index 4cbede1f..d90804dc 100644 --- a/src/main/java/com/youlai/boot/common/util/ResponseUtils.java +++ b/src/main/java/com/youlai/boot/common/util/ResponseUtils.java @@ -21,19 +21,15 @@ import java.nio.charset.StandardCharsets; @Slf4j public class ResponseUtils { + /** * 异常消息返回(适用过滤器中处理异常响应) * - * @param response HttpServletResponse + * @param response HttpServletResponse * @param resultCode 响应结果码 */ public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode) { - // 根据不同的结果码设置HTTP状态 - int status = switch (resultCode) { - case ACCESS_UNAUTHORIZED, ACCESS_TOKEN_INVALID , REFRESH_TOKEN_INVALID - -> HttpStatus.UNAUTHORIZED.value(); - default -> HttpStatus.BAD_REQUEST.value(); - }; + int status = getHttpStatus(resultCode); response.setStatus(status); response.setContentType(MediaType.APPLICATION_JSON_VALUE); @@ -48,4 +44,40 @@ public class ResponseUtils { } } + /** + * 异常消息返回(适用过滤器中处理异常响应) + * + * @param response HttpServletResponse + * @param resultCode 响应结果码 + */ + public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode, String message) { + int status = getHttpStatus(resultCode); + + response.setStatus(status); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + + try (PrintWriter writer = response.getWriter()) { + String jsonResponse = JSONUtil.toJsonStr(Result.failed(resultCode, message)); + writer.print(jsonResponse); + writer.flush(); // 确保将响应内容写入到输出流 + } catch (IOException e) { + log.error("响应异常处理失败", e); + } + } + + + /** + * 根据结果码获取HTTP状态码 + * + * @param resultCode 结果码 + * @return HTTP状态码 + */ + private static int getHttpStatus(ResultCode resultCode) { + return switch (resultCode) { + case ACCESS_UNAUTHORIZED, ACCESS_TOKEN_INVALID, REFRESH_TOKEN_INVALID -> HttpStatus.UNAUTHORIZED.value(); + default -> HttpStatus.BAD_REQUEST.value(); + }; + } + } diff --git a/src/main/java/com/youlai/boot/config/SecurityConfig.java b/src/main/java/com/youlai/boot/config/SecurityConfig.java index 63d94cd7..587c1295 100644 --- a/src/main/java/com/youlai/boot/config/SecurityConfig.java +++ b/src/main/java/com/youlai/boot/config/SecurityConfig.java @@ -7,7 +7,8 @@ import com.youlai.boot.config.property.SecurityProperties; import com.youlai.boot.core.filter.RateLimiterFilter; import com.youlai.boot.core.security.exception.MyAccessDeniedHandler; import com.youlai.boot.core.security.exception.MyAuthenticationEntryPoint; -import com.youlai.boot.core.security.extension.WechatAuthenticationProvider; +import com.youlai.boot.core.security.extension.sms.SmsAuthenticationProvider; +import com.youlai.boot.core.security.extension.wechat.WechatAuthenticationProvider; import com.youlai.boot.core.security.filter.CaptchaValidationFilter; import com.youlai.boot.core.security.filter.JwtAuthenticationFilter; import com.youlai.boot.core.security.service.SysUserDetailsService; @@ -131,6 +132,10 @@ public class SecurityConfig { return new WechatAuthenticationProvider(userService, wxMaService); } + public SmsAuthenticationProvider smsAuthenticationProvider() { + return new SmsAuthenticationProvider(userService, redisTemplate); + } + /** * 手动注入 AuthenticationManager,支持多种认证方式 * - DaoAuthenticationProvider:用户名密码认证 @@ -138,6 +143,10 @@ public class SecurityConfig { */ @Bean public AuthenticationManager authenticationManager() { - return new ProviderManager(daoAuthenticationProvider(), weChatAuthenticationProvider()); + return new ProviderManager( + daoAuthenticationProvider(), + weChatAuthenticationProvider(), + smsAuthenticationProvider() + ); } } diff --git a/src/main/java/com/youlai/boot/core/filter/RateLimiterFilter.java b/src/main/java/com/youlai/boot/core/filter/RateLimiterFilter.java index 7b4c570d..4f51be44 100644 --- a/src/main/java/com/youlai/boot/core/filter/RateLimiterFilter.java +++ b/src/main/java/com/youlai/boot/core/filter/RateLimiterFilter.java @@ -2,6 +2,7 @@ package com.youlai.boot.core.filter; import cn.hutool.core.convert.Convert; import com.youlai.boot.common.constant.RedisConstants; +import com.youlai.boot.common.constant.SystemConstants; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.common.util.IPUtils; import com.youlai.boot.common.util.ResponseUtils; @@ -49,13 +50,13 @@ public class RateLimiterFilter extends OncePerRequestFilter { if (count == null || count == 1) { redisTemplate.expire(key,1, TimeUnit.SECONDS); } - Object systemConfig = configService.getSystemConfig(RedisConstants.IP_QPS_THRESHOLD_LIMIT_KEY); + Object systemConfig = configService.getSystemConfig(SystemConstants.SYSTEM_CONFIG_IP_QPS_LIMIT_KEY); long limit = 10; if(systemConfig != null){ limit = Convert.toLong(systemConfig,50L); }else{ log.warn("[RedisRateLimiterFilter.rateLimit]系统配置中未配置IP请求限制QPS阈值配置,使用默认值:{},请检查配置项:{}", - limit,RedisConstants.IP_QPS_THRESHOLD_LIMIT_KEY); + limit,SystemConstants.SYSTEM_CONFIG_IP_QPS_LIMIT_KEY); } return count != null && count > limit; } diff --git a/src/main/java/com/youlai/boot/core/security/exception/MyAuthenticationEntryPoint.java b/src/main/java/com/youlai/boot/core/security/exception/MyAuthenticationEntryPoint.java index 7fc1fb42..5bd0a14b 100644 --- a/src/main/java/com/youlai/boot/core/security/exception/MyAuthenticationEntryPoint.java +++ b/src/main/java/com/youlai/boot/core/security/exception/MyAuthenticationEntryPoint.java @@ -30,10 +30,10 @@ public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { } else { if (authException instanceof BadCredentialsException) { // 用户名或密码错误 - ResponseUtils.writeErrMsg(response, ResultCode.USER_PASSWORD_ERROR); + ResponseUtils.writeErrMsg(response, ResultCode.USER_PASSWORD_ERROR, authException.getMessage()); } else { // 未认证或者token过期 - ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID); + ResponseUtils.writeErrMsg(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage()); } } } diff --git a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java index bf728e77..db7fa180 100644 --- a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java @@ -3,17 +3,17 @@ package com.youlai.boot.core.security.extension.sms; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.youlai.boot.common.constant.RedisConstants; -import com.youlai.boot.core.security.extension.WechatAuthenticationToken; import com.youlai.boot.core.security.model.SysUserDetails; import com.youlai.boot.system.model.dto.UserAuthInfo; import com.youlai.boot.system.service.UserService; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** @@ -27,10 +27,10 @@ public class SmsAuthenticationProvider implements AuthenticationProvider { private final UserService userService; - private final StringRedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; - public SmsAuthenticationProvider(UserService userService, StringRedisTemplate redisTemplate) { + public SmsAuthenticationProvider(UserService userService, RedisTemplate redisTemplate) { this.userService = userService; this.redisTemplate = redisTemplate; } @@ -46,22 +46,28 @@ public class SmsAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String mobile = (String) authentication.getPrincipal(); - String verifyCode = (String) authentication.getCredentials(); + String inputVerifyCode = (String) authentication.getCredentials(); // 根据手机号获取用户信息 UserAuthInfo userAuthInfo = userService.getUserAuthInfoByMobile(mobile); + if (userAuthInfo == null) { + throw new UsernameNotFoundException("用户不存在"); + } + // 检查用户状态是否有效 if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) { throw new DisabledException("用户已被禁用"); } // 校验发送短信验证码的手机号是否与当前登录用户一致 + String cachedVerifyCode = (String) redisTemplate.opsForValue().get(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile); - String cachedVerifyCode= redisTemplate.opsForValue().get(RedisConstants.SMS_LOGIN_VERIFY_CODE_PREFIX + mobile); - - if ( !StrUtil.equals(verifyCode, cachedVerifyCode)) { - throw new CredentialsExpiredException("验证码错误"); + if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) { + throw new BadCredentialsException("验证码错误"); + } else { + // 验证成功后删除验证码 + redisTemplate.delete(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile); } // 构建认证后的用户详情信息 @@ -76,6 +82,6 @@ public class SmsAuthenticationProvider implements AuthenticationProvider { @Override public boolean supports(Class authentication) { - return WechatAuthenticationToken.class.isAssignableFrom(authentication); + return SmsAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java index 64b0b7f2..8f1484c7 100644 --- a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java @@ -15,18 +15,27 @@ import java.util.Collection; public class SmsAuthenticationToken extends AbstractAuthenticationToken { @Serial private static final long serialVersionUID = 621L; + + /** + * 认证信息 (手机号) + */ private final Object principal; - private Object credentials; + + /** + * 凭证信息 (短信验证码) + */ + private final Object credentials; /** * 短信验证码认证 Token (未认证) * * @param principal 微信用户信息 */ - public SmsAuthenticationToken(Object principal) { + public SmsAuthenticationToken(Object principal, Object credentials) { // 没有授权信息时,设置为 null super(null); this.principal = principal; + this.credentials = credentials; // 默认未认证 this.setAuthenticated(false); } @@ -40,6 +49,7 @@ public class SmsAuthenticationToken extends AbstractAuthenticationToken { public SmsAuthenticationToken(Object principal, Collection authorities) { super(authorities); this.principal = principal; + this.credentials = null; // 认证通过 super.setAuthenticated(true); } @@ -58,7 +68,7 @@ public class SmsAuthenticationToken extends AbstractAuthenticationToken { @Override public Object getCredentials() { - return this.credentials ; + return this.credentials; } @Override diff --git a/src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationProvider.java b/src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationProvider.java similarity index 98% rename from src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationProvider.java rename to src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationProvider.java index 6a1831fe..01f410ff 100644 --- a/src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationProvider.java +++ b/src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationProvider.java @@ -1,4 +1,4 @@ -package com.youlai.boot.core.security.extension; +package com.youlai.boot.core.security.extension.wechat; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; diff --git a/src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationToken.java b/src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationToken.java similarity index 96% rename from src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationToken.java rename to src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationToken.java index 56854705..fc8bb2be 100644 --- a/src/main/java/com/youlai/boot/core/security/extension/WechatAuthenticationToken.java +++ b/src/main/java/com/youlai/boot/core/security/extension/wechat/WechatAuthenticationToken.java @@ -1,4 +1,4 @@ -package com.youlai.boot.core.security.extension; +package com.youlai.boot.core.security.extension.wechat; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java index f347624c..6bb06473 100644 --- a/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java +++ b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java @@ -13,7 +13,7 @@ import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.config.property.SecurityProperties; import com.youlai.boot.core.security.model.SysUserDetails; -import com.youlai.boot.core.security.model.AuthToken; +import com.youlai.boot.core.security.model.AuthenticationToken; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -57,14 +57,14 @@ public class JwtTokenManager implements TokenManager { * @return 令牌响应对象 */ @Override - public AuthToken generateToken(Authentication authentication) { + public AuthenticationToken generateToken(Authentication authentication) { int accessTokenTimeToLive = securityProperties.getJwt().getAccessTokenTimeToLive(); int refreshTokenTimeToLive = securityProperties.getJwt().getRefreshTokenTimeToLive(); String accessToken = generateToken(authentication, accessTokenTimeToLive); String refreshToken = generateToken(authentication, refreshTokenTimeToLive); - return AuthToken.builder() + return AuthenticationToken.builder() .accessToken(accessToken) .refreshToken(refreshToken) .tokenType("Bearer") @@ -163,7 +163,7 @@ public class JwtTokenManager implements TokenManager { */ @Override - public AuthToken refreshToken(String refreshToken) { + public AuthenticationToken refreshToken(String refreshToken) { boolean isValid = validateToken(refreshToken); if (!isValid) { @@ -174,7 +174,7 @@ public class JwtTokenManager implements TokenManager { int accessTokenExpiration = securityProperties.getJwt().getRefreshTokenTimeToLive(); String newAccessToken = generateToken(authentication, accessTokenExpiration); - return AuthToken.builder() + return AuthenticationToken.builder() .accessToken(newAccessToken) .refreshToken(refreshToken) .tokenType("Bearer") diff --git a/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java index b154e549..872b0bec 100644 --- a/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java +++ b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java @@ -1,6 +1,6 @@ package com.youlai.boot.core.security.manager; -import com.youlai.boot.core.security.model.AuthToken; +import com.youlai.boot.core.security.model.AuthenticationToken; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; @@ -22,7 +22,7 @@ public class RedisTokenManager implements TokenManager { * @return */ @Override - public AuthToken generateToken(Authentication authentication) { + public AuthenticationToken generateToken(Authentication authentication) { return null; } @@ -55,7 +55,7 @@ public class RedisTokenManager implements TokenManager { * @return */ @Override - public AuthToken refreshToken(String token) { + public AuthenticationToken refreshToken(String token) { return null; } } diff --git a/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java index 1997053d..63466bd7 100644 --- a/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java +++ b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java @@ -1,7 +1,7 @@ package com.youlai.boot.core.security.manager; -import com.youlai.boot.core.security.model.AuthToken; +import com.youlai.boot.core.security.model.AuthenticationToken; import org.springframework.security.core.Authentication; /** @@ -18,7 +18,7 @@ public interface TokenManager { * @param authentication 用户认证信息 * @return 认证 Token 响应 */ - AuthToken generateToken(Authentication authentication); + AuthenticationToken generateToken(Authentication authentication); /** * 解析 Token 获取认证信息 @@ -44,7 +44,7 @@ public interface TokenManager { * @param token 刷新令牌 * @return 认证 Token 响应 */ - AuthToken refreshToken(String token); + AuthenticationToken refreshToken(String token); /** * 将 Token 加入黑名单 diff --git a/src/main/java/com/youlai/boot/core/security/model/AuthToken.java b/src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java similarity index 91% rename from src/main/java/com/youlai/boot/core/security/model/AuthToken.java rename to src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java index 7905713b..aaa352dd 100644 --- a/src/main/java/com/youlai/boot/core/security/model/AuthToken.java +++ b/src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java @@ -7,13 +7,13 @@ import lombok.Data; /** * 认证令牌响应对象 * - * @author Ray + * @author Ray.Hao * @since 0.0.1 */ @Schema(description = "认证令牌响应对象") @Data @Builder -public class AuthToken { +public class AuthenticationToken { @Schema(description = "令牌类型", example = "Bearer") private String tokenType; diff --git a/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java b/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java index 5c2f3e34..efed422e 100644 --- a/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java +++ b/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java @@ -2,10 +2,9 @@ package com.youlai.boot.shared.auth.controller; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.Result; -import com.youlai.boot.shared.auth.model.RefreshTokenRequest; import com.youlai.boot.shared.auth.service.AuthService; -import com.youlai.boot.shared.auth.model.CaptchaResponse; -import com.youlai.boot.core.security.model.AuthToken; +import com.youlai.boot.shared.auth.model.CaptchaInfo; +import com.youlai.boot.core.security.model.AuthenticationToken; import com.youlai.boot.common.annotation.Log; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -29,18 +28,25 @@ public class AuthController { private final AuthService authService; - @Operation(summary = "登录") + @Operation(summary = "获取登录验证码") + @GetMapping("/captcha") + public Result getCaptcha() { + CaptchaInfo captcha = authService.getCaptcha(); + return Result.success(captcha); + } + + @Operation(summary = "账号密码登录") @PostMapping("/login") @Log(value = "登录", module = LogModuleEnum.LOGIN) - public Result login( + public Result login( @Parameter(description = "用户名", example = "admin") @RequestParam String username, @Parameter(description = "密码", example = "123456") @RequestParam String password ) { - AuthToken authToken = authService.login(username, password); - return Result.success(authToken); + AuthenticationToken authenticationToken = authService.login(username, password); + return Result.success(authenticationToken); } - @Operation(summary = "注销") + @Operation(summary = "注销登录") @DeleteMapping("/logout") @Log(value = "注销", module = LogModuleEnum.LOGIN) public Result logout() { @@ -48,48 +54,42 @@ public class AuthController { return Result.success(); } - @Operation(summary = "获取验证码") - @GetMapping("/captcha") - public Result getCaptcha() { - CaptchaResponse captcha = authService.getCaptcha(); - return Result.success(captcha); - } - - @Operation(summary = "刷新token") + @Operation(summary = "刷新访问令牌") @PostMapping("/refresh-token") - public Result refreshToken(@RequestBody RefreshTokenRequest request) { - AuthToken authToken = authService.refreshToken(request); - return Result.success(authToken); + public Result refreshToken( + @Parameter(description = "刷新令牌", example = "xxx.xxx.xxx") @RequestParam String refreshToken + ) { + AuthenticationToken authenticationToken = authService.refreshToken(refreshToken); + return Result.success(authenticationToken); } - @Operation(summary = "微信登录") - @PostMapping("/wechat-login") + @Operation(summary = "微信授权登录") + @PostMapping("/login/wechat") @Log(value = "微信登录", module = LogModuleEnum.LOGIN) - public Result wechatLogin( + public Result loginByWechat( @Parameter(description = "微信授权码", example = "code") @RequestParam String code ) { - AuthToken loginResult = authService.wechatLogin(code); + AuthenticationToken loginResult = authService.loginByWechat(code); return Result.success(loginResult); } - - @Operation(summary = "短信验证码登录") - @PostMapping("/sms-login") - @Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN) - public Result smsLogin( - @Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile, - @Parameter(description = "验证码", example = "123456") @RequestParam String code - ) { - AuthToken loginResult = authService.smsLogin(mobile, code); - return Result.success(loginResult); - } - - @Operation(summary = "短信验证码登录发送短信") - @PostMapping("/sms-login/verify-code") + @Operation(summary = "发送登录短信验证码") + @PostMapping("/login/sms/code") public Result sendLoginVerifyCode( - @Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile + @Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile ) { - authService.sendLoginVerifyCode(mobile); + authService.sendSmsLoginCode(mobile); return Result.success(); } + + @Operation(summary = "短信验证码登录") + @PostMapping("/login/sms") + @Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN) + public Result loginBySms( + @Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile, + @Parameter(description = "验证码", example = "123456") @RequestParam String code + ) { + AuthenticationToken loginResult = authService.loginBySms(mobile, code); + return Result.success(loginResult); + } } diff --git a/src/main/java/com/youlai/boot/shared/auth/model/CaptchaResponse.java b/src/main/java/com/youlai/boot/shared/auth/model/CaptchaInfo.java similarity index 65% rename from src/main/java/com/youlai/boot/shared/auth/model/CaptchaResponse.java rename to src/main/java/com/youlai/boot/shared/auth/model/CaptchaInfo.java index e636bb54..b290cc50 100644 --- a/src/main/java/com/youlai/boot/shared/auth/model/CaptchaResponse.java +++ b/src/main/java/com/youlai/boot/shared/auth/model/CaptchaInfo.java @@ -5,17 +5,17 @@ import lombok.Builder; import lombok.Data; /** - * 验证码响应对象 + * 验证码信息 * - * @author Ray Hao + * @author Ray。Hao * @since 2023/03/24 */ -@Schema(description = "验证码响应对象") +@Schema(description = "验证码信息") @Data @Builder -public class CaptchaResponse { +public class CaptchaInfo { - @Schema(description = "验证码ID") + @Schema(description = "验证码缓存 Key") private String captchaKey; @Schema(description = "验证码图片Base64字符串") diff --git a/src/main/java/com/youlai/boot/shared/auth/model/RefreshTokenRequest.java b/src/main/java/com/youlai/boot/shared/auth/model/RefreshTokenRequest.java deleted file mode 100644 index 0ce433f0..00000000 --- a/src/main/java/com/youlai/boot/shared/auth/model/RefreshTokenRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.youlai.boot.shared.auth.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -/** - * 刷新令牌请求参数 - * - * @author haoxr - * @since 2024/11/11 - */ -@Schema(description = "刷新令牌请求参数") -@Data -public class RefreshTokenRequest { - - @Schema(description = "刷新令牌") - @NotBlank(message = "刷新令牌不能为空") - private String refreshToken; - -} diff --git a/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java b/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java index 13d66d9b..1243403f 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java +++ b/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java @@ -1,13 +1,12 @@ package com.youlai.boot.shared.auth.service; -import com.youlai.boot.shared.auth.model.CaptchaResponse; -import com.youlai.boot.core.security.model.AuthToken; -import com.youlai.boot.shared.auth.model.RefreshTokenRequest; +import com.youlai.boot.shared.auth.model.CaptchaInfo; +import com.youlai.boot.core.security.model.AuthenticationToken; /** * 认证服务接口 * - * @author haoxr + * @author Ray.Hao * @since 2.4.0 */ public interface AuthService { @@ -19,7 +18,7 @@ public interface AuthService { * @param password 密码 * @return 登录结果 */ - AuthToken login(String username, String password); + AuthenticationToken login(String username, String password); /** * 登出 @@ -31,15 +30,15 @@ public interface AuthService { * * @return 验证码 */ - CaptchaResponse getCaptcha(); + CaptchaInfo getCaptcha(); /** * 刷新令牌 * - * @param request 刷新令牌请求参数 + * @param refreshToken 刷新令牌 * @return 登录结果 */ - AuthToken refreshToken(RefreshTokenRequest request); + AuthenticationToken refreshToken(String refreshToken); /** * 微信小程序登录 @@ -47,12 +46,21 @@ public interface AuthService { * @param code 微信登录code * @return 登录结果 */ - AuthToken wechatLogin(String code); + AuthenticationToken loginByWechat(String code); /** * 发送短信验证码 * * @param mobile 手机号 */ - void sendLoginVerifyCode(String mobile); + void sendSmsLoginCode(String mobile); + + /** + * 短信验证码登录 + * + * @param mobile 手机号 + * @param code 验证码 + * @return 登录结果 + */ + AuthenticationToken loginBySms(String mobile, String code); } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java index 668aa571..8233248d 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java @@ -5,16 +5,17 @@ import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.SecurityConstants; import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.config.property.CaptchaProperties; -import com.youlai.boot.core.security.extension.WechatAuthenticationToken; +import com.youlai.boot.core.security.extension.sms.SmsAuthenticationToken; +import com.youlai.boot.core.security.extension.wechat.WechatAuthenticationToken; import com.youlai.boot.core.security.util.SecurityUtils; import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum; -import com.youlai.boot.core.security.model.AuthToken; -import com.youlai.boot.shared.auth.model.CaptchaResponse; -import com.youlai.boot.shared.auth.model.RefreshTokenRequest; +import com.youlai.boot.core.security.model.AuthenticationToken; +import com.youlai.boot.shared.auth.model.CaptchaInfo; import com.youlai.boot.shared.auth.service.AuthService; import com.youlai.boot.core.security.manager.TokenManager; import com.youlai.boot.shared.sms.enums.SmsTypeEnum; @@ -29,12 +30,14 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import java.awt.*; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; /** * 认证服务实现类 * - * @author haoxr + * @author Ray.Hao * @since 2.4.0 */ @Service @@ -43,14 +46,16 @@ import java.util.concurrent.TimeUnit; public class AuthServiceImpl implements AuthService { private final AuthenticationManager authenticationManager; - private final RedisTemplate redisTemplate; - private final CodeGenerator codeGenerator; - private final Font captchaFont; - private final CaptchaProperties captchaProperties; private final TokenManager tokenManager; + private final Font captchaFont; + private final CaptchaProperties captchaProperties; + private final CodeGenerator codeGenerator; + private final SmsService smsService; + private final RedisTemplate redisTemplate; + /** * 用户名密码登录 * @@ -59,7 +64,7 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthToken login(String username, String password) { + public AuthenticationToken login(String username, String password) { // 1. 创建用于密码认证的令牌(未认证) UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username.trim(), password); @@ -68,10 +73,10 @@ public class AuthServiceImpl implements AuthService { Authentication authentication = authenticationManager.authenticate(authenticationToken); // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) - AuthToken authTokenResponse = + AuthenticationToken authenticationTokenResponse = tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); - return authTokenResponse; + return authenticationTokenResponse; } /** @@ -81,42 +86,69 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthToken wechatLogin(String code) { + public AuthenticationToken loginByWechat(String code) { // 1. 创建用户微信认证的令牌(未认证) - WechatAuthenticationToken authenticationToken = new WechatAuthenticationToken(code); + WechatAuthenticationToken wechatAuthenticationToken = new WechatAuthenticationToken(code); // 2. 执行认证(认证中) - Authentication authentication = authenticationManager.authenticate(authenticationToken); + Authentication authentication = authenticationManager.authenticate(wechatAuthenticationToken); // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) - AuthToken authTokenResponse = tokenManager.generateToken(authentication); + AuthenticationToken authenticationToken = tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); - return authTokenResponse; + return authenticationToken; } - /** * 发送短信验证码 * * @param mobile 手机号 */ @Override - public void sendLoginVerifyCode(String mobile) { + public void sendSmsLoginCode(String mobile) { // 随机生成4位验证码 - String verifyCode = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码 + String code = "1234"; + // 发送短信验证码 - smsService.sendSms(mobile, SmsTypeEnum.LOGIN, verifyCode); - - - - - + Map templateParams = new HashMap<>(); + templateParams.put("code", code); + try { + smsService.sendSms(mobile, SmsTypeEnum.LOGIN, templateParams); + } catch (Exception e) { + log.error("发送短信验证码失败", e); + } + // 缓存验证码至Redis,用于登录校验 + redisTemplate.opsForValue().set(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile, code, 5, TimeUnit.MINUTES); } /** - * 注销 + * 短信验证码登录 + * + * @param mobile 手机号 + * @param code 验证码 + * @return 访问令牌 + */ + @Override + public AuthenticationToken loginBySms(String mobile, String code) { + // 1. 创建用户微信认证的令牌(未认证) + SmsAuthenticationToken smsAuthenticationToken = new SmsAuthenticationToken(mobile, code); + + // 2. 执行认证(认证中) + Authentication authentication = authenticationManager.authenticate(smsAuthenticationToken); + + // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) + AuthenticationToken authenticationToken = tokenManager.generateToken(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + + return authenticationToken; + } + + /** + * 注销登录 */ @Override public void logout() { @@ -136,7 +168,7 @@ public class AuthServiceImpl implements AuthService { * @return 验证码 */ @Override - public CaptchaResponse getCaptcha() { + public CaptchaInfo getCaptcha() { String captchaType = captchaProperties.getType(); int width = captchaProperties.getWidth(); @@ -168,30 +200,27 @@ public class AuthServiceImpl implements AuthService { redisTemplate.opsForValue().set(SecurityConstants.CAPTCHA_CODE_PREFIX + captchaKey, captchaCode, captchaProperties.getExpireSeconds(), TimeUnit.SECONDS); - return CaptchaResponse.builder() + return CaptchaInfo.builder() .captchaKey(captchaKey) .captchaBase64(imageBase64Data) .build(); } /** - * 刷新令牌 + * 刷新token * - * @param request 刷新令牌请求参数 + * @param refreshToken 刷新令牌 * @return 新的访问令牌 */ @Override - public AuthToken refreshToken(RefreshTokenRequest request) { + public AuthenticationToken refreshToken(String refreshToken) { // 验证刷新令牌 - - String refreshToken = request.getRefreshToken(); - boolean isValidate = tokenManager.validateToken(refreshToken); if (!isValidate) { throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); } - + // 刷新令牌有效,生成新的访问令牌 return tokenManager.refreshToken(refreshToken); } diff --git a/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java b/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java index a2aaa1cb..b852a797 100644 --- a/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java +++ b/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java @@ -5,12 +5,29 @@ import lombok.Getter; /** * 短信类型枚举 + *

+ * value 值对应 application-*.yml 中的 sms.templates.* 配置 + * + * @author Ray.Hao + * @since 2.21.0 */ @Getter public enum SmsTypeEnum implements IBaseEnum { + + /** + * 注册短信验证码 + */ REGISTER("register", "注册短信验证码"), + + /** + * 登录短信验证码 + */ LOGIN("login", "登录短信验证码"), - RESET_PASSWORD("reset-password", "重置密码短信验证码"); + + /** + * 修改手机号短信验证码 + */ + CHANGE_MOBILE("change-mobile", "修改手机号短信验证码"); private final String value; private final String label; diff --git a/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java b/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java index 06d2061b..3deb1157 100644 --- a/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java +++ b/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java @@ -2,6 +2,8 @@ package com.youlai.boot.shared.sms.service; import com.youlai.boot.shared.sms.enums.SmsTypeEnum; +import java.util.Map; + /** * 短信服务接口层 * @@ -13,10 +15,10 @@ public interface SmsService { /** * 发送短信 * - * @param mobile 手机号 13388886666 - * @param smsType 短信模板 SMS_194640010 - * @param templateParam 模板参数 "[{"code":"123456"}]" + * @param mobile 手机号 13388886666 + * @param smsType 短信模板 SMS_194640010,模板内容:您的验证码为:${code},请在5分钟内使用 + * @param templateParams 模板参数 [{"code":"123456"}] ,用于替换短信模板中的变量 * @return boolean 是否发送成功 */ - boolean sendSms(String mobile, SmsTypeEnum smsType, String templateParam); + boolean sendSms(String mobile, SmsTypeEnum smsType, Map templateParams); } diff --git a/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java b/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java index 84915efe..b37ed844 100644 --- a/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java +++ b/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java @@ -1,11 +1,11 @@ package com.youlai.boot.shared.sms.service.impl; +import cn.hutool.json.JSONUtil; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; -import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import com.youlai.boot.config.property.AliyunSmsProperties; @@ -14,11 +14,13 @@ import com.youlai.boot.shared.sms.service.SmsService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.Map; + /** * 阿里云短信业务类 - * + * * @author Ray - * @since 2024/8/17 + * @since 2024/8/17 */ @Service @RequiredArgsConstructor @@ -29,14 +31,13 @@ public class AliyunSmsService implements SmsService { /** * 发送短信验证码 * - * @param mobile 手机号 13388886666 - * @param smsType 短信模板 SMS_194640010 - * @param templateParam 模板参数 [{"code":"123456"}] - * - * @return boolean 是否发送成功 + * @param mobile 手机号 13388886666 + * @param smsType 短信模板 SMS_194640010 + * @param templateParams 模板参数 [{"code":"123456"}] + * @return boolean 是否发送成功 */ @Override - public boolean sendSms(String mobile, SmsTypeEnum smsType, String templateParam) { + public boolean sendSms(String mobile, SmsTypeEnum smsType, Map templateParams) { String templateCode = aliyunSmsProperties.getTemplates().get(smsType.getValue()); @@ -63,7 +64,7 @@ public class AliyunSmsService implements SmsService { // 您申请的模板 code request.putQueryParameter("TemplateCode", templateCode); - request.putQueryParameter("TemplateParam", templateParam); + request.putQueryParameter("TemplateParam", JSONUtil.toJsonStr(templateParams)); try { CommonResponse response = client.getCommonResponse(request); diff --git a/src/main/java/com/youlai/boot/system/controller/RoleController.java b/src/main/java/com/youlai/boot/system/controller/RoleController.java index 54cdcf33..b5b7419a 100644 --- a/src/main/java/com/youlai/boot/system/controller/RoleController.java +++ b/src/main/java/com/youlai/boot/system/controller/RoleController.java @@ -22,11 +22,10 @@ import jakarta.validation.Valid; import java.util.List; - /** * 角色控制层 * - * @author Ray + * @author Ray.Hao * @since 2022/10/16 */ @Tag(name = "03.角色接口") @@ -39,9 +38,9 @@ public class RoleController { @Operation(summary = "角色分页列表") @GetMapping("/page") - @Log( value = "角色分页列表",module = LogModuleEnum.ROLE) + @Log(value = "角色分页列表", module = LogModuleEnum.ROLE) public PageResult getRolePage( - RolePageQuery queryParams + RolePageQuery queryParams ) { Page result = roleService.getRolePage(queryParams); return PageResult.success(result); @@ -83,11 +82,11 @@ public class RoleController { @Operation(summary = "删除角色") @DeleteMapping("/{ids}") @PreAuthorize("@ss.hasPerm('sys:role:delete')") - public Result deleteRoles( + public Result deleteRoles( @Parameter(description = "删除角色,多个以英文逗号(,)拼接") @PathVariable String ids ) { - boolean result = roleService.deleteRoles(ids); - return Result.judge(result); + roleService.deleteRoles(ids); + return Result.success(); } @Operation(summary = "修改角色状态") @@ -111,11 +110,11 @@ public class RoleController { @Operation(summary = "分配菜单(包括按钮权限)给角色") @PutMapping("/{roleId}/menus") - public Result assignMenusToRole( + public Result assignMenusToRole( @PathVariable Long roleId, @RequestBody List menuIds ) { - boolean result = roleService.assignMenusToRole(roleId, menuIds); - return Result.judge(result); + roleService.assignMenusToRole(roleId, menuIds); + return Result.success(); } } diff --git a/src/main/java/com/youlai/boot/system/controller/UserController.java b/src/main/java/com/youlai/boot/system/controller/UserController.java index c425d14a..88af5819 100644 --- a/src/main/java/com/youlai/boot/system/controller/UserController.java +++ b/src/main/java/com/youlai/boot/system/controller/UserController.java @@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.youlai.boot.common.annotation.Log; import com.youlai.boot.common.annotation.RepeatSubmit; -import com.youlai.boot.system.enums.ContactType; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.model.Option; import com.youlai.boot.common.result.PageResult; @@ -45,7 +44,7 @@ import java.util.List; /** * 用户控制层 * - * @author Ray + * @author Ray.Hao * @since 2022/10/16 */ @Tag(name = "02.用户接口") @@ -203,39 +202,46 @@ public class UserController { @Operation(summary = "修改密码") @PutMapping(value = "/password") public Result changePassword( - @RequestBody PasswordChangeForm data + @RequestBody PasswordUpdateForm data ) { Long currUserId = SecurityUtils.getUserId(); boolean result = userService.changePassword(currUserId, data); return Result.judge(result); } - @Operation(summary = "发送短信/邮箱验证码") - @PostMapping(value = "/send-verification-code") - public Result sendVerificationCode( - @Parameter(description = "联系方式(手机号码或邮箱地址)", required = true) @RequestParam String contact, - @Parameter(description = "联系方式类型(Mobile或Email)", required = true) @RequestParam ContactType contactType + @Operation(summary = "发送短信验证码(绑定或更换手机号)") + @PostMapping(value = "/mobile/code") + public Result sendMobileCode( + @Parameter(description = "手机号码", required = true) @RequestParam String mobile ) { - boolean result = userService.sendVerificationCode(contact, contactType); + boolean result = userService.sendMobileCode(mobile); return Result.judge(result); } - @Operation(summary = "个人中心绑定用户手机号") + @Operation(summary = "绑定或更换手机号") @PutMapping(value = "/mobile") - public Result bindMobile( - @RequestBody @Validated MobileBindingForm data + public Result bindOrChangeMobile( + @RequestBody @Validated MobileUpdateForm data ) { - boolean result = userService.bindMobile(data); + boolean result = userService.bindOrChangeMobile(data); return Result.judge(result); } - - @Operation(summary = "个人中心绑定用户邮箱") - @PutMapping(value = "/email") - public Result bindEmail( - @RequestBody @Validated EmailBindingForm data + @Operation(summary = "发送邮箱验证码(绑定或更换邮箱)") + @PostMapping(value = "/email/code") + public Result sendEmailCode( + @Parameter(description = "邮箱地址", required = true) @RequestParam String email ) { - boolean result = userService.bindEmail(data); + userService.sendEmailCode(email); + return Result.success(); + } + + @Operation(summary = "绑定或更换邮箱") + @PutMapping(value = "/email") + public Result bindOrChangeEmail( + @RequestBody @Validated EmailUpdateForm data + ) { + boolean result = userService.bindOrChangeEmail(data); return Result.judge(result); } diff --git a/src/main/java/com/youlai/boot/system/converter/RoleConverter.java b/src/main/java/com/youlai/boot/system/converter/RoleConverter.java index 2ef57392..ddcfc930 100644 --- a/src/main/java/com/youlai/boot/system/converter/RoleConverter.java +++ b/src/main/java/com/youlai/boot/system/converter/RoleConverter.java @@ -26,9 +26,9 @@ public interface RoleConverter { @Mapping(target = "value", source = "id"), @Mapping(target = "label", source = "name") }) - Option entity2Option(Role role); + Option toOption(Role role); - List> entities2Options(List roles); + List> toOptions(List roles); Role toEntity(RoleForm roleForm); diff --git a/src/main/java/com/youlai/boot/system/converter/UserConverter.java b/src/main/java/com/youlai/boot/system/converter/UserConverter.java index 96840c8a..2134c5e8 100644 --- a/src/main/java/com/youlai/boot/system/converter/UserConverter.java +++ b/src/main/java/com/youlai/boot/system/converter/UserConverter.java @@ -1,6 +1,7 @@ package com.youlai.boot.system.converter; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.youlai.boot.common.model.Option; import com.youlai.boot.system.model.entity.User; import com.youlai.boot.system.model.vo.UserInfoVO; import com.youlai.boot.system.model.vo.UserPageVO; @@ -14,10 +15,12 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; +import java.util.List; + /** * 用户对象转换器 * - * @author haoxr + * @author Ray.Hao * @since 2022/6/8 */ @Mapper(componentModel = "spring") @@ -43,4 +46,12 @@ public interface UserConverter { UserProfileVO toProfileVO(UserBO bo); User toEntity(UserProfileForm formData); + + @Mappings({ + @Mapping(target = "label", source = "nickname"), + @Mapping(target = "value", source = "id") + }) + Option toOption(User entity); + + List> toOptions(List list); } diff --git a/src/main/java/com/youlai/boot/system/enums/ContactType.java b/src/main/java/com/youlai/boot/system/enums/ContactType.java deleted file mode 100644 index 8c6f87dc..00000000 --- a/src/main/java/com/youlai/boot/system/enums/ContactType.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.youlai.boot.system.enums; - -/** - * 联系方式类型 - * - * @author Ray - * @since 2.10.0 - */ -public enum ContactType { - /** - * 手机 - */ - MOBILE, - - /** - * 邮箱 - */ - EMAIL -} diff --git a/src/main/java/com/youlai/boot/system/enums/DictCodeEnum.java b/src/main/java/com/youlai/boot/system/enums/DictCodeEnum.java index add33329..0ccddd7e 100644 --- a/src/main/java/com/youlai/boot/system/enums/DictCodeEnum.java +++ b/src/main/java/com/youlai/boot/system/enums/DictCodeEnum.java @@ -6,7 +6,7 @@ import lombok.Getter; /** * 字典编码枚举 * - * @author Ray + * @author Ray.Hao * @since 2024/10/30 */ @Getter diff --git a/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java b/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java index fa24303e..d78fe5b0 100644 --- a/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java +++ b/src/main/java/com/youlai/boot/system/enums/MenuTypeEnum.java @@ -7,7 +7,7 @@ import lombok.Getter; /** * 菜单类型枚举 * - * @author haoxr + * @author Ray.Hao * @since 2022/4/23 9:36 */ @Getter diff --git a/src/main/java/com/youlai/boot/system/enums/NoticePublishStatusEnum.java b/src/main/java/com/youlai/boot/system/enums/NoticePublishStatusEnum.java index cd5e718e..bedd5e53 100644 --- a/src/main/java/com/youlai/boot/system/enums/NoticePublishStatusEnum.java +++ b/src/main/java/com/youlai/boot/system/enums/NoticePublishStatusEnum.java @@ -7,7 +7,7 @@ import lombok.Getter; /** * 通告发布状态枚举 * - * @author haoxr + * @author Ray.Hao * @since 2024/10/14 */ @Getter diff --git a/src/main/java/com/youlai/boot/system/enums/NoticeTargetEnum.java b/src/main/java/com/youlai/boot/system/enums/NoticeTargetEnum.java index 6cf6c974..cbd9638e 100644 --- a/src/main/java/com/youlai/boot/system/enums/NoticeTargetEnum.java +++ b/src/main/java/com/youlai/boot/system/enums/NoticeTargetEnum.java @@ -7,8 +7,8 @@ import lombok.Getter; /** * 通知目标类型枚举 * - * @author haoxr - * @since 2022/10/14 + * @author Ray.Hao + * @since 2024/10/14 */ @Getter @Schema(enumAsRef = true) diff --git a/src/main/java/com/youlai/boot/system/model/form/EmailBindingForm.java b/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java similarity index 75% rename from src/main/java/com/youlai/boot/system/model/form/EmailBindingForm.java rename to src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java index 74986b10..971fdf45 100644 --- a/src/main/java/com/youlai/boot/system/model/form/EmailBindingForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/EmailUpdateForm.java @@ -4,14 +4,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** - * 绑定邮箱表单 + * 修改邮箱表单 * * @author Ray.Hao * @since 2024/8/19 */ -@Schema(description = "绑定邮箱表单") +@Schema(description = "修改邮箱表单") @Data -public class EmailBindingForm { +public class EmailUpdateForm { @Schema(description = "邮箱") private String email; diff --git a/src/main/java/com/youlai/boot/system/model/form/MobileBindingForm.java b/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java similarity index 71% rename from src/main/java/com/youlai/boot/system/model/form/MobileBindingForm.java rename to src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java index 26623138..a5612b48 100644 --- a/src/main/java/com/youlai/boot/system/model/form/MobileBindingForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/MobileUpdateForm.java @@ -4,14 +4,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** - * 绑定手机表单 + * 修改手机表单 * - * @author Ray + * @author Ray.Hao * @since 2024/8/19 */ -@Schema(description = "绑定手机表单") +@Schema(description = "修改手机表单") @Data -public class MobileBindingForm { +public class MobileUpdateForm { @Schema(description = "手机号码") private String mobile; diff --git a/src/main/java/com/youlai/boot/system/model/form/PasswordChangeForm.java b/src/main/java/com/youlai/boot/system/model/form/PasswordUpdateForm.java similarity index 87% rename from src/main/java/com/youlai/boot/system/model/form/PasswordChangeForm.java rename to src/main/java/com/youlai/boot/system/model/form/PasswordUpdateForm.java index f282c6bf..453207a2 100644 --- a/src/main/java/com/youlai/boot/system/model/form/PasswordChangeForm.java +++ b/src/main/java/com/youlai/boot/system/model/form/PasswordUpdateForm.java @@ -6,12 +6,12 @@ import lombok.Data; /** * 修改密码表单 * - * @author Ray + * @author Ray.Hao * @since 2024/8/13 */ @Schema(description = "修改密码表单") @Data -public class PasswordChangeForm { +public class PasswordUpdateForm { @Schema(description = "原密码") private String oldPassword; diff --git a/src/main/java/com/youlai/boot/system/service/RoleService.java b/src/main/java/com/youlai/boot/system/service/RoleService.java index 348af800..43761877 100644 --- a/src/main/java/com/youlai/boot/system/service/RoleService.java +++ b/src/main/java/com/youlai/boot/system/service/RoleService.java @@ -64,10 +64,8 @@ public interface RoleService extends IService { * 批量删除角色 * * @param ids 角色ID,多个使用英文逗号(,)分割 - * @return */ - boolean deleteRoles(String ids); - + void deleteRoles(String ids); /** * 获取角色的菜单ID集合 @@ -77,15 +75,13 @@ public interface RoleService extends IService { */ List getRoleMenuIds(Long roleId); - /** * 修改角色的资源权限 * - * @param roleId - * @param menuIds - * @return + * @param roleId 角色ID + * @param menuIds 菜单ID集合 */ - boolean assignMenusToRole(Long roleId, List menuIds); + void assignMenusToRole(Long roleId, List menuIds); /** * 获取最大范围的数据权限 diff --git a/src/main/java/com/youlai/boot/system/service/UserService.java b/src/main/java/com/youlai/boot/system/service/UserService.java index 4257bab6..cacdbd2d 100644 --- a/src/main/java/com/youlai/boot/system/service/UserService.java +++ b/src/main/java/com/youlai/boot/system/service/UserService.java @@ -1,9 +1,7 @@ package com.youlai.boot.system.service; - import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.youlai.boot.system.enums.ContactType; import com.youlai.boot.common.model.Option; import com.youlai.boot.system.model.dto.UserAuthInfo; import com.youlai.boot.system.model.dto.UserExportDTO; @@ -27,16 +25,15 @@ public interface UserService extends IService { /** * 用户分页列表 * - * @return + * @return {@link IPage} 用户分页列表 */ IPage getUserPage(UserPageQuery queryParams); - /** * 获取用户表单数据 * - * @param userId - * @return + * @param userId 用户ID + * @return {@link UserForm} 用户表单数据 */ UserForm getUserFormData(Long userId); @@ -45,7 +42,7 @@ public interface UserService extends IService { * 新增用户 * * @param userForm 用户表单对象 - * @return + * @return {@link Boolean} 是否新增成功 */ boolean saveUser(UserForm userForm); @@ -54,7 +51,7 @@ public interface UserService extends IService { * * @param userId 用户ID * @param userForm 用户表单对象 - * @return + * @return {@link Boolean} 是否修改成功 */ boolean updateUser(Long userId, UserForm userForm); @@ -63,7 +60,7 @@ public interface UserService extends IService { * 删除用户 * * @param idsStr 用户ID,多个以英文逗号(,)分割 - * @return + * @return {@link Boolean} 是否删除成功 */ boolean deleteUsers(String idsStr); @@ -82,7 +79,7 @@ public interface UserService extends IService { * 获取导出用户列表 * * @param queryParams 查询参数 - * @return + * @return {@link List} 导出用户列表 */ List listExportUsers(UserPageQuery queryParams); @@ -90,14 +87,14 @@ public interface UserService extends IService { /** * 获取登录用户信息 * - * @return + * @return {@link UserInfoVO} 登录用户信息 */ UserInfoVO getCurrentUserInfo(); /** * 获取个人中心用户信息 * - * @return + * @return {@link UserProfileVO} 个人中心用户信息 */ UserProfileVO getUserProfile(Long userId); @@ -105,7 +102,7 @@ public interface UserService extends IService { * 修改个人中心用户信息 * * @param formData 表单数据 - * @return + * @return {@link Boolean} 是否修改成功 */ boolean updateUserProfile(UserProfileForm formData); @@ -114,43 +111,49 @@ public interface UserService extends IService { * * @param userId 用户ID * @param data 修改密码表单数据 - * @return + * @return {@link Boolean} 是否修改成功 */ - boolean changePassword(Long userId, PasswordChangeForm data); + boolean changePassword(Long userId, PasswordUpdateForm data); /** * 重置用户密码 * * @param userId 用户ID * @param password 重置后的密码 - * @return + * @return {@link Boolean} 是否重置成功 */ boolean resetPassword(Long userId, String password); /** - * 发送验证码 + * 发送短信验证码(绑定或更换手机号) * - * @param contact 联系方式 - * @param type 联系方式类型 - * @return + * @param mobile 手机号 + * @return {@link Boolean} 是否发送成功 */ - boolean sendVerificationCode(String contact, ContactType type); + boolean sendMobileCode(String mobile); /** * 修改当前用户手机号 * * @param data 表单数据 - * @return + * @return {@link Boolean} 是否修改成功 */ - boolean bindMobile(MobileBindingForm data); + boolean bindOrChangeMobile(MobileUpdateForm data); /** - * 修改当前用户邮箱 + * 发送邮箱验证码(绑定或更换邮箱) + * + * @param email 邮箱 + */ + void sendEmailCode(String email); + + /** + * 绑定或更换邮箱 * * @param data 表单数据 * @return {@link Boolean} 是否绑定成功 */ - boolean bindEmail(EmailBindingForm data); + boolean bindOrChangeEmail(EmailUpdateForm data); /** * 获取用户选项列表 @@ -182,4 +185,6 @@ public interface UserService extends IService { * @return {@link UserAuthInfo} */ UserAuthInfo getUserAuthInfoByMobile(String mobile); + + } diff --git a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java index 95536ce5..f1e8f66b 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/RoleServiceImpl.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.system.converter.RoleConverter; import com.youlai.boot.system.mapper.RoleMapper; import com.youlai.boot.system.model.entity.Role; @@ -88,7 +89,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro ); // 实体转换 - return roleConverter.entities2Options(roleList); + return roleConverter.toOptions(roleList); } /** @@ -157,7 +158,9 @@ public class RoleServiceImpl extends ServiceImpl implements Ro public boolean updateRoleStatus(Long roleId, Integer status) { Role role = this.getById(roleId); - Assert.isTrue(role != null, "角色不存在"); + if (role == null) { + throw new BusinessException("角色不存在"); + } role.setStatus(status); boolean result = this.updateById(role); @@ -172,10 +175,9 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * 批量删除角色 * * @param ids 角色ID,多个使用英文逗号(,)分割 - * @return {@link Boolean} */ @Override - public boolean deleteRoles(String ids) { + public void deleteRoles(String ids) { Assert.isTrue(StrUtil.isNotBlank(ids), "删除的角色ID不能为空"); List roleIds = Arrays.stream(ids.split(",")) .map(Long::parseLong) @@ -195,7 +197,6 @@ public class RoleServiceImpl extends ServiceImpl implements Ro roleMenuService.refreshRolePermsCache(role.getCode()); } } - return true; } /** @@ -214,15 +215,15 @@ public class RoleServiceImpl extends ServiceImpl implements Ro * * @param roleId 角色ID * @param menuIds 菜单ID集合 - * @return {@link Boolean} */ @Override @Transactional @CacheEvict(cacheNames = "menu", key = "'routes'") - public boolean assignMenusToRole(Long roleId, List menuIds) { + public void assignMenusToRole(Long roleId, List menuIds) { Role role = this.getById(roleId); - Assert.isTrue(role != null, "角色不存在"); - + if (role == null) { + throw new RuntimeException("角色不存在"); + } // 删除角色菜单 roleMenuService.remove( new LambdaQueryWrapper() @@ -239,8 +240,6 @@ public class RoleServiceImpl extends ServiceImpl implements Ro // 刷新角色的权限缓存 roleMenuService.refreshRolePermsCache(role.getCode()); - - return true; } /** diff --git a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java index 0d413931..e9beb707 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java @@ -11,14 +11,13 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.SystemConstants; import com.youlai.boot.core.security.manager.TokenManager; -import com.youlai.boot.system.enums.ContactType; import com.youlai.boot.common.model.Option; import com.youlai.boot.shared.mail.service.MailService; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; import com.youlai.boot.shared.sms.service.SmsService; import com.youlai.boot.system.model.entity.User; import com.youlai.boot.system.model.entity.UserRole; import com.youlai.boot.system.model.form.*; -import com.youlai.boot.config.property.AliyunSmsProperties; import com.youlai.boot.system.converter.UserConverter; import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.system.model.vo.UserProfileVO; @@ -40,17 +39,14 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 用户业务实现类 * - * @author haoxr + * @author Ray.Hao * @since 2022/1/14 */ @Service @@ -69,8 +65,6 @@ public class UserServiceImpl extends ServiceImpl implements Us private final MailService mailService; - private final AliyunSmsProperties aliyunSmsProperties; - private final StringRedisTemplate redisTemplate; private final TokenManager tokenManager; @@ -101,7 +95,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 获取用户表单数据 * * @param userId 用户ID - * @return + * @return {@link UserForm} 用户表单数据 */ @Override public UserForm getUserFormData(Long userId) { @@ -112,7 +106,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 新增用户 * * @param userForm 用户表单对象 - * @return + * @return true|false */ @Override public boolean saveUser(UserForm userForm) { @@ -144,7 +138,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * * @param userId 用户ID * @param userForm 用户表单对象 - * @return + * @return true|false */ @Override @Transactional @@ -206,7 +200,6 @@ public class UserServiceImpl extends ServiceImpl implements Us return userAuthInfo; } - /** * 根据 openid 获取用户认证信息 * @@ -225,7 +218,6 @@ public class UserServiceImpl extends ServiceImpl implements Us return userAuthInfo; } - /** * 根据手机号获取用户认证信息 * @@ -235,8 +227,13 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public UserAuthInfo getUserAuthInfoByMobile(String mobile) { UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfoByMobile(mobile); - - return null; + if (userAuthInfo != null) { + Set roles = userAuthInfo.getRoles(); + // 获取最大范围的数据权限 + Integer dataScope = roleService.getMaximumDataScope(roles); + userAuthInfo.setDataScope(dataScope); + } + return userAuthInfo; } @@ -319,7 +316,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 获取个人中心用户信息 * * @param userId 用户ID - * @return + * @return {@link UserProfileVO} 个人中心用户信息 */ @Override public UserProfileVO getUserProfile(Long userId) { @@ -331,7 +328,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 修改个人中心用户信息 * * @param formData 表单数据 - * @return + * @return true|false */ @Override public boolean updateUserProfile(UserProfileForm formData) { @@ -347,10 +344,10 @@ public class UserServiceImpl extends ServiceImpl implements Us * * @param userId 用户ID * @param data 密码修改表单数据 - * @return + * @return true|false */ @Override - public boolean changePassword(Long userId, PasswordChangeForm data) { + public boolean changePassword(Long userId, PasswordUpdateForm data) { User user = this.getById(userId); if (user == null) { @@ -387,7 +384,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * * @param userId 用户ID * @param password 密码重置表单数据 - * @return + * @return true|false */ @Override public boolean resetPassword(Long userId, String password) { @@ -398,47 +395,38 @@ public class UserServiceImpl extends ServiceImpl implements Us } /** - * 发送验证码 + * 发送短信验证码(绑定或更换手机号) * - * @param contact 联系方式 手机号/邮箱 - * @param type 联系方式类型 {@link ContactType} - * @return + * @param mobile 手机号 + * @return true|false */ @Override - public boolean sendVerificationCode(String contact, ContactType type) { + public boolean sendMobileCode(String mobile) { - // 随机生成4位验证码 - String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); - // 发送验证码 + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码 + String code = "1234"; - String verificationCodePrefix = null; - switch (type) { - case MOBILE: - // 获取修改密码的模板code - String changePasswordSmsTemplateCode = aliyunSmsProperties.getTemplateCodes().get("changePassword"); - smsService.sendSms(contact, changePasswordSmsTemplateCode, "[{\"code\":\"" + code + "\"}]"); - verificationCodePrefix = RedisConstants.MOBILE_VERIFICATION_CODE_PREFIX; - break; - case EMAIL: - mailService.sendMail(contact, "验证码", "您的验证码是:" + code); - verificationCodePrefix = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX; - break; - default: - throw new BusinessException("不支持的联系方式类型"); + Map templateParams = new HashMap<>(); + templateParams.put("code", code); + boolean result = smsService.sendSms(mobile, SmsTypeEnum.CHANGE_MOBILE, templateParams); + if (result) { + // 缓存验证码,5分钟有效,用于更换手机号校验 + String redisCacheKey = RedisConstants.SMS_CHANGE_CODE_PREFIX + mobile; + redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES); } - // 存入 redis 用于校验, 5分钟有效 - redisTemplate.opsForValue().set(verificationCodePrefix + contact, code, 5, TimeUnit.MINUTES); - return true; + return result; } /** - * 修改当前用户手机号码 + * 绑定或更换手机号 * * @param form 表单数据 - * @return + * @return true|false */ @Override - public boolean bindMobile(MobileBindingForm form) { + public boolean bindOrChangeMobile(MobileUpdateForm form) { + Long currentUserId = SecurityUtils.getUserId(); User currentUser = this.getById(currentUserId); @@ -447,13 +435,13 @@ public class UserServiceImpl extends ServiceImpl implements Us } // 校验验证码 - String inputVerificationCode = form.getCode(); + String inputVerifyCode = form.getCode(); String mobile = form.getMobile(); - String redisCacheKey = RedisConstants.MOBILE_VERIFICATION_CODE_PREFIX + mobile; - String cachedVerificationCode = redisTemplate.opsForValue().get(redisCacheKey); + String redisCacheKey = RedisConstants.SMS_CHANGE_CODE_PREFIX + mobile; + String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey); - if (!inputVerificationCode.equals(cachedVerificationCode)) { + if (!inputVerifyCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } @@ -465,6 +453,24 @@ public class UserServiceImpl extends ServiceImpl implements Us ); } + /** + * 发送邮箱验证码(绑定或更换邮箱) + * + * @param email 邮箱 + */ + @Override + public void sendEmailCode(String email) { + + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码 + String code = "1234"; + + mailService.sendMail(email, "邮箱验证码", "您的验证码为:" + code + ",请在5分钟内使用"); + // 缓存验证码,5分钟有效,用于更换邮箱校验 + String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email; + redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES); + } + /** * 修改当前用户邮箱 * @@ -472,7 +478,8 @@ public class UserServiceImpl extends ServiceImpl implements Us * @return true|false */ @Override - public boolean bindEmail(EmailBindingForm form) { + public boolean bindOrChangeEmail(EmailUpdateForm form) { + Long currentUserId = SecurityUtils.getUserId(); User currentUser = this.getById(currentUserId); @@ -480,14 +487,15 @@ public class UserServiceImpl extends ServiceImpl implements Us throw new BusinessException("用户不存在"); } - // 校验验证码 - String inputVerificationCode = form.getCode(); - String email = form.getEmail(); + // 获取前端输入的验证码 + String inputVerifyCode = form.getCode(); - String redisCacheKey = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX + email; + // 获取缓存的验证码 + String email = form.getEmail(); + String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email; String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey); - if (!inputVerificationCode.equals(cachedVerifyCode)) { + if (!inputVerifyCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } @@ -506,11 +514,10 @@ public class UserServiceImpl extends ServiceImpl implements Us */ @Override public List> listUserOptions() { - List list = this.list(); - if (CollectionUtil.isNotEmpty(list)) { - return list.stream().map(user -> new Option<>(user.getId().toString(), user.getNickname())).collect(Collectors.toList()); - } - return Collections.emptyList(); + List list = this.list(new LambdaQueryWrapper() + .eq(User::getStatus, 1) + ); + return userConverter.toOptions(list); } }