diff --git a/pom.xml b/pom.xml index c45f36ae..c5885bd0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.youlai youlai-boot - 2.19.0 + 2.21.0 基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。 diff --git a/sql/mysql5/youlai_boot.sql b/sql/mysql5/youlai_boot.sql index 87f0a8f6..b9dbe911 100644 --- a/sql/mysql5/youlai_boot.sql +++ b/sql/mysql5/youlai_boot.sql @@ -416,9 +416,9 @@ CREATE TABLE `sys_user` ( -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668888', 1, 'youlaitech@163.com', NULL, NULL, NULL, NULL, 0,NULL); -INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668887', 1, '', '2019-10-10 13:41:22', NULL, '2022-07-31 12:39:30', NULL, 0,NULL); -INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668886', 1, 'youlaitech@163.com', '2021-06-05 01:31:29', NULL, '2021-06-05 01:31:29', NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', NULL, NULL, NULL, NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, '', '2019-10-10 13:41:22', NULL, '2022-07-31 12:39:30', NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', '2021-06-05 01:31:29', NULL, '2021-06-05 01:31:29', NULL, 0,NULL); -- ---------------------------- -- Table structure for sys_user_role diff --git a/sql/mysql8/youlai_boot.sql b/sql/mysql8/youlai_boot.sql index 3b2d8dd6..70247977 100644 --- a/sql/mysql8/youlai_boot.sql +++ b/sql/mysql8/youlai_boot.sql @@ -374,9 +374,9 @@ CREATE TABLE `sys_user` ( -- ---------------------------- -- Records of sys_user -- ---------------------------- -INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668888', 1, 'youlaitech@163.com', NULL, NULL, NULL, NULL, 0,NULL); -INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668887', 1, '', '2019-10-10 13:41:22', NULL, '2022-07-31 12:39:30', NULL, 0,NULL); -INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18866668886', 1, 'youlaitech@163.com', '2021-06-05 01:31:29', NULL, '2021-06-05 01:31:29', NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', NULL, NULL, NULL, NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, '', '2019-10-10 13:41:22', NULL, '2022-07-31 12:39:30', NULL, 0,NULL); +INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', '2021-06-05 01:31:29', NULL, '2021-06-05 01:31:29', NULL, 0,NULL); -- ---------------------------- -- Table structure for sys_user_role 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 48ca4765..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,35 +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 MOBILE_VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE:MOBILE:"; - + String SMS_CHANGE_CODE_PREFIX = "code:sms:change:"; /** - * 邮箱验证码缓存前缀 + * 绑定或更换邮箱验证码 Redis 键前缀 */ - 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/OpenApiConfig.java b/src/main/java/com/youlai/boot/config/OpenApiConfig.java index 1269192f..5e6c2d54 100644 --- a/src/main/java/com/youlai/boot/config/OpenApiConfig.java +++ b/src/main/java/com/youlai/boot/config/OpenApiConfig.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.http.HttpHeaders; +import org.springframework.util.AntPathMatcher; import java.util.stream.Stream; @@ -82,10 +83,12 @@ public class OpenApiConfig { if (openApi.getPaths() != null) { openApi.getPaths().forEach((path, pathItem) -> { - // 忽略认证的请求无需携带Authorization + // 忽略认证的请求无需携带 Authorization String[] ignoreUrls = securityProperties.getIgnoreUrls(); if (ArrayUtil.isNotEmpty(ignoreUrls)) { - if (Stream.of(ignoreUrls).anyMatch(path::equals)) { + // Ant 匹配忽略的路径,不添加Authorization + AntPathMatcher antPathMatcher = new AntPathMatcher(); + if (Stream.of(ignoreUrls).anyMatch(ignoreUrl -> antPathMatcher.match(ignoreUrl, path))) { return; } } diff --git a/src/main/java/com/youlai/boot/config/SecurityConfig.java b/src/main/java/com/youlai/boot/config/SecurityConfig.java index 6b32835e..587c1295 100644 --- a/src/main/java/com/youlai/boot/config/SecurityConfig.java +++ b/src/main/java/com/youlai/boot/config/SecurityConfig.java @@ -7,11 +7,12 @@ 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; -import com.youlai.boot.shared.auth.service.impl.JwtTokenService; +import com.youlai.boot.core.security.manager.JwtTokenManager; import com.youlai.boot.system.service.ConfigService; import com.youlai.boot.system.service.UserService; import lombok.RequiredArgsConstructor; @@ -47,7 +48,7 @@ public class SecurityConfig { private final RedisTemplate redisTemplate; private final PasswordEncoder passwordEncoder; - private final JwtTokenService jwtTokenService; + private final JwtTokenManager jwtTokenService; private final WxMaService wxMaService; private final UserService userService; private final SysUserDetailsService userDetailsService; @@ -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/config/property/AliyunSmsProperties.java b/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java index 05cf7b66..138d9479 100644 --- a/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java +++ b/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java @@ -43,8 +43,8 @@ public class AliyunSmsProperties { private String signName; /** - * 模板编码 + * 短信模板集合 */ - private Map templateCodes; + private Map templates; } 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 new file mode 100644 index 00000000..db7fa180 --- /dev/null +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java @@ -0,0 +1,87 @@ +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.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.RedisTemplate; +import org.springframework.security.authentication.AuthenticationProvider; +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; + + +/** + * 短信验证码认证 Provider + * + * @author Ray.Hao + * @since 2.17.0 + */ +@Slf4j +public class SmsAuthenticationProvider implements AuthenticationProvider { + + private final UserService userService; + + private final RedisTemplate redisTemplate; + + + public SmsAuthenticationProvider(UserService userService, RedisTemplate redisTemplate) { + this.userService = userService; + this.redisTemplate = redisTemplate; + } + + /** + * 短信验证码认证逻辑,参考 Spring Security 认证密码校验流程 + * + * @param authentication 认证对象 + * @return 认证后的 Authentication 对象 + * @throws AuthenticationException 认证异常 + * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate(Authentication) + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String mobile = (String) authentication.getPrincipal(); + 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); + + if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) { + throw new BadCredentialsException("验证码错误"); + } else { + // 验证成功后删除验证码 + redisTemplate.delete(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile); + } + + // 构建认证后的用户详情信息 + SysUserDetails userDetails = new SysUserDetails(userAuthInfo); + + // 创建已认证的 WeChatAuthenticationToken + return SmsAuthenticationToken.authenticated( + userDetails, + userDetails.getAuthorities() + ); + } + + @Override + public boolean supports(Class 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 new file mode 100644 index 00000000..8f1484c7 --- /dev/null +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java @@ -0,0 +1,78 @@ +package com.youlai.boot.core.security.extension.sms; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.io.Serial; +import java.util.Collection; + +/** + * 短信验证码认证 Token + * + * @author Ray.Hao + * @since 2.20.0 + */ +public class SmsAuthenticationToken extends AbstractAuthenticationToken { + @Serial + private static final long serialVersionUID = 621L; + + /** + * 认证信息 (手机号) + */ + private final Object principal; + + /** + * 凭证信息 (短信验证码) + */ + private final Object credentials; + + /** + * 短信验证码认证 Token (未认证) + * + * @param principal 微信用户信息 + */ + public SmsAuthenticationToken(Object principal, Object credentials) { + // 没有授权信息时,设置为 null + super(null); + this.principal = principal; + this.credentials = credentials; + // 默认未认证 + this.setAuthenticated(false); + } + + /** + * 短信验证码认证 Token (已认证) + * + * @param principal 用户信息 + * @param authorities 授权信息 + */ + public SmsAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = null; + // 认证通过 + super.setAuthenticated(true); + } + + + /** + * 认证通过 + * + * @param principal 用户信息 + * @param authorities 授权信息 + * @return + */ + public static SmsAuthenticationToken authenticated(Object principal, Collection authorities) { + return new SmsAuthenticationToken(principal, authorities); + } + + @Override + public Object getCredentials() { + return this.credentials; + } + + @Override + public Object getPrincipal() { + return this.principal; + } +} 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/filter/JwtAuthenticationFilter.java b/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java index ac14f3bc..3b2da4b9 100644 --- a/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java @@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil; import com.youlai.boot.common.constant.SecurityConstants; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.common.util.ResponseUtils; -import com.youlai.boot.shared.auth.service.impl.JwtTokenService; +import com.youlai.boot.core.security.manager.JwtTokenManager; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -24,10 +24,10 @@ import java.io.IOException; */ public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenService jwtTokenService; + private final JwtTokenManager jwtTokenService; - public JwtAuthenticationFilter(JwtTokenService jwtTokenService) { + public JwtAuthenticationFilter(JwtTokenManager jwtTokenService) { this.jwtTokenService = jwtTokenService; } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java similarity index 93% rename from src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java index b27e27f4..6bb06473 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java @@ -1,4 +1,4 @@ -package com.youlai.boot.shared.auth.service.impl; +package com.youlai.boot.core.security.manager; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; @@ -13,8 +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.shared.auth.model.AuthTokenResponse; -import com.youlai.boot.shared.auth.service.TokenService; +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; @@ -38,14 +37,14 @@ import java.util.stream.Collectors; */ @ConditionalOnProperty(value = "security.session.type", havingValue = "jwt") @Service -public class JwtTokenService implements TokenService { +public class JwtTokenManager implements TokenManager { private final SecurityProperties securityProperties; private final RedisTemplate redisTemplate; private final byte[] secretKey; - public JwtTokenService(SecurityProperties securityProperties, RedisTemplate redisTemplate) { + public JwtTokenManager(SecurityProperties securityProperties, RedisTemplate redisTemplate) { this.securityProperties = securityProperties; this.redisTemplate = redisTemplate; this.secretKey = securityProperties.getJwt().getKey().getBytes(); @@ -58,14 +57,14 @@ public class JwtTokenService implements TokenService { * @return 令牌响应对象 */ @Override - public AuthTokenResponse 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 AuthTokenResponse.builder() + return AuthenticationToken.builder() .accessToken(accessToken) .refreshToken(refreshToken) .tokenType("Bearer") @@ -164,7 +163,7 @@ public class JwtTokenService implements TokenService { */ @Override - public AuthTokenResponse refreshToken(String refreshToken) { + public AuthenticationToken refreshToken(String refreshToken) { boolean isValid = validateToken(refreshToken); if (!isValid) { @@ -175,7 +174,7 @@ public class JwtTokenService implements TokenService { int accessTokenExpiration = securityProperties.getJwt().getRefreshTokenTimeToLive(); String newAccessToken = generateToken(authentication, accessTokenExpiration); - return AuthTokenResponse.builder() + return AuthenticationToken.builder() .accessToken(newAccessToken) .refreshToken(refreshToken) .tokenType("Bearer") diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java similarity index 73% rename from src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java index 78603b4d..872b0bec 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java @@ -1,7 +1,6 @@ -package com.youlai.boot.shared.auth.service.impl; +package com.youlai.boot.core.security.manager; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; -import com.youlai.boot.shared.auth.service.TokenService; +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; @@ -14,7 +13,7 @@ import org.springframework.stereotype.Service; */ @ConditionalOnProperty(value = "security.session.type", havingValue = "redis-token") @Service -public class RedisTokenService implements TokenService { +public class RedisTokenManager implements TokenManager { /** * 生成令牌 @@ -23,7 +22,7 @@ public class RedisTokenService implements TokenService { * @return */ @Override - public AuthTokenResponse generateToken(Authentication authentication) { + public AuthenticationToken generateToken(Authentication authentication) { return null; } @@ -56,7 +55,7 @@ public class RedisTokenService implements TokenService { * @return */ @Override - public AuthTokenResponse refreshToken(String token) { + public AuthenticationToken refreshToken(String token) { return null; } } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/TokenService.java b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java similarity index 78% rename from src/main/java/com/youlai/boot/shared/auth/service/TokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/TokenManager.java index 674539f7..63466bd7 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/TokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java @@ -1,7 +1,7 @@ -package com.youlai.boot.shared.auth.service; +package com.youlai.boot.core.security.manager; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; +import com.youlai.boot.core.security.model.AuthenticationToken; import org.springframework.security.core.Authentication; /** @@ -10,7 +10,7 @@ import org.springframework.security.core.Authentication; * @author Ray * @since 2.16.0 */ -public interface TokenService { +public interface TokenManager { /** * 生成认证 Token @@ -18,7 +18,7 @@ public interface TokenService { * @param authentication 用户认证信息 * @return 认证 Token 响应 */ - AuthTokenResponse generateToken(Authentication authentication); + AuthenticationToken generateToken(Authentication authentication); /** * 解析 Token 获取认证信息 @@ -44,7 +44,7 @@ public interface TokenService { * @param token 刷新令牌 * @return 认证 Token 响应 */ - AuthTokenResponse refreshToken(String token); + AuthenticationToken refreshToken(String token); /** * 将 Token 加入黑名单 diff --git a/src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java b/src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java similarity index 84% rename from src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java rename to src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java index 719623c5..aaa352dd 100644 --- a/src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java +++ b/src/main/java/com/youlai/boot/core/security/model/AuthenticationToken.java @@ -1,4 +1,4 @@ -package com.youlai.boot.shared.auth.model; +package com.youlai.boot.core.security.model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -7,13 +7,13 @@ import lombok.Data; /** * 认证令牌响应对象 * - * @author Ray + * @author Ray.Hao * @since 0.0.1 */ @Schema(description = "认证令牌响应对象") @Data @Builder -public class AuthTokenResponse { +public class AuthenticationToken { @Schema(description = "令牌类型", example = "Bearer") private String tokenType; @@ -21,7 +21,6 @@ public class AuthTokenResponse { @Schema(description = "访问令牌") private String accessToken; - @Schema(description = "刷新令牌") private String refreshToken; 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 a6758aea..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.shared.auth.model.AuthTokenResponse; +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 ) { - AuthTokenResponse authTokenResponse = authService.login(username, password); - return Result.success(authTokenResponse); + 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,27 +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) { - AuthTokenResponse authTokenResponse = authService.refreshToken(request); - return Result.success(authTokenResponse); + 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 ) { - AuthTokenResponse loginResult = authService.wechatLogin(code); + AuthenticationToken loginResult = authService.loginByWechat(code); + return Result.success(loginResult); + } + + @Operation(summary = "发送登录短信验证码") + @PostMapping("/login/sms/code") + public Result sendLoginVerifyCode( + @Parameter(description = "手机号", example = "18812345678") @RequestParam String 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 1a4215a4..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.shared.auth.model.AuthTokenResponse; -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 登录结果 */ - AuthTokenResponse 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 登录结果 */ - AuthTokenResponse refreshToken(RefreshTokenRequest request); + AuthenticationToken refreshToken(String refreshToken); /** * 微信小程序登录 @@ -47,5 +46,21 @@ public interface AuthService { * @param code 微信登录code * @return 登录结果 */ - AuthTokenResponse wechatLogin(String code); + AuthenticationToken loginByWechat(String code); + + /** + * 发送短信验证码 + * + * @param 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 78bb6a74..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,18 +5,21 @@ 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.shared.auth.model.AuthTokenResponse; -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.shared.auth.service.TokenService; +import com.youlai.boot.core.security.manager.TokenManager; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; +import com.youlai.boot.shared.sms.service.SmsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -27,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 @@ -41,11 +46,15 @@ import java.util.concurrent.TimeUnit; public class AuthServiceImpl implements AuthService { private final AuthenticationManager authenticationManager; - private final RedisTemplate redisTemplate; - private final CodeGenerator codeGenerator; + private final TokenManager tokenManager; + private final Font captchaFont; private final CaptchaProperties captchaProperties; - private final TokenService tokenService; + private final CodeGenerator codeGenerator; + + private final SmsService smsService; + + private final RedisTemplate redisTemplate; /** * 用户名密码登录 @@ -55,7 +64,7 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthTokenResponse login(String username, String password) { + public AuthenticationToken login(String username, String password) { // 1. 创建用于密码认证的令牌(未认证) UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username.trim(), password); @@ -64,9 +73,10 @@ public class AuthServiceImpl implements AuthService { Authentication authentication = authenticationManager.authenticate(authenticationToken); // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) - AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication); + AuthenticationToken authenticationTokenResponse = + tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); - return authTokenResponse; + return authenticationTokenResponse; } /** @@ -76,22 +86,69 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthTokenResponse 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 使用(已认证) - AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication); + AuthenticationToken authenticationToken = tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); - return authTokenResponse; + return authenticationToken; } /** - * 注销 + * 发送短信验证码 + * + * @param mobile 手机号 + */ + @Override + public void sendSmsLoginCode(String mobile) { + + // 随机生成4位验证码 + // String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码 + String code = "1234"; + + // 发送短信验证码 + 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() { @@ -99,7 +156,7 @@ public class AuthServiceImpl implements AuthService { if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) { token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length()); // 将JWT令牌加入黑名单 - tokenService.blacklistToken(token); + tokenManager.blacklistToken(token); // 清除Security上下文 SecurityContextHolder.clearContext(); } @@ -111,7 +168,7 @@ public class AuthServiceImpl implements AuthService { * @return 验证码 */ @Override - public CaptchaResponse getCaptcha() { + public CaptchaInfo getCaptcha() { String captchaType = captchaProperties.getType(); int width = captchaProperties.getWidth(); @@ -143,31 +200,28 @@ 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 AuthTokenResponse refreshToken(RefreshTokenRequest request) { + public AuthenticationToken refreshToken(String refreshToken) { // 验证刷新令牌 - - String refreshToken = request.getRefreshToken(); - - boolean isValidate = tokenService.validateToken(refreshToken); + boolean isValidate = tokenManager.validateToken(refreshToken); if (!isValidate) { throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); } - - return tokenService.refreshToken(refreshToken); + // 刷新令牌有效,生成新的访问令牌 + return tokenManager.refreshToken(refreshToken); } diff --git a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java index e5ea9f2f..db5fce55 100644 --- a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java +++ b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java @@ -128,8 +128,8 @@ public class MinioFileService implements FileService { /** * 删除文件 * - * @param filePath 文件路径 - * https://oss.youlai.tech/default/20221120/test.jpg + * @param filePath 文件路径 http://localhost:9000/default/20221120/test.jpg + * * @return */ @Override @@ -151,10 +151,8 @@ public class MinioFileService implements FileService { minioClient.removeObject(removeObjectArgs); return true; - } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | - InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | - XmlParserException e) { - throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException("文件删除失败", e); } } diff --git a/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java b/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java index bb85859d..710601bc 100644 --- a/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java +++ b/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java @@ -6,6 +6,7 @@ package com.youlai.boot.shared.sms.controller; * @author Ray * @since 2.10.0 */ + public class SmsController { 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 new file mode 100644 index 00000000..b852a797 --- /dev/null +++ b/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java @@ -0,0 +1,39 @@ +package com.youlai.boot.shared.sms.enums; + +import com.youlai.boot.common.base.IBaseEnum; +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", "登录短信验证码"), + + /** + * 修改手机号短信验证码 + */ + CHANGE_MOBILE("change-mobile", "修改手机号短信验证码"); + + private final String value; + private final String label; + + SmsTypeEnum(String value, String label) { + this.value = value; + this.label = 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 85583d07..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 @@ -1,11 +1,13 @@ package com.youlai.boot.shared.sms.service; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; + +import java.util.Map; + /** * 短信服务接口层 - *

- * SMS = Short Message Service 短信服务 * - * @author Ray + * @author Ray.Hao * @since 2024/8/17 */ public interface SmsService { @@ -13,10 +15,10 @@ public interface SmsService { /** * 发送短信 * - * @param mobile 手机号 13388886666 - * @param templateCode 短信模板 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, String templateCode, 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 a831bdf3..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,23 +1,26 @@ 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; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; 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 @@ -28,14 +31,15 @@ public class AliyunSmsService implements SmsService { /** * 发送短信验证码 * - * @param mobile 手机号 13388886666 - * @param templateCode 短信模板 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,String templateCode,String templateParam) { + public boolean sendSms(String mobile, SmsTypeEnum smsType, Map templateParams) { + + String templateCode = aliyunSmsProperties.getTemplates().get(smsType.getValue()); DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), aliyunSmsProperties.getAccessKeyId(), aliyunSmsProperties.getAccessKeySecret()); @@ -60,13 +64,11 @@ 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); return response.getHttpResponse().isSuccess(); - } catch (ServerException e) { - e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } 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/mapper/UserMapper.java b/src/main/java/com/youlai/boot/system/mapper/UserMapper.java index 2f2e2c87..10708945 100644 --- a/src/main/java/com/youlai/boot/system/mapper/UserMapper.java +++ b/src/main/java/com/youlai/boot/system/mapper/UserMapper.java @@ -56,6 +56,14 @@ public interface UserMapper extends BaseMapper { */ UserAuthInfo getUserAuthInfoByOpenId(String openid); + /** + * 根据手机号获取用户认证信息 + * + * @param mobile + * @return + */ + UserAuthInfo getUserAuthInfoByMobile(String mobile); + /** * 获取导出用户列表 * @@ -73,5 +81,4 @@ public interface UserMapper extends BaseMapper { */ UserBO getUserProfile(Long userId); - } 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 e68db0c9..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; @@ -19,7 +17,7 @@ import java.util.List; /** * 用户业务接口 * - * @author haoxr + * @author Ray.Hao * @since 2022/1/14 */ public interface UserService extends IService { @@ -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); /** * 获取用户选项列表 @@ -174,4 +177,14 @@ public interface UserService extends IService { * @param openId 微信 OpenID */ void registerOrBindWechatUser(String openId); + + /** + * 根据手机号获取用户认证信息 + * + * @param mobile 手机号 + * @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 c2abd1e9..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 @@ -10,15 +10,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.shared.auth.service.TokenService; -import com.youlai.boot.system.enums.ContactType; +import com.youlai.boot.core.security.manager.TokenManager; 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; @@ -31,7 +30,6 @@ import com.youlai.boot.system.model.dto.UserExportDTO; import com.youlai.boot.system.model.vo.UserInfoVO; import com.youlai.boot.system.model.vo.UserPageVO; import com.youlai.boot.core.security.service.PermissionService; -import com.youlai.boot.system.service.RoleMenuService; import com.youlai.boot.system.service.RoleService; import com.youlai.boot.system.service.UserRoleService; import com.youlai.boot.system.service.UserService; @@ -41,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 @@ -62,8 +57,6 @@ public class UserServiceImpl extends ServiceImpl implements Us private final UserRoleService userRoleService; - private final RoleMenuService roleMenuService; - private final RoleService roleService; private final PermissionService permissionService; @@ -72,11 +65,9 @@ public class UserServiceImpl extends ServiceImpl implements Us private final MailService mailService; - private final AliyunSmsProperties aliyunSmsProperties; - private final StringRedisTemplate redisTemplate; - private final TokenService tokenService; + private final TokenManager tokenManager; private final UserConverter userConverter; @@ -104,7 +95,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 获取用户表单数据 * * @param userId 用户ID - * @return + * @return {@link UserForm} 用户表单数据 */ @Override public UserForm getUserFormData(Long userId) { @@ -115,7 +106,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 新增用户 * * @param userForm 用户表单对象 - * @return + * @return true|false */ @Override public boolean saveUser(UserForm userForm) { @@ -147,7 +138,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * * @param userId 用户ID * @param userForm 用户表单对象 - * @return + * @return true|false */ @Override @Transactional @@ -209,7 +200,6 @@ public class UserServiceImpl extends ServiceImpl implements Us return userAuthInfo; } - /** * 根据 openid 获取用户认证信息 * @@ -228,6 +218,25 @@ public class UserServiceImpl extends ServiceImpl implements Us return userAuthInfo; } + /** + * 根据手机号获取用户认证信息 + * + * @param mobile 手机号 + * @return {@link UserAuthInfo} + */ + @Override + public UserAuthInfo getUserAuthInfoByMobile(String mobile) { + UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfoByMobile(mobile); + if (userAuthInfo != null) { + Set roles = userAuthInfo.getRoles(); + // 获取最大范围的数据权限 + Integer dataScope = roleService.getMaximumDataScope(roles); + userAuthInfo.setDataScope(dataScope); + } + return userAuthInfo; + } + + /** * 根据微信 OpenID 注册或绑定用户 *

@@ -307,7 +316,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 获取个人中心用户信息 * * @param userId 用户ID - * @return + * @return {@link UserProfileVO} 个人中心用户信息 */ @Override public UserProfileVO getUserProfile(Long userId) { @@ -319,7 +328,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 修改个人中心用户信息 * * @param formData 表单数据 - * @return + * @return true|false */ @Override public boolean updateUserProfile(UserProfileForm formData) { @@ -335,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) { @@ -365,7 +374,7 @@ public class UserServiceImpl extends ServiceImpl implements Us if (result) { // 加入黑名单,重新登录 String accessToken = SecurityUtils.getTokenFromRequest(); - tokenService.blacklistToken(accessToken); + tokenManager.blacklistToken(accessToken); } return result; } @@ -375,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) { @@ -386,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); @@ -435,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("验证码错误"); } @@ -453,14 +453,33 @@ 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); + } + /** * 修改当前用户邮箱 * * @param form 表单数据 - * @return + * @return true|false */ @Override - public boolean bindEmail(EmailBindingForm form) { + public boolean bindOrChangeEmail(EmailUpdateForm form) { + Long currentUserId = SecurityUtils.getUserId(); User currentUser = this.getById(currentUserId); @@ -468,14 +487,15 @@ public class UserServiceImpl extends ServiceImpl implements Us throw new BusinessException("用户不存在"); } - // 校验验证码 - String inputVerificationCode = form.getCode(); + // 获取前端输入的验证码 + String inputVerifyCode = form.getCode(); + + // 获取缓存的验证码 String email = form.getEmail(); + String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email; + String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey); - String redisCacheKey = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX + email; - String cachedVerificationCode = redisTemplate.opsForValue().get(redisCacheKey); - - if (cachedVerificationCode == null || !inputVerificationCode.equals(cachedVerificationCode)) { + if (!inputVerifyCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } @@ -494,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); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index bd6b123e..0a9722ae 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -87,8 +87,7 @@ security: refresh-token-time-to-live: 604800 # 无需认证的请求路径 ignore-urls: - - /api/v1/auth/login # 用户登录接口 - - /api/v1/auth/wechat-login # 微信登录接口 + - /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录) - /api/v1/auth/captcha # 验证码获取接口 - /api/v1/auth/refresh-token # 刷新令牌接口 - /ws/** # WebSocket接口 @@ -103,25 +102,25 @@ security: # 文件存储配置 oss: - # OSS 类型 (目前支持aliyun、minio) + # OSS 类型 (目前支持aliyun、minio、local) type: minio # MinIO 对象存储服务 minio: - # 服务Endpoint + # MinIO 服务地址 endpoint: http://localhost:9000 # 访问凭据 access-key: minioadmin # 凭据密钥 secret-key: minioadmin # 存储桶名称 - bucket-name: public - # (可选)自定义域名,如果配置了域名,生成的文件URL是域名格式,未配置则URL则是IP格式 (eg: https://oss.youlai.tech) + bucket-name: youlai + # (可选) 自定义域名:配置后,文件 URL 会使用该域名格式 custom-domain: # 阿里云OSS对象存储服务 aliyun: # 服务Endpoint endpoint: oss-cn-hangzhou.aliyuncs.com - # 访问凭据 + # 访问凭据` access-key-id: your-access-key-id # 凭据密钥 access-key-secret: your-access-key-secret @@ -140,19 +139,19 @@ sms: domain: dysmsapi.aliyuncs.com regionId: cn-shanghai signName: 有来技术 - templateCodes: - # 注册(预留) + templates: + # 注册短信验证码模板 register: SMS_22xxx771 - # 登录(预留) + # 登录短信验证码模板 login: SMS_22xxx772 - # 修改密码 - changePassword: SMS_22xxx773 + # 修改手机号短信验证码模板 + change-mobile: SMS_22xxx773 # springdoc配置: https://springdoc.org/properties.html springdoc: swagger-ui: path: /swagger-ui.html - operationsSorter: alpha + operations-sorter: alpha tags-sorter: alpha api-docs: path: /v3/api-docs @@ -195,32 +194,32 @@ xxl: # 验证码配置 captcha: - # 验证码类型 circle-圆圈干扰验证码|gif-Gif验证码|line-干扰线验证码|shear-扭曲干扰验证码 - type: circle - # 验证码宽度 - width: 120 - # 验证码高度 - height: 40 - # 验证码干扰元素个数 - interfere-count: 2 - # 文本透明度(0.0-1.0) - text-alpha: 0.8 - # 验证码字符配置 - code: - # 验证码字符类型 math-算术|random-随机字符 - type: math - # 验证码字符长度,type=算术时,表示运算位数(1:个位数运算 2:十位数运算);type=随机字符时,表示字符个数 - length: 1 - # 验证码字体 - font: - # 字体名称 Dialog|DialogInput|Monospaced|Serif|SansSerif - name: SansSerif - # 字体样式 0-普通|1-粗体|2-斜体 - weight: 1 - # 字体大小 - size: 24 - # 验证码有效期(秒) - expire-seconds: 120 + # 验证码类型 circle-圆圈干扰验证码|gif-Gif验证码|line-干扰线验证码|shear-扭曲干扰验证码 + type: circle + # 验证码宽度 + width: 120 + # 验证码高度 + height: 40 + # 验证码干扰元素个数 + interfere-count: 2 + # 文本透明度(0.0-1.0) + text-alpha: 0.8 + # 验证码字符配置 + code: + # 验证码字符类型 math-算术|random-随机字符 + type: math + # 验证码字符长度,type=算术时,表示运算位数(1:个位数运算 2:十位数运算);type=随机字符时,表示字符个数 + length: 1 + # 验证码字体 + font: + # 字体名称 Dialog|DialogInput|Monospaced|Serif|SansSerif + name: SansSerif + # 字体样式 0-普通|1-粗体|2-斜体 + weight: 1 + # 字体大小 + size: 24 + # 验证码有效期(秒) + expire-seconds: 120 # 微信小程配置 wx: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index bfe7a255..24379401 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -86,8 +86,7 @@ security: refresh-token-time-to-live: 604800 # 无需认证的请求路径 ignore-urls: - - /api/v1/auth/login # 用户登录接口 - - /api/v1/auth/wechat-login # 微信登录接口 + - /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录) - /api/v1/auth/captcha # 验证码获取接口 - /api/v1/auth/refresh-token # 刷新令牌接口 - /ws/** # WebSocket接口 @@ -138,13 +137,13 @@ sms: domain: dysmsapi.aliyuncs.com regionId: cn-shanghai signName: 有来技术 - templateCodes: - # 注册(预留) + templates: + # 注册短信验证码模板 register: SMS_22xxx771 - # 登录(预留) + # 登录短信验证码模板 login: SMS_22xxx772 - # 修改密码 - changePassword: SMS_22xxx773 + # 修改手机号短信验证码模板 + change-mobile: SMS_22xxx773 # springdoc配置: https://springdoc.org/properties.html springdoc: diff --git a/src/main/resources/mapper/system/UserMapper.xml b/src/main/resources/mapper/system/UserMapper.xml index 8bd7dc3e..0660dbcf 100644 --- a/src/main/resources/mapper/system/UserMapper.xml +++ b/src/main/resources/mapper/system/UserMapper.xml @@ -159,6 +159,24 @@ t1.openid = #{openid} AND t1.is_deleted = 0 + + + + +