feat: 重构微信小程序登录模块
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.youlai.boot.auth.controller;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.auth.model.vo.WechatLoginResult;
|
||||
import com.youlai.boot.auth.model.dto.LoginRequest;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
@@ -41,10 +40,8 @@ public class AuthController {
|
||||
@Operation(summary = "账号密码登录")
|
||||
@PostMapping("/login")
|
||||
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<?> login(@RequestBody @Valid LoginRequest request) {
|
||||
String username = request.getUsername();
|
||||
String password = request.getPassword();
|
||||
AuthenticationToken authenticationToken = authService.login(username, password);
|
||||
public Result<AuthenticationToken> login(@RequestBody @Valid LoginRequest request) {
|
||||
AuthenticationToken authenticationToken = authService.login(request.getUsername(), request.getPassword());
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
@@ -53,7 +50,7 @@ public class AuthController {
|
||||
@Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> loginBySms(
|
||||
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile,
|
||||
@Parameter(description = "验证码", example = "1234") @RequestParam String code
|
||||
@Parameter(description = "验证码", example = "123456") @RequestParam String code
|
||||
) {
|
||||
AuthenticationToken loginResult = authService.loginBySms(mobile, code);
|
||||
return Result.success(loginResult);
|
||||
@@ -61,57 +58,24 @@ public class AuthController {
|
||||
|
||||
@Operation(summary = "发送登录短信验证码")
|
||||
@PostMapping("/sms/code")
|
||||
public Result<Void> sendLoginVerifyCode(
|
||||
public Result<Void> sendSmsCode(
|
||||
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile
|
||||
) {
|
||||
authService.sendSmsLoginCode(mobile);
|
||||
authService.sendSmsCode(mobile);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "微信小程序登录(个人小程序)")
|
||||
@PostMapping("/wechat-miniapp/login")
|
||||
@Log(value = "微信小程序登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<WechatLoginResult> loginByWechatMini(
|
||||
@Parameter(description = "微信登录code", example = "xxx") @RequestParam String code
|
||||
) {
|
||||
WechatLoginResult result = authService.loginByWechatMini(code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "微信小程序一键登录(企业小程序)")
|
||||
@PostMapping("/wechat-miniapp/phone-login")
|
||||
@Log(value = "微信小程序一键登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> loginByWechatMiniWithPhone(
|
||||
@Parameter(description = "微信登录code", example = "xxx") @RequestParam String loginCode,
|
||||
@Parameter(description = "手机号授权code", example = "xxx") @RequestParam String phoneCode
|
||||
) {
|
||||
AuthenticationToken result = authService.wechatMiniLoginWithPhone(loginCode, phoneCode);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "微信小程序绑定手机号")
|
||||
@PostMapping("/wechat-miniapp/bind-mobile")
|
||||
@Log(value = "微信小程序绑定手机号", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> bindMobileForWechatMini(
|
||||
@Parameter(description = "微信openid") @RequestParam String openid,
|
||||
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile,
|
||||
@Parameter(description = "短信验证码", example = "1234") @RequestParam String code
|
||||
) {
|
||||
AuthenticationToken result = authService.bindMobileForWechatMini(openid, mobile, code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "退出登录")
|
||||
@DeleteMapping("/logout")
|
||||
@Log(value = "退出登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<?> logout() {
|
||||
public Result<Void> logout() {
|
||||
authService.logout();
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新令牌")
|
||||
@PostMapping("/refresh-token")
|
||||
public Result<?> refreshToken(
|
||||
public Result<AuthenticationToken> refreshToken(
|
||||
@Parameter(description = "刷新令牌", example = "xxx.xxx.xxx") @RequestParam String refreshToken
|
||||
) {
|
||||
AuthenticationToken authenticationToken = authService.refreshToken(refreshToken);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.youlai.boot.auth.controller;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.auth.service.WechatMiniappAuthService;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 微信小程序认证控制层
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Tag(name = "13.微信小程序认证")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/wechat/miniapp/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WechatMiniappAuthController {
|
||||
|
||||
private final WechatMiniappAuthService wechatMiniappAuthService;
|
||||
|
||||
/**
|
||||
* 静默登录
|
||||
* <p>
|
||||
* 适用场景:个人小程序、无需手机号的登录场景
|
||||
* <ul>
|
||||
* <li>已绑定手机号的用户:直接返回 token,登录成功</li>
|
||||
* <li>未绑定手机号的用户:返回 openid,需调用绑定手机号接口</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Operation(summary = "静默登录", description = "通过微信 code 登录,已绑定用户直接返回 token,未绑定用户返回 openid 需绑定手机号")
|
||||
@PostMapping("/silent-login")
|
||||
@Log(value = "微信小程序静默登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<WechatMiniappLoginResult> silentLogin(
|
||||
@Parameter(description = "微信登录凭证(wx.login 获取)", required = true, example = "0xxx")
|
||||
@RequestParam String code
|
||||
) {
|
||||
WechatMiniappLoginResult result = wechatMiniappAuthService.silentLogin(code);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号快捷登录
|
||||
* <p>
|
||||
* 适用场景:企业认证小程序(已开通手机号快捷登录权限)
|
||||
* <p>
|
||||
* 一步完成登录,无需绑定流程,自动创建新用户
|
||||
*/
|
||||
@Operation(summary = "手机号快捷登录", description = "同时使用微信 code 和手机号授权 code 登录,适用于企业认证小程序")
|
||||
@PostMapping("/phone-login")
|
||||
@Log(value = "微信小程序手机号快捷登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> phoneLogin(
|
||||
@Parameter(description = "微信登录凭证(wx.login 获取)", required = true, example = "0xxx")
|
||||
@RequestParam String loginCode,
|
||||
@Parameter(description = "手机号授权凭证(getPhoneNumber 事件获取)", required = true, example = "0xxx")
|
||||
@RequestParam String phoneCode
|
||||
) {
|
||||
AuthenticationToken result = wechatMiniappAuthService.phoneLogin(loginCode, phoneCode);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
* <p>
|
||||
* 适用场景:静默登录后未绑定手机号的用户
|
||||
* <p>
|
||||
* 绑定成功后自动完成登录
|
||||
*/
|
||||
@Operation(summary = "绑定手机号", description = "为静默登录用户绑定手机号,绑定成功后自动登录")
|
||||
@PostMapping("/bind-mobile")
|
||||
@Log(value = "微信小程序绑定手机号", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> bindMobile(
|
||||
@Parameter(description = "微信用户唯一标识(静默登录返回)", required = true)
|
||||
@RequestParam String openid,
|
||||
@Parameter(description = "手机号码", required = true, example = "18812345678")
|
||||
@RequestParam String mobile,
|
||||
@Parameter(description = "短信验证码", required = true, example = "123456")
|
||||
@RequestParam String smsCode
|
||||
) {
|
||||
AuthenticationToken result = wechatMiniappAuthService.bindMobile(openid, mobile, smsCode);
|
||||
return Result.success(result);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class LoginRequest {
|
||||
@Schema(description = "验证码缓存ID", example = "captcha_id_123")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码", example = "1234")
|
||||
@Schema(description = "验证码", example = "123456")
|
||||
private String captchaCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信小程序登录结果
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "微信小程序登录结果")
|
||||
public class WechatLoginResult {
|
||||
public class WechatMiniappLoginResult {
|
||||
|
||||
@Schema(description = "是否新用户")
|
||||
private Boolean isNewUser;
|
||||
@@ -30,13 +33,13 @@ public class WechatLoginResult {
|
||||
private String tokenType;
|
||||
|
||||
@Schema(description = "过期时间(秒)")
|
||||
private Long expiresIn;
|
||||
private Integer expiresIn;
|
||||
|
||||
/**
|
||||
* 创建需要绑定手机号的结果
|
||||
*/
|
||||
public static WechatLoginResult needBindMobile(String openid) {
|
||||
WechatLoginResult result = new WechatLoginResult();
|
||||
public static WechatMiniappLoginResult needBindMobile(String openid) {
|
||||
WechatMiniappLoginResult result = new WechatMiniappLoginResult();
|
||||
result.setIsNewUser(true);
|
||||
result.setNeedBindMobile(true);
|
||||
result.setOpenid(openid);
|
||||
@@ -46,8 +49,8 @@ public class WechatLoginResult {
|
||||
/**
|
||||
* 创建登录成功的结果
|
||||
*/
|
||||
public static WechatLoginResult success(AuthenticationToken token) {
|
||||
WechatLoginResult result = new WechatLoginResult();
|
||||
public static WechatMiniappLoginResult success(AuthenticationToken token) {
|
||||
WechatMiniappLoginResult result = new WechatMiniappLoginResult();
|
||||
result.setIsNewUser(false);
|
||||
result.setNeedBindMobile(false);
|
||||
result.setAccessToken(token.getAccessToken());
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.youlai.boot.auth.service;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.auth.model.vo.WechatLoginResult;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
|
||||
/**
|
||||
@@ -13,16 +12,32 @@ import com.youlai.boot.security.model.AuthenticationToken;
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* 账号密码登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return 登录结果
|
||||
* @return 认证令牌
|
||||
*/
|
||||
AuthenticationToken login(String username, String password);
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* 短信验证码登录
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param code 验证码
|
||||
* @return 认证令牌
|
||||
*/
|
||||
AuthenticationToken loginBySms(String mobile, String code);
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*
|
||||
* @param mobile 手机号
|
||||
*/
|
||||
void sendSmsCode(String mobile);
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
void logout();
|
||||
|
||||
@@ -37,50 +52,7 @@ public interface AuthService {
|
||||
* 刷新令牌
|
||||
*
|
||||
* @param refreshToken 刷新令牌
|
||||
* @return 登录结果
|
||||
* @return 认证令牌
|
||||
*/
|
||||
AuthenticationToken refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*
|
||||
* @param mobile 手机号
|
||||
*/
|
||||
void sendSmsLoginCode(String mobile);
|
||||
|
||||
/**
|
||||
* 短信验证码登录
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param code 验证码
|
||||
* @return 登录结果
|
||||
*/
|
||||
AuthenticationToken loginBySms(String mobile, String code);
|
||||
|
||||
/**
|
||||
* 微信小程序登录(个人小程序)
|
||||
*
|
||||
* @param code 微信登录code
|
||||
* @return 登录结果
|
||||
*/
|
||||
WechatLoginResult loginByWechatMini(String code);
|
||||
|
||||
/**
|
||||
* 微信小程序一键登录(企业小程序)
|
||||
*
|
||||
* @param loginCode 微信登录code
|
||||
* @param phoneCode 手机号授权code
|
||||
* @return 登录结果
|
||||
*/
|
||||
AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode);
|
||||
|
||||
/**
|
||||
* 微信小程序绑定手机号
|
||||
*
|
||||
* @param openid 微信openid
|
||||
* @param mobile 手机号
|
||||
* @param smsCode 短信验证码
|
||||
* @return 登录结果
|
||||
*/
|
||||
AuthenticationToken bindMobileForWechatMini(String openid, String mobile, String smsCode);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.youlai.boot.auth.service;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* 微信小程序认证服务接口
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public interface WechatMiniappAuthService {
|
||||
|
||||
/**
|
||||
* 静默登录
|
||||
* <p>
|
||||
* 通过微信登录凭证(code)获取用户唯一标识(openid),
|
||||
* 如果用户已绑定手机号则直接登录成功,否则返回需绑定手机号的提示。
|
||||
* </p>
|
||||
*
|
||||
* @param code 微信登录凭证(wx.login 获取)
|
||||
* @return 登录结果(成功返回 token,需绑定返回 openid)
|
||||
*/
|
||||
WechatMiniappLoginResult silentLogin(String code);
|
||||
|
||||
/**
|
||||
* 手机号快捷登录
|
||||
* <p>
|
||||
* 同时使用微信登录凭证和手机号授权凭证,
|
||||
* 一步完成用户注册/登录,无需额外绑定流程。
|
||||
* 适用于企业认证的小程序(已开通手机号快捷登录权限)。
|
||||
* </p>
|
||||
*
|
||||
* @param loginCode 微信登录凭证(wx.login 获取)
|
||||
* @param phoneCode 手机号授权凭证(getPhoneNumber 事件获取)
|
||||
* @return 认证令牌
|
||||
*/
|
||||
AuthenticationToken phoneLogin(String loginCode, String phoneCode);
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
* <p>
|
||||
* 为已静默登录但未绑定手机号的用户绑定手机号,
|
||||
* 绑定成功后自动完成登录。
|
||||
* </p>
|
||||
*
|
||||
* @param openid 微信用户唯一标识
|
||||
* @param mobile 手机号码
|
||||
* @param smsCode 短信验证码
|
||||
* @return 认证令牌
|
||||
*/
|
||||
AuthenticationToken bindMobile(String openid, String mobile, String smsCode);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.youlai.boot.auth.service.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
@@ -236,7 +238,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode) {
|
||||
// 1. 用 loginCode 换取 openid
|
||||
cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult session;
|
||||
WxMaJscode2SessionResult session;
|
||||
try {
|
||||
session = wxMaService.jsCode2SessionInfo(loginCode);
|
||||
} catch (Exception e) {
|
||||
@@ -246,7 +248,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
String openid = session.getOpenid();
|
||||
|
||||
// 2. 用 phoneCode 换取手机号
|
||||
cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo phoneInfo;
|
||||
WxMaPhoneNumberInfo phoneInfo;
|
||||
try {
|
||||
phoneInfo = wxMaService.getUserService().getPhoneNoInfo(phoneCode);
|
||||
} catch (Exception e) {
|
||||
@@ -266,8 +268,8 @@ public class AuthServiceImpl implements AuthService {
|
||||
user = new User();
|
||||
user.setMobile(mobile);
|
||||
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
|
||||
user.setNickname(phoneInfo.getNickName() != null ? phoneInfo.getNickName() : "微信用户");
|
||||
user.setAvatar(phoneInfo.getAvatarUrl());
|
||||
user.setNickname("微信用户");
|
||||
user.setAvatar(null);
|
||||
user.setStatus(1);
|
||||
user.setIsDeleted(0);
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package com.youlai.boot.auth.service.impl;
|
||||
|
||||
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.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.auth.model.vo.WechatMiniappLoginResult;
|
||||
import com.youlai.boot.auth.service.WechatMiniappAuthService;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.security.exception.NeedBindMobileException;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import com.youlai.boot.security.model.WechatMiniAuthenticationToken;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.system.enums.SocialPlatformEnum;
|
||||
import com.youlai.boot.system.model.entity.User;
|
||||
import com.youlai.boot.system.service.UserSocialService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import com.youlai.boot.system.service.UserRoleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 微信小程序认证服务实现
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WechatMiniappAuthServiceImpl implements WechatMiniappAuthService {
|
||||
|
||||
private final WxMaService wxMaService;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final TokenManager tokenManager;
|
||||
private final UserService userService;
|
||||
private final UserSocialService userSocialService;
|
||||
private final UserRoleService userRoleService;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 静默登录
|
||||
*/
|
||||
@Override
|
||||
public WechatMiniappLoginResult silentLogin(String code) {
|
||||
WechatMiniAuthenticationToken token = new WechatMiniAuthenticationToken(code);
|
||||
|
||||
try {
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
AuthenticationToken authToken = tokenManager.generateToken(authentication);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
return WechatMiniappLoginResult.success(authToken);
|
||||
} catch (NeedBindMobileException e) {
|
||||
return WechatMiniappLoginResult.needBindMobile(e.getOpenid());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号快捷登录
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AuthenticationToken phoneLogin(String loginCode, String phoneCode) {
|
||||
// 1. 解析微信登录凭证,获取会话信息
|
||||
WxMaJscode2SessionResult session = resolveSession(loginCode);
|
||||
String openid = session.getOpenid();
|
||||
|
||||
// 2. 解析手机号授权凭证,获取手机号
|
||||
String mobile = resolvePhoneNumber(phoneCode);
|
||||
|
||||
log.info("微信小程序手机号快捷登录:openid={}, mobile={}", openid, mobile);
|
||||
|
||||
// 3. 查询或创建用户
|
||||
User user = findOrCreateUser(mobile);
|
||||
|
||||
// 4. 绑定微信 openid
|
||||
bindWechatOpenid(user, session);
|
||||
|
||||
// 5. 生成认证令牌
|
||||
return generateAuthToken(mobile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AuthenticationToken bindMobile(String openid, String mobile, String smsCode) {
|
||||
// 1. 验证短信验证码
|
||||
validateSmsCode(mobile, smsCode);
|
||||
|
||||
// 2. 查询或创建用户
|
||||
User user = findOrCreateUser(mobile);
|
||||
|
||||
// 3. 绑定微信 openid
|
||||
userSocialService.bindOrUpdate(
|
||||
user.getId(),
|
||||
SocialPlatformEnum.WECHAT_MINI,
|
||||
openid,
|
||||
null, null, null, null
|
||||
);
|
||||
|
||||
log.info("微信小程序绑定手机号成功:mobile={}, openid={}", mobile, openid);
|
||||
|
||||
// 4. 生成认证令牌
|
||||
return generateAuthToken(mobile);
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 解析微信登录凭证,获取会话信息
|
||||
*/
|
||||
private WxMaJscode2SessionResult resolveSession(String loginCode) {
|
||||
try {
|
||||
return wxMaService.jsCode2SessionInfo(loginCode);
|
||||
} catch (Exception e) {
|
||||
log.error("获取微信会话信息失败,loginCode={}", loginCode, e);
|
||||
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析手机号授权凭证,获取手机号
|
||||
*/
|
||||
private String resolvePhoneNumber(String phoneCode) {
|
||||
try {
|
||||
WxMaPhoneNumberInfo phoneInfo = wxMaService.getUserService().getPhoneNoInfo(phoneCode);
|
||||
return phoneInfo.getPhoneNumber();
|
||||
} catch (Exception e) {
|
||||
log.error("获取微信手机号失败,phoneCode={}", phoneCode, e);
|
||||
throw new IllegalArgumentException("获取手机号失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询或创建用户
|
||||
*/
|
||||
private User findOrCreateUser(String mobile) {
|
||||
User user = userService.lambdaQuery()
|
||||
.eq(User::getMobile, mobile)
|
||||
.one();
|
||||
|
||||
if (user == null) {
|
||||
user = createNewUser(mobile);
|
||||
log.info("微信小程序登录:创建新用户,mobile={}, userId={}", mobile, user.getId());
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新用户
|
||||
* <p>
|
||||
* 新用户默认分配 GUEST(访问游客)角色
|
||||
* </p>
|
||||
*/
|
||||
private User createNewUser(String mobile) {
|
||||
User user = new User();
|
||||
user.setMobile(mobile);
|
||||
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
|
||||
user.setNickname("微信用户");
|
||||
user.setStatus(1);
|
||||
user.setIsDeleted(0);
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
userService.save(user);
|
||||
|
||||
// 分配 GUEST 角色(角色ID=3)
|
||||
userRoleService.saveUserRoles(user.getId(), Collections.singletonList(3L));
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定微信 openid
|
||||
*/
|
||||
private void bindWechatOpenid(User user, WxMaJscode2SessionResult session) {
|
||||
try {
|
||||
userSocialService.bindOrUpdate(
|
||||
user.getId(),
|
||||
SocialPlatformEnum.WECHAT_MINI,
|
||||
session.getOpenid(),
|
||||
session.getUnionid(),
|
||||
user.getNickname(),
|
||||
user.getAvatar(),
|
||||
session.getSessionKey()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
// 绑定失败不影响登录
|
||||
log.warn("绑定微信 openid 失败,userId={}, openid={}", user.getId(), session.getOpenid(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证短信验证码
|
||||
*/
|
||||
private void validateSmsCode(String mobile, String smsCode) {
|
||||
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
|
||||
String cachedCode = (String) redisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (!StrUtil.equals(smsCode, cachedCode)) {
|
||||
throw new IllegalArgumentException("验证码错误");
|
||||
}
|
||||
|
||||
// 验证成功后删除验证码
|
||||
redisTemplate.delete(cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成认证令牌
|
||||
*/
|
||||
private AuthenticationToken generateAuthToken(String mobile) {
|
||||
SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile));
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities()
|
||||
);
|
||||
AuthenticationToken authToken = tokenManager.generateToken(authentication);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
return authToken;
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,32 @@ package com.youlai.boot.config;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
|
||||
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
|
||||
import com.youlai.boot.config.property.WxMaProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 微信小程序配置
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2024/01/01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(WxMaProperties.class)
|
||||
public class WxMaConfiguration {
|
||||
public class WxMaConfig {
|
||||
|
||||
/**
|
||||
* 微信小程序服务
|
||||
*
|
||||
* @param properties 微信小程序配置属性
|
||||
* @return {@link WxMaService}
|
||||
*/
|
||||
@Bean
|
||||
public WxMaService wxMaService(WxMaProperties properties) {
|
||||
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
|
||||
config.setAppid(properties.getAppid());
|
||||
config.setSecret(properties.getSecret());
|
||||
config.setToken(properties.getToken());
|
||||
config.setAesKey(properties.getAesKey());
|
||||
config.setMsgDataFormat(properties.getMsgDataFormat());
|
||||
|
||||
WxMaService service = new WxMaServiceImpl();
|
||||
service.setWxMaConfig(config);
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.config;
|
||||
package com.youlai.boot.config.property;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -11,28 +11,13 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
public class WxMaProperties {
|
||||
|
||||
/**
|
||||
* 小程序appid
|
||||
* 小程序 AppID
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 小程序Secret
|
||||
* 小程序 AppSecret
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 小程序token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 小程序EncodingAESKey
|
||||
*/
|
||||
private String aesKey;
|
||||
|
||||
/**
|
||||
* 消息格式
|
||||
*/
|
||||
private String msgDataFormat;
|
||||
|
||||
}
|
||||
@@ -276,5 +276,5 @@ public class UserController {
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -432,8 +432,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
}
|
||||
|
||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码
|
||||
String code = "1234";
|
||||
// TODO 为了方便测试,验证码固定为 123456,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码
|
||||
String code = "123456";
|
||||
|
||||
Map<String, String> templateParams = new HashMap<>();
|
||||
templateParams.put("code", code);
|
||||
@@ -517,8 +517,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
}
|
||||
|
||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||
// TODO 为了方便测试,验证码固定为 1234,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码
|
||||
String code = "1234";
|
||||
// TODO 为了方便测试,验证码固定为 123456,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码
|
||||
String code = "123456";
|
||||
|
||||
mailService.sendMail(email, "邮箱验证码", "您的验证码为:" + code + ",请在5分钟内使用");
|
||||
// 缓存验证码,5分钟有效,用于更换邮箱校验
|
||||
|
||||
Reference in New Issue
Block a user