refactor: 微信小程序授权登录重构

This commit is contained in:
Ray.Hao
2025-06-01 17:32:46 +08:00
parent 3d3e7f8c92
commit 194f3e7ca8
12 changed files with 543 additions and 71 deletions

View File

@@ -8,7 +8,8 @@ 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.sms.SmsAuthenticationProvider;
import com.youlai.boot.core.security.extension.wechat.WechatAuthenticationProvider;
import com.youlai.boot.core.security.extension.wx.WxMiniAppCodeAuthenticationProvider;
import com.youlai.boot.core.security.extension.wx.WxMiniAppPhoneAuthenticationProvider;
import com.youlai.boot.core.security.filter.CaptchaValidationFilter;
import com.youlai.boot.core.security.filter.TokenAuthenticationFilter;
import com.youlai.boot.core.security.token.TokenManager;
@@ -125,13 +126,20 @@ public class SecurityConfig {
}
/**
* 微信认证 Provider
* 微信小程序Code认证Provider
*/
@Bean
public WechatAuthenticationProvider weChatAuthenticationProvider() {
return new WechatAuthenticationProvider(userService, wxMaService);
public WxMiniAppCodeAuthenticationProvider wxMiniAppCodeAuthenticationProvider() {
return new WxMiniAppCodeAuthenticationProvider(userService, wxMaService);
}
/**
* 微信小程序手机号认证Provider
*/
@Bean
public WxMiniAppPhoneAuthenticationProvider wxMiniAppPhoneAuthenticationProvider() {
return new WxMiniAppPhoneAuthenticationProvider(userService, wxMaService);
}
/**
* 短信验证码认证 Provider
@@ -147,12 +155,14 @@ public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager(
DaoAuthenticationProvider daoAuthenticationProvider,
WechatAuthenticationProvider weChatAuthenticationProvider,
WxMiniAppCodeAuthenticationProvider wxMiniAppCodeAuthenticationProvider,
WxMiniAppPhoneAuthenticationProvider wxMiniAppPhoneAuthenticationProvider,
SmsAuthenticationProvider smsAuthenticationProvider
) {
return new ProviderManager(
daoAuthenticationProvider,
weChatAuthenticationProvider,
wxMiniAppCodeAuthenticationProvider,
wxMiniAppPhoneAuthenticationProvider,
smsAuthenticationProvider
);
}

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.core.security.extension.wechat;
package com.youlai.boot.core.security.extension.wx;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
@@ -18,20 +18,19 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 微信认证 Provider
* 微信小程序Code认证Provider
*
* @author Ray.Hao
* @since 2.17.0
* @author 有来技术团队
* @since 2.0.0
*/
@Slf4j
public class WechatAuthenticationProvider implements AuthenticationProvider {
public class WxMiniAppCodeAuthenticationProvider implements AuthenticationProvider {
private final UserService userService;
private final WxMaService wxMaService;
public WechatAuthenticationProvider(UserService userService, WxMaService wxMaService) {
public WxMiniAppCodeAuthenticationProvider(UserService userService, WxMaService wxMaService) {
this.userService = userService;
this.wxMaService = wxMaService;
}
@@ -66,7 +65,7 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
UserAuthCredentials userAuthCredentials = userService.getAuthCredentialsByOpenId(openId);
if (userAuthCredentials == null) {
// TODO: 用户不存在则注册这里需要获取用户手机号并与现有用户绑定
// 用户不存在则注册
userService.registerOrBindWechatUser(openId);
// 再次查询用户信息确保用户注册成功
@@ -80,13 +79,12 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
if (ObjectUtil.notEqual(userAuthCredentials.getStatus(), 1)) {
throw new DisabledException("用户已被禁用");
}
// 这里因为已经根据 code 从微信小程序获取到 openid 不需要再经过系统认证所以直接生成
// 构建认证后的用户详情信息
SysUserDetails userDetails = new SysUserDetails(userAuthCredentials);
// 创建已认证的 WeChatAuthenticationToken
return WechatAuthenticationToken.authenticated(
// 创建已认证的Token
return WxMiniAppCodeAuthenticationToken.authenticated(
userDetails,
userDetails.getAuthorities()
);
@@ -94,6 +92,6 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
@Override
public boolean supports(Class<?> authentication) {
return WechatAuthenticationToken.class.isAssignableFrom(authentication);
return WxMiniAppCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.youlai.boot.core.security.extension.wechat;
package com.youlai.boot.core.security.extension.wx;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@@ -7,22 +7,22 @@ import java.io.Serial;
import java.util.Collection;
/**
* 微信认证 Token
* 微信小程序Code认证Token
*
* @author Ray.Hao
* @since 2024/12/2
* @author 有来技术团队
* @since 2.0.0
*/
public class WechatAuthenticationToken extends AbstractAuthenticationToken {
public class WxMiniAppCodeAuthenticationToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 621L;
private final Object principal;
/**
* 微信认证 Token (未认证)
* 微信小程序Code认证Token (未认证)
*
* @param principal 微信用户信息
* @param principal 微信code
*/
public WechatAuthenticationToken(Object principal) {
public WxMiniAppCodeAuthenticationToken(Object principal) {
// 没有授权信息时设置为 null
super(null);
this.principal = principal;
@@ -32,12 +32,12 @@ public class WechatAuthenticationToken extends AbstractAuthenticationToken {
/**
* 微信认证 Token (已认证)
* 微信小程序Code认证Token (已认证)
*
* @param principal 微信用户信息
* @param authorities 授权信息
*/
public WechatAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
public WxMiniAppCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// 认证通过
@@ -50,10 +50,10 @@ public class WechatAuthenticationToken extends AbstractAuthenticationToken {
*
* @param principal 微信用户信息
* @param authorities 授权信息
* @return
* @return 已认证的Token
*/
public static WechatAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new WechatAuthenticationToken(principal, authorities);
public static WxMiniAppCodeAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new WxMiniAppCodeAuthenticationToken(principal, authorities);
}
@Override
@@ -66,4 +66,4 @@ public class WechatAuthenticationToken extends AbstractAuthenticationToken {
public Object getPrincipal() {
return this.principal;
}
}
}

View File

@@ -0,0 +1,114 @@
package com.youlai.boot.core.security.extension.wx;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.core.security.model.SysUserDetails;
import com.youlai.boot.core.security.model.UserAuthCredentials;
import com.youlai.boot.system.service.UserService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.CredentialsExpiredException;
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 有来技术团队
* @since 2.0.0
*/
@Slf4j
public class WxMiniAppPhoneAuthenticationProvider implements AuthenticationProvider {
private final UserService userService;
private final WxMaService wxMaService;
public WxMiniAppPhoneAuthenticationProvider(UserService userService, WxMaService wxMaService) {
this.userService = userService;
this.wxMaService = wxMaService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxMiniAppPhoneAuthenticationToken authenticationToken = (WxMiniAppPhoneAuthenticationToken) authentication;
String code = (String) authenticationToken.getPrincipal();
String encryptedData = authenticationToken.getEncryptedData();
String iv = authenticationToken.getIv();
// 1. 通过code获取session_key
WxMaJscode2SessionResult sessionInfo;
try {
sessionInfo = wxMaService.getUserService().getSessionInfo(code);
} catch (WxErrorException e) {
log.error("获取微信session_key失败", e);
throw new CredentialsExpiredException("微信登录code无效或已过期");
}
String sessionKey = sessionInfo.getSessionKey();
String openId = sessionInfo.getOpenid();
if (StrUtil.isBlank(sessionKey) || StrUtil.isBlank(openId)) {
throw new CredentialsExpiredException("获取微信会话信息失败");
}
// 2. 解密手机号信息
WxMaPhoneNumberInfo phoneNumberInfo;
try {
if (StrUtil.isNotBlank(encryptedData) && StrUtil.isNotBlank(iv)) {
phoneNumberInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
} else {
throw new IllegalArgumentException("缺少手机号加密数据");
}
} catch (Exception e) {
log.error("解密微信手机号失败", e);
throw new CredentialsExpiredException("解密手机号信息失败");
}
if (phoneNumberInfo == null || StrUtil.isBlank(phoneNumberInfo.getPhoneNumber())) {
throw new CredentialsExpiredException("获取手机号失败");
}
String phoneNumber = phoneNumberInfo.getPhoneNumber();
// 3. 根据手机号查询用户,不存在则创建新用户
UserAuthCredentials userAuthCredentials = userService.getAuthCredentialsByMobile(phoneNumber);
if (userAuthCredentials == null) {
// 用户不存在,注册新用户
boolean registered = userService.registerUserByMobileAndOpenId(phoneNumber, openId);
if (!registered) {
throw new UsernameNotFoundException("用户注册失败");
}
// 重新获取用户信息
userAuthCredentials = userService.getAuthCredentialsByMobile(phoneNumber);
} else {
// 用户存在绑定openId如果未绑定
userService.bindUserOpenId(userAuthCredentials.getUserId(), openId);
}
// 4. 检查用户状态
if (ObjectUtil.notEqual(userAuthCredentials.getStatus(), 1)) {
throw new DisabledException("用户已被禁用");
}
// 5. 构建认证后的用户详情
SysUserDetails userDetails = new SysUserDetails(userAuthCredentials);
// 6. 创建已认证的Token
return WxMiniAppPhoneAuthenticationToken.authenticated(
userDetails,
userDetails.getAuthorities()
);
}
@Override
public boolean supports(Class<?> authentication) {
return WxMiniAppPhoneAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -0,0 +1,89 @@
package com.youlai.boot.core.security.extension.wx;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.io.Serial;
import java.util.Collection;
/**
* 微信小程序手机号认证Token
*
* @author 有来技术团队
* @since 2.0.0
*/
public class WxMiniAppPhoneAuthenticationToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 622L;
private final Object principal; // code
private String encryptedData;
private String iv;
/**
* 微信小程序手机号认证Token (未认证)
*
* @param code 微信登录code
* @param encryptedData 加密数据
* @param iv 初始向量
*/
public WxMiniAppPhoneAuthenticationToken(String code, String encryptedData, String iv) {
super(null);
this.principal = code;
this.encryptedData = encryptedData;
this.iv = iv;
this.setAuthenticated(false);
}
/**
* 微信小程序手机号认证Token (已认证)
*
* @param principal 用户信息
* @param authorities 授权信息
*/
public WxMiniAppPhoneAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
/**
* 认证通过
*
* @param principal 用户信息
* @param authorities 授权信息
* @return 认证通过的Token
*/
public static WxMiniAppPhoneAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new WxMiniAppPhoneAuthenticationToken(principal, authorities);
}
@Override
public Object getCredentials() {
// 微信小程序手机号认证不需要密码
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
/**
* 获取加密数据
*
* @return 加密数据
*/
public String getEncryptedData() {
return encryptedData;
}
/**
* 获取初始向量
*
* @return 初始向量
*/
public String getIv() {
return iv;
}
}

View File

@@ -4,15 +4,19 @@ import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
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;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 认证控制层
*
@@ -92,4 +96,18 @@ public class AuthController {
AuthenticationToken loginResult = authService.loginBySms(mobile, code);
return Result.success(loginResult);
}
@Operation(summary = "微信小程序Code登录")
@PostMapping("/wx/miniapp/code-login")
public Result<AuthenticationToken> loginByWxMiniAppCode(@RequestBody @Valid WxMiniAppCodeLoginDTO loginDTO) {
AuthenticationToken token = authService.loginByWxMiniAppCode(loginDTO);
return Result.success(token);
}
@Operation(summary = "微信小程序手机号登录")
@PostMapping("/wx/miniapp/phone-login")
public Result<AuthenticationToken> loginByWxMiniAppPhone(@RequestBody @Valid WxMiniAppPhoneLoginDTO loginDTO) {
AuthenticationToken token = authService.loginByWxMiniAppPhone(loginDTO);
return Result.success(token);
}
}

View File

@@ -0,0 +1,22 @@
package com.youlai.boot.shared.auth.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
/**
* 微信小程序Code登录请求参数
*
* @author 有来技术团队
* @since 2.0.0
*/
@Schema(description = "微信小程序Code登录请求参数")
@Data
public class WxMiniAppCodeLoginDTO {
@Schema(description = "微信小程序登录时获取的code", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "code不能为空")
private String code;
}

View File

@@ -0,0 +1,28 @@
package com.youlai.boot.shared.auth.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
/**
* 微信小程序手机号登录请求参数
*
* @author 有来技术团队
* @since 2.0.0
*/
@Schema(description = "微信小程序手机号登录请求参数")
@Data
public class WxMiniAppPhoneLoginDTO {
@Schema(description = "微信小程序登录时获取的code", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "code不能为空")
private String code;
@Schema(description = "包括敏感数据在内的完整用户信息的加密数据")
private String encryptedData;
@Schema(description = "加密算法的初始向量")
private String iv;
}

View File

@@ -2,6 +2,8 @@ package com.youlai.boot.shared.auth.service;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
/**
* 认证服务接口
@@ -48,6 +50,22 @@ public interface AuthService {
*/
AuthenticationToken loginByWechat(String code);
/**
* 微信小程序Code登录
*
* @param loginDTO 登录参数
* @return 访问令牌
*/
AuthenticationToken loginByWxMiniAppCode(WxMiniAppCodeLoginDTO loginDTO);
/**
* 微信小程序手机号登录
*
* @param loginDTO 登录参数
* @return 访问令牌
*/
AuthenticationToken loginByWxMiniAppPhone(WxMiniAppPhoneLoginDTO loginDTO);
/**
* 发送短信验证码
*

View File

@@ -11,11 +11,12 @@ 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.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.AuthenticationToken;
import com.youlai.boot.shared.auth.model.CaptchaInfo;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.shared.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.shared.auth.service.AuthService;
import com.youlai.boot.core.security.token.TokenManager;
import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
@@ -28,6 +29,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import com.youlai.boot.core.security.extension.wx.WxMiniAppCodeAuthenticationToken;
import com.youlai.boot.core.security.extension.wx.WxMiniAppPhoneAuthenticationToken;
import java.awt.*;
import java.util.HashMap;
@@ -87,16 +90,16 @@ public class AuthServiceImpl implements AuthService {
@Override
public AuthenticationToken loginByWechat(String code) {
// 1. 创建用户微信认证的令牌(未认证)
WechatAuthenticationToken wechatAuthenticationToken = new WechatAuthenticationToken(code);
WxMiniAppCodeAuthenticationToken authenticationToken = new WxMiniAppCodeAuthenticationToken(code);
// 2. 执行认证(认证中)
Authentication authentication = authenticationManager.authenticate(wechatAuthenticationToken);
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
AuthenticationToken authenticationToken = tokenManager.generateToken(authentication);
AuthenticationToken token = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authenticationToken;
return token;
}
/**
@@ -227,5 +230,50 @@ public class AuthServiceImpl implements AuthService {
return tokenManager.refreshToken(refreshToken);
}
/**
* 微信小程序Code登录
*
* @param loginDTO 登录参数
* @return 访问令牌
*/
@Override
public AuthenticationToken loginByWxMiniAppCode(WxMiniAppCodeLoginDTO loginDTO) {
// 1. 创建微信小程序认证令牌(未认证)
WxMiniAppCodeAuthenticationToken authenticationToken = new WxMiniAppCodeAuthenticationToken(loginDTO.getCode());
// 2. 执行认证(认证中)
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
AuthenticationToken token = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return token;
}
/**
* 微信小程序手机号登录
*
* @param loginDTO 登录参数
* @return 访问令牌
*/
@Override
public AuthenticationToken loginByWxMiniAppPhone(WxMiniAppPhoneLoginDTO loginDTO) {
// 创建微信小程序手机号认证Token
WxMiniAppPhoneAuthenticationToken authenticationToken = new WxMiniAppPhoneAuthenticationToken(
loginDTO.getCode(),
loginDTO.getEncryptedData(),
loginDTO.getIv()
);
// 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 认证成功后生成JWT令牌并存入Security上下文
AuthenticationToken token = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return token;
}
}

View File

@@ -165,18 +165,18 @@ public interface UserService extends IService<User> {
/**
* 根据 openid 获取用户认证信息
*
* @param username 用户名
* @param openId 用户名
* @return {@link UserAuthCredentials}
*/
UserAuthCredentials getAuthCredentialsByOpenId(String username);
UserAuthCredentials getAuthCredentialsByOpenId(String openId);
/**
* 根据微信 OpenID 注册或绑定用户
*
* @param openId 微信 OpenID
*/
void registerOrBindWechatUser(String openId);
boolean registerOrBindWechatUser(String openId);
/**
* 根据手机号获取用户认证信息
@@ -186,5 +186,22 @@ public interface UserService extends IService<User> {
*/
UserAuthCredentials getAuthCredentialsByMobile(String mobile);
/**
* 根据手机号和OpenID注册用户
*
* @param mobile 手机号
* @param openId 微信OpenID
* @return 是否成功
*/
boolean registerUserByMobileAndOpenId(String mobile, String openId);
/**
* 绑定用户微信OpenID
*
* @param userId 用户ID
* @param openId 微信OpenID
* @return 是否成功
*/
boolean bindUserOpenId(Long userId, String openId);
}

View File

@@ -34,11 +34,13 @@ import com.youlai.boot.system.model.vo.UserPageVO;
import com.youlai.boot.system.model.vo.UserProfileVO;
import com.youlai.boot.system.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -51,6 +53,7 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private final PasswordEncoder passwordEncoder;
@@ -207,14 +210,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
}
/**
* 根据 openid 获取用户认证信息
* 根据OpenID获取用户认证信息
*
* @param openid 微信 OpenId
* @return {@link UserAuthCredentials}
* @param openId 微信OpenID
* @return 用户认证信息
*/
@Override
public UserAuthCredentials getAuthCredentialsByOpenId(String openid) {
UserAuthCredentials userAuthCredentials = this.baseMapper.getAuthCredentialsByOpenId(openid);
public UserAuthCredentials getAuthCredentialsByOpenId(String openId) {
if (StrUtil.isBlank(openId)) {
return null;
}
UserAuthCredentials userAuthCredentials = this.baseMapper.getAuthCredentialsByOpenId(openId);
if (userAuthCredentials != null) {
Set<String> roles = userAuthCredentials.getRoles();
// 获取最大范围的数据权限
@@ -225,13 +231,16 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
}
/**
* 根据手机号获取用户认证凭证信息
* 根据手机号获取用户认证信息
*
* @param mobile 手机号
* @return {@link UserAuthCredentials}
* @return 用户认证信息
*/
@Override
public UserAuthCredentials getAuthCredentialsByMobile(String mobile) {
if (StrUtil.isBlank(mobile)) {
return null;
}
UserAuthCredentials userAuthCredentials = this.baseMapper.getAuthCredentialsByMobile(mobile);
if (userAuthCredentials != null) {
Set<String> roles = userAuthCredentials.getRoles();
@@ -242,34 +251,135 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return userAuthCredentials;
}
/**
* 根据微信 OpenID 注册或绑定用户
* <p>
* TODO 根据手机号绑定用户
* 注册或绑定微信用户
*
* @param openId 微信 OpenID
* @param openId 微信OpenID
* @return 是否成功
*/
@Override
public void registerOrBindWechatUser(String openId) {
User user = this.getOne(
new LambdaQueryWrapper<User>().eq(User::getOpenid, openId)
);
if (user == null) {
user = new User();
user.setNickname("微信用户"); // 默认昵称
user.setUsername(openId); // TODO 后续替换为手机号
user.setOpenid(openId);
user.setGender(0); // 保密
user.setUpdateBy(SecurityUtils.getUserId());
user.setPassword(SystemConstants.DEFAULT_PASSWORD);
this.save(user);
// 为了默认系统管理员角色,这里按需调整,实际情况绑定已存在的系统用户,另一种情况是给默认游客角色,然后由系统管理员设置用户的角色
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
userRole.setRoleId(1L); // TODO 系统管理员
userRoleService.save(userRole);
@Transactional(rollbackFor = Exception.class)
public boolean registerOrBindWechatUser(String openId) {
if (StrUtil.isBlank(openId)) {
return false;
}
// 查询是否已存在该openId的用户
User existUser = this.getOne(
new LambdaQueryWrapper<User>()
.eq(User::getOpenid, openId)
);
if (existUser != null) {
// 用户已存在,不需要注册
return true;
}
// 创建新用户
User newUser = new User();
newUser.setNickname("微信用户"); // 默认昵称
newUser.setUsername(openId); // TODO 后续替换为手机号
newUser.setOpenid(openId);
newUser.setGender(0); // 保密
newUser.setUpdateBy(SecurityUtils.getUserId());
newUser.setPassword(SystemConstants.DEFAULT_PASSWORD);
newUser.setCreateTime(LocalDateTime.now());
newUser.setUpdateTime(LocalDateTime.now());
this.save(newUser);
// 为了默认系统管理员角色,这里按需调整,实际情况绑定已存在的系统用户,另一种情况是给默认游客角色,然后由系统管理员设置用户的角色
UserRole userRole = new UserRole();
userRole.setUserId(newUser.getId());
userRole.setRoleId(1L); // TODO 系统管理员
userRoleService.save(userRole);
return true;
}
/**
* 根据手机号和OpenID注册用户
*
* @param mobile 手机号
* @param openId 微信OpenID
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean registerUserByMobileAndOpenId(String mobile, String openId) {
if (StrUtil.isBlank(mobile) || StrUtil.isBlank(openId)) {
return false;
}
// 先查询是否已存在手机号对应的用户
User existingUser = this.getOne(
new LambdaQueryWrapper<User>()
.eq(User::getMobile, mobile)
);
if (existingUser != null) {
// 如果存在用户但没绑定openId则绑定openId
if (StrUtil.isBlank(existingUser.getOpenid())) {
return bindUserOpenId(existingUser.getId(), openId);
}
// 如果已经绑定了其他openId则判断是否需要更新
else if (!openId.equals(existingUser.getOpenid())) {
return bindUserOpenId(existingUser.getId(), openId);
}
// 如果已经绑定了相同的openId则不需要任何操作
return true;
}
// 不存在用户,创建新用户
User newUser = new User();
newUser.setMobile(mobile);
newUser.setOpenid(openId);
newUser.setUsername(mobile); // 使用手机号作为用户名
newUser.setNickname("微信用户_" + mobile.substring(mobile.length() - 4)); // 使用手机号后4位作为昵称
newUser.setPassword(SystemConstants.DEFAULT_PASSWORD); // 使用加密的openId作为初始密码
newUser.setGender(0); // 保密
newUser.setCreateTime(LocalDateTime.now());
newUser.setUpdateTime(LocalDateTime.now());
this.save(newUser);
// 为了默认系统管理员角色,这里按需调整,实际情况绑定已存在的系统用户,另一种情况是给默认游客角色,然后由系统管理员设置用户的角色
UserRole userRole = new UserRole();
userRole.setUserId(newUser.getId());
userRole.setRoleId(1L); // TODO 系统管理员
userRoleService.save(userRole);
return true;
}
/**
* 绑定用户微信OpenID
*
* @param userId 用户ID
* @param openId 微信OpenID
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean bindUserOpenId(Long userId, String openId) {
if (userId == null || StrUtil.isBlank(openId)) {
return false;
}
// 检查是否已有其他用户绑定了此openId
User existingUser = this.getOne(
new LambdaQueryWrapper<User>()
.eq(User::getOpenid, openId)
.ne(User::getId, userId)
);
if (existingUser != null) {
log.warn("OpenID {} 已被用户 {} 绑定,无法为用户 {} 绑定", openId, existingUser.getId(), userId);
return false;
}
// 更新用户openId
boolean updated = this.update(
new LambdaUpdateWrapper<User>()
.eq(User::getId, userId)
.set(User::getOpenid, openId)
.set(User::getUpdateTime, LocalDateTime.now())
);
return updated ;
}
/**