refactor: 发送验证码代码重构优化;扩展Spring Security 支持短信验证码;
This commit is contained in:
@@ -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:";
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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<T> implements Serializable {
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode, String msg) {
|
||||
return result(resultCode.getCode(), msg, null);
|
||||
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(IResultCode resultCode, T data) {
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> redisTemplate;
|
||||
|
||||
|
||||
public SmsAuthenticationProvider(UserService userService, StringRedisTemplate redisTemplate) {
|
||||
public SmsAuthenticationProvider(UserService userService, RedisTemplate<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends GrantedAuthority> 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
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 加入黑名单
|
||||
|
||||
@@ -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;
|
||||
@@ -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<CaptchaInfo> getCaptcha() {
|
||||
CaptchaInfo captcha = authService.getCaptcha();
|
||||
return Result.success(captcha);
|
||||
}
|
||||
|
||||
@Operation(summary = "账号密码登录")
|
||||
@PostMapping("/login")
|
||||
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthToken> login(
|
||||
public Result<AuthenticationToken> 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<CaptchaResponse> 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<AuthToken> wechatLogin(
|
||||
public Result<AuthenticationToken> 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<AuthToken> 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<AuthenticationToken> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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字符串")
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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<String, Object> 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<String, String> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,29 @@ import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 短信类型枚举
|
||||
* <p>
|
||||
* value 值对应 application-*.yml 中的 sms.templates.* 配置
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.21.0
|
||||
*/
|
||||
@Getter
|
||||
public enum SmsTypeEnum implements IBaseEnum<String> {
|
||||
|
||||
/**
|
||||
* 注册短信验证码
|
||||
*/
|
||||
REGISTER("register", "注册短信验证码"),
|
||||
|
||||
/**
|
||||
* 登录短信验证码
|
||||
*/
|
||||
LOGIN("login", "登录短信验证码"),
|
||||
RESET_PASSWORD("reset-password", "重置密码短信验证码");
|
||||
|
||||
/**
|
||||
* 修改手机号短信验证码
|
||||
*/
|
||||
CHANGE_MOBILE("change-mobile", "修改手机号短信验证码");
|
||||
|
||||
private final String value;
|
||||
private final String label;
|
||||
|
||||
@@ -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<String, String> templateParams);
|
||||
}
|
||||
|
||||
@@ -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<String, String> 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);
|
||||
|
||||
@@ -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<RolePageVO> getRolePage(
|
||||
RolePageQuery queryParams
|
||||
RolePageQuery queryParams
|
||||
) {
|
||||
Page<RolePageVO> 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<Void> 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<Void> assignMenusToRole(
|
||||
@PathVariable Long roleId,
|
||||
@RequestBody List<Long> menuIds
|
||||
) {
|
||||
boolean result = roleService.assignMenusToRole(roleId, menuIds);
|
||||
return Result.judge(result);
|
||||
roleService.assignMenusToRole(roleId, menuIds);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Void> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ public interface RoleConverter {
|
||||
@Mapping(target = "value", source = "id"),
|
||||
@Mapping(target = "label", source = "name")
|
||||
})
|
||||
Option<Long> entity2Option(Role role);
|
||||
Option<Long> toOption(Role role);
|
||||
|
||||
List<Option<Long>> entities2Options(List<Role> roles);
|
||||
List<Option<Long>> toOptions(List<Role> roles);
|
||||
|
||||
Role toEntity(RoleForm roleForm);
|
||||
|
||||
|
||||
@@ -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<String> toOption(User entity);
|
||||
|
||||
List<Option<String>> toOptions(List<User> list);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.youlai.boot.system.enums;
|
||||
|
||||
/**
|
||||
* 联系方式类型
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
public enum ContactType {
|
||||
/**
|
||||
* 手机
|
||||
*/
|
||||
MOBILE,
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
EMAIL
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import lombok.Getter;
|
||||
/**
|
||||
* 字典编码枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2024/10/30
|
||||
*/
|
||||
@Getter
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Getter;
|
||||
/**
|
||||
* 菜单类型枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2022/4/23 9:36
|
||||
*/
|
||||
@Getter
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Getter;
|
||||
/**
|
||||
* 通告发布状态枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2024/10/14
|
||||
*/
|
||||
@Getter
|
||||
|
||||
@@ -7,8 +7,8 @@ import lombok.Getter;
|
||||
/**
|
||||
* 通知目标类型枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/14
|
||||
* @author Ray.Hao
|
||||
* @since 2024/10/14
|
||||
*/
|
||||
@Getter
|
||||
@Schema(enumAsRef = true)
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -64,10 +64,8 @@ public interface RoleService extends IService<Role> {
|
||||
* 批量删除角色
|
||||
*
|
||||
* @param ids 角色ID,多个使用英文逗号(,)分割
|
||||
* @return
|
||||
*/
|
||||
boolean deleteRoles(String ids);
|
||||
|
||||
void deleteRoles(String ids);
|
||||
|
||||
/**
|
||||
* 获取角色的菜单ID集合
|
||||
@@ -77,15 +75,13 @@ public interface RoleService extends IService<Role> {
|
||||
*/
|
||||
List<Long> getRoleMenuIds(Long roleId);
|
||||
|
||||
|
||||
/**
|
||||
* 修改角色的资源权限
|
||||
*
|
||||
* @param roleId
|
||||
* @param menuIds
|
||||
* @return
|
||||
* @param roleId 角色ID
|
||||
* @param menuIds 菜单ID集合
|
||||
*/
|
||||
boolean assignMenusToRole(Long roleId, List<Long> menuIds);
|
||||
void assignMenusToRole(Long roleId, List<Long> menuIds);
|
||||
|
||||
/**
|
||||
* 获取最大范围的数据权限
|
||||
|
||||
@@ -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<User> {
|
||||
/**
|
||||
* 用户分页列表
|
||||
*
|
||||
* @return
|
||||
* @return {@link IPage<UserPageVO>} 用户分页列表
|
||||
*/
|
||||
IPage<UserPageVO> 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<User> {
|
||||
* 新增用户
|
||||
*
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
* @return {@link Boolean} 是否新增成功
|
||||
*/
|
||||
boolean saveUser(UserForm userForm);
|
||||
|
||||
@@ -54,7 +51,7 @@ public interface UserService extends IService<User> {
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean updateUser(Long userId, UserForm userForm);
|
||||
|
||||
@@ -63,7 +60,7 @@ public interface UserService extends IService<User> {
|
||||
* 删除用户
|
||||
*
|
||||
* @param idsStr 用户ID,多个以英文逗号(,)分割
|
||||
* @return
|
||||
* @return {@link Boolean} 是否删除成功
|
||||
*/
|
||||
boolean deleteUsers(String idsStr);
|
||||
|
||||
@@ -82,7 +79,7 @@ public interface UserService extends IService<User> {
|
||||
* 获取导出用户列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return
|
||||
* @return {@link List<UserExportDTO>} 导出用户列表
|
||||
*/
|
||||
List<UserExportDTO> listExportUsers(UserPageQuery queryParams);
|
||||
|
||||
@@ -90,14 +87,14 @@ public interface UserService extends IService<User> {
|
||||
/**
|
||||
* 获取登录用户信息
|
||||
*
|
||||
* @return
|
||||
* @return {@link UserInfoVO} 登录用户信息
|
||||
*/
|
||||
UserInfoVO getCurrentUserInfo();
|
||||
|
||||
/**
|
||||
* 获取个人中心用户信息
|
||||
*
|
||||
* @return
|
||||
* @return {@link UserProfileVO} 个人中心用户信息
|
||||
*/
|
||||
UserProfileVO getUserProfile(Long userId);
|
||||
|
||||
@@ -105,7 +102,7 @@ public interface UserService extends IService<User> {
|
||||
* 修改个人中心用户信息
|
||||
*
|
||||
* @param formData 表单数据
|
||||
* @return
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean updateUserProfile(UserProfileForm formData);
|
||||
|
||||
@@ -114,43 +111,49 @@ public interface UserService extends IService<User> {
|
||||
*
|
||||
* @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<User> {
|
||||
* @return {@link UserAuthInfo}
|
||||
*/
|
||||
UserAuthInfo getUserAuthInfoByMobile(String mobile);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<RoleMapper, Role> implements Ro
|
||||
);
|
||||
|
||||
// 实体转换
|
||||
return roleConverter.entities2Options(roleList);
|
||||
return roleConverter.toOptions(roleList);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +158,9 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> 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<RoleMapper, Role> 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<Long> roleIds = Arrays.stream(ids.split(","))
|
||||
.map(Long::parseLong)
|
||||
@@ -195,7 +197,6 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
||||
roleMenuService.refreshRolePermsCache(role.getCode());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,15 +215,15 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param menuIds 菜单ID集合
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean assignMenusToRole(Long roleId, List<Long> menuIds) {
|
||||
public void assignMenusToRole(Long roleId, List<Long> menuIds) {
|
||||
Role role = this.getById(roleId);
|
||||
Assert.isTrue(role != null, "角色不存在");
|
||||
|
||||
if (role == null) {
|
||||
throw new RuntimeException("角色不存在");
|
||||
}
|
||||
// 删除角色菜单
|
||||
roleMenuService.remove(
|
||||
new LambdaQueryWrapper<RoleMenu>()
|
||||
@@ -239,8 +240,6 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
||||
|
||||
// 刷新角色的权限缓存
|
||||
roleMenuService.refreshRolePermsCache(role.getCode());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<UserMapper, User> 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<UserMapper, User> implements Us
|
||||
* 获取用户表单数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
* @return {@link UserForm} 用户表单数据
|
||||
*/
|
||||
@Override
|
||||
public UserForm getUserFormData(Long userId) {
|
||||
@@ -112,7 +106,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
* 新增用户
|
||||
*
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean saveUser(UserForm userForm) {
|
||||
@@ -144,7 +138,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userForm 用户表单对象
|
||||
* @return
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -206,7 +200,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 openid 获取用户认证信息
|
||||
*
|
||||
@@ -225,7 +218,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据手机号获取用户认证信息
|
||||
*
|
||||
@@ -235,8 +227,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
@Override
|
||||
public UserAuthInfo getUserAuthInfoByMobile(String mobile) {
|
||||
UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfoByMobile(mobile);
|
||||
|
||||
return null;
|
||||
if (userAuthInfo != null) {
|
||||
Set<String> roles = userAuthInfo.getRoles();
|
||||
// 获取最大范围的数据权限
|
||||
Integer dataScope = roleService.getMaximumDataScope(roles);
|
||||
userAuthInfo.setDataScope(dataScope);
|
||||
}
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +316,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
* 获取个人中心用户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return
|
||||
* @return {@link UserProfileVO} 个人中心用户信息
|
||||
*/
|
||||
@Override
|
||||
public UserProfileVO getUserProfile(Long userId) {
|
||||
@@ -331,7 +328,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
* 修改个人中心用户信息
|
||||
*
|
||||
* @param formData 表单数据
|
||||
* @return
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean updateUserProfile(UserProfileForm formData) {
|
||||
@@ -347,10 +344,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> 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<UserMapper, User> 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<UserMapper, User> 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<String, String> 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<UserMapper, User> 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<UserMapper, User> 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<UserMapper, User> 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<UserMapper, User> 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<UserMapper, User> implements Us
|
||||
*/
|
||||
@Override
|
||||
public List<Option<String>> listUserOptions() {
|
||||
List<User> 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<User> list = this.list(new LambdaQueryWrapper<User>()
|
||||
.eq(User::getStatus, 1)
|
||||
);
|
||||
return userConverter.toOptions(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user