@@ -1,361 +1,379 @@
package com.viewsh.module.system.service.auth ;
import cn.hutool.core.util.ObjectUtil ;
import com.viewsh.framework.common.enums.CommonStatusEnum ;
import com.viewsh.framework.common.enums.UserTypeEnum ;
import com.viewsh.framework.common.util.monitor.TracerUtils ;
import com.viewsh.framework.common.util.object.BeanUtils ;
import com.viewsh.framework.common.util.servlet.ServletUtils ;
import com.viewsh.framework.common.util.validation.ValidationUtils ;
import com.viewsh.framework.datapermission.core.annotation.DataPermission ;
import com.viewsh.module.system.api.logger.dto.LoginLogCreateReqDTO ;
import com.viewsh.module.system.api.sms.SmsCodeApi ;
import com.viewsh.module.system.api.sms.dto .cod e.SmsCodeUseReqDTO ;
import com.viewsh.module.system.api.social.dto.SocialUserBind ReqDTO ;
import com.viewsh.module.system.api.social.dto.SocialUserRespDTO ;
import com.viewsh.module.system.controller.admin.auth.vo.* ;
import com.viewsh.module.system.convert.auth.AuthConvert ;
import com.viewsh.module.system.dal.dataobject.oauth2.OAuth2AccessTokenD O ;
import com.viewsh.module.system.dal.dataobject.us er.A dminUserDO ;
import com.viewsh.module.system.enums.logger.LoginLogTypeEnum ;
import com.viewsh.module.system.enums.logger.LoginResultEnum ;
import com.viewsh.module.system.enums.oauth2.OAuth2ClientConstants ;
import com.viewsh.module.system.enums.sms.SmsScen eEnum ;
import com.viewsh.module.system.service .logger.LoginLogService ;
import com.viewsh.module.system.service.member.MemberService ;
import com.viewsh.module.system.service.social.SocialClientService ;
import com.viewsh.module.system.service.oauth2.OAuth2Token Service ;
import com.viewsh.module.system.service.social.SocialUs erService ;
import com.viewsh.module.system.service.user.AdminUser Service ;
import com.anji.captcha.model.common.ResponseModel ;
import com.anji.captcha.model.vo.CaptchaVO ;
import com.anji.captcha.service.Captcha Service ;
import com.google.common.annotations.VisibleForTesting ;
import jakarta.annotation.Resource ;
import jakarta.validation.Validator ;
import lombok.Setter ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.beans.factory.annot ation.Value ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Transactional ;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo ;
import cn.hutool.core.lang.Assert ;
import com.viewsh.module.system.enums.social.SocialTypeEnum ;
import java.util.Objects ;
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception ;
import static com.viewsh.framework.common.util.servlet.ServletUtils.getClientIP ;
import static com.viewsh.module.system.enums.ErrorCodeConstants.* ;
/**
* Auth Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class AdminAuthServiceImpl implements AdminAuthService {
@Resource
private AdminUser Service user Service;
@Resource
private LoginLogService loginLogService ;
@Resource
private OAuth2TokenService oauth2TokenService ;
@Resource
private SocialUserService socialUserService ;
@Resource
private MemberService memberService ;
@Resource
private SocialClientService socialClientService ;
@Resource
private Validator validator ;
@Resource
private CaptchaService captchaService ;
@Resource
private SmsCodeApi smsCodeApi ;
/**
* 验证码的开关,默认为 true
*/
@Value ( " ${viewsh.captcha.enable:true} " )
@Setter // 为了单测:开启或者关闭验证码
private Boolean captchaEnable ;
@Override
public AdminUserDO authenticate ( String username , String password ) {
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum . LOGIN_USERNAME ;
// 校验账号是否存在
AdminUserDO user = userService . getUserByUsername ( username ) ;
if ( user = = null ) {
createLoginLog ( null , username , logTypeEnum , LoginResultEnum . BAD_CREDENTIALS ) ;
throw exception ( AUTH_LOGIN_BAD_CREDENTIALS ) ;
}
if ( ! userService . isPasswordMatch ( password , user . getPassword ( ) ) ) {
createLoginLog ( user . getId ( ) , username , logTypeEnum , LoginResultEnum . BAD_CREDENTIALS) ;
throw exception ( AUTH_LOGIN_BAD_CREDENTIALS ) ;
}
// 校验是否禁用
if ( CommonStatusEnum . isDisable ( user . getStatus ( ) ) ) {
createLoginLog ( user . getId ( ) , username , logTypeEnum , LoginResultEnum . USER_DISABLED ) ;
throw exception ( AUTH_LOGIN_USER_DISABLED ) ;
}
return user ;
}
@Override
@DataPermission ( enable = false )
public AuthLoginRespVO login ( AuthLoginReqVO reqVO ) {
// 校验验证码
validateCaptcha ( reqVO ) ;
// 使用账号密码,进行登录
AdminUserDO user = authenticate ( reqVO . getUsername ( ) , reqVO . getPassword ( ) ) ;
// 如果 socialType 非空,说明需要绑定社交用户
if ( reqVO . getSocialType ( ) ! = null ) {
socialUserService . bindSocialUser ( new SocialUserBindReqDTO ( user . getId ( ) , getUserType ( ) . getValue ( ) ,
reqVO . getSocialType ( ) , reqVO . getSocialCode ( ) , reqVO . getSocialState ( ) ) ) ;
}
// 创建 Token 令牌,记录登录日志
return c reateTokenAfterLoginSuccess ( user . getId ( ) , reqVO . getUsernam e ( ) , LoginLogTypeEnum . LOGIN_USERNAME ) ;
}
@Override
public void sendSmsCode ( AuthSmsSendReqVO reqVO ) {
// 如果是重置密码场景,需要校验图形验证码是否正确
if ( Objects . equals ( SmsSceneEnum . ADMIN_MEMBER_RESET_PASSWORD . getScene ( ) , reqVO . getScene ( ) ) ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
if ( ! response . isSuccess ( ) ) {
throw exception ( AUTH_REGISTER_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
// 登录场景,验证是否存在
if ( userService . getUserByMobile ( reqVO . getMobile ( ) ) = = null ) {
throw exception ( AUTH_MOBILE_NOT_EXISTS ) ;
}
// 发送验证码
smsCodeApi . sendSmsCode ( AuthConvert . INSTANCE . convert ( reqVO ) . setCreateIp ( getClientIP ( ) ) ) ;
}
@Override
public AuthLoginRespVO smsLogin ( AuthSmsLoginReqVO reqVO ) {
// 校验验证码
smsCodeApi . useSmsCode ( AuthConvert . INSTANCE . convert ( reqVO , SmsSceneEnum . ADMIN_MEMBER_LOGIN . getScene ( ) , getClientIP ( ) ) ) . checkError ( ) ;
// 获得用户信息
AdminUserDO user = userService . getUserByMobile ( reqVO . getMobile ( ) ) ;
if ( user = = null ) {
throw exception ( USER_NOT_EXISTS ) ;
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , reqVO . getMobile ( ) , LoginLogTypeEnum . LOGIN_MOBILE ) ;
}
private void createLoginLog ( Long userId , String username ,
LoginLogTypeEnum logTypeEnum , LoginResultEnum loginResult ) {
// 插入登录日志
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO ( ) ;
reqDTO . setLogType ( logTypeEnum . getType ( ) ) ;
reqDTO . setTraceId ( TracerUtils . getTraceId ( ) ) ;
reqDTO . setUserId ( userId ) ;
reqDTO . setUser Type ( getUserType ( ) . getValu e ( ) ) ;
reqDTO . setUsername ( username ) ;
reqDTO . setUserAgent ( ServletUtils . getUserAgent ( ) ) ;
reqDTO . setUserIp ( ServletUtils . getClientIP ( ) ) ;
reqDTO . setResult ( loginResult . getResult ( ) ) ;
loginLogService . createLoginLog ( reqDTO ) ;
// 更新最后登录时间
if ( userId ! = null & & Objects . equals ( LoginResultEnum . SUCCESS . g etResult() , loginResult . getResult ( ) ) ) {
user Service . updateUser Login( userId , ServletUtils . getClientIP ( ) ) ;
}
}
@Override
public AuthLoginRespVO socialLogin ( AuthSocialLoginReqVO reqVO ) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
SocialUserRespDTO socialUser = socialUserService . getSocialUserByCode ( UserTypeEnum . ADMIN . getValue ( ) , reqVO . getType ( ) ,
reqVO . getCode ( ) , reqVO . getState ( ) ) ;
if ( socialUser = = null | | socialUser . getUserId ( ) = = null ) {
throw exception ( AUTH_THIRD_LOGIN_NOT_BIND ) ;
}
// 获得用户
AdminUserDO user = userService . getUser ( socialUser . getUserId ( ) ) ;
if ( user = = null ) {
throw exception ( USER_NOT_EXISTS ) ;
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , user . getUsername ( ) , LoginLogTypeEnum . LOGIN_SOCIAL ) ;
}
@Override
@Transactional ( rollbackFor = Exception . class )
public AuthLoginRespVO weixinMiniAppLogin ( AuthWeixinMiniAppLoginReqVO reqVO ) {
// 1. 通过 phoneCode 获取手机号
WxMaPhoneNumberInfo phoneNumberInfo = socialClientService . getWxMaPhoneNumberInfo (
UserTypeEnum . ADMIN . getValue ( ) , reqVO . getPhoneCode ( ) ) ;
Assert . notNull ( phoneNumberInfo , " 获得手机信息失败,结果为空 " ) ;
String mobile = phoneNumberInfo . getPure PhoneNumber () ;
// 2. 通过手机号查找管理员
AdminUserDO user = userService . getUserByMobile ( mobile ) ;
if ( user = = null ) {
throw exception ( AUTH_WEIXIN_MINI_APP_PHONE_NOT_FOUND ) ;
}
if ( CommonStatusEnum . isDisable ( user . getStatus ( ) ) ) {
throw exception ( AUTH_LOGIN_USER_DISABLE D ) ;
}
// 3. 通过 loginCode 获取社交用户(含 openid)
SocialUserRespDTO socialUser = socialUserService . getSocialUserByCode (
UserTypeEnum . ADMIN . getValue ( ) , SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ,
reqVO . getLoginCode ( ) , reqVO . getState ( ) ) ;
Assert . notNull ( socialUser , " 社交用户不能为空 " ) ;
// 4. 绑定冲突检测
// 4a. 当前 openid 是否已绑定其他管理员
if ( socialUser . getUserId ( ) ! = null & & ! socialUser . getUserId ( ) . equals ( user . getId ( ) ) ) {
throw exception ( AUTH_WEIXIN_MINI_APP_WECHAT_BINDTO_OTHER ) ;
}
// 4b. 目标管理员是否已绑定其他微信
SocialUserRespDTO existByUser = socialUserService . getSocialUserByUserId (
UserTypeEnum . ADMIN . getValue ( ) , user . getId ( ) ,
SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ) ;
if ( existByUser ! = null & & ! existByUser . getOpenid ( ) . equals ( socialUser . getOpenid ( ) ) ) {
throw exception ( AUTH_WEIXIN_MINI_APP_BINDTO_OTHER_WECHAT ) ;
}
// 5. 绑定社交用户(无冲突且未绑定时)
if ( existByUser = = null ) {
socialUserService . bindSocialUser ( new SocialUserBindReqDTO ( user . getId ( ) ,
getUserType ( ) . getValue ( ) , SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ,
reqVO . getLoginCode ( ) , reqVO . getState ( ) ) ) ;
}
// 6. 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , user . getUsername ( ) , LoginLogTypeEnum . LOGIN_SOCIAL ) ;
}
@VisibleForTesting
void validateCaptcha ( AuthLoginReqVO reqVO ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
// 校验验证码
if ( ! response . isSuccess ( ) ) {
// 创建登录失败日志(验证码不正确)
createLoginLog ( null , reqVO . getUsername ( ) , LoginLogTypeEnum . LOGIN_USERNAME , LoginResultEnum . CAPTCHA_CODE_ERROR ) ;
throw exception ( AUTH_LOGIN_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
private ResponseModel doValidateCaptcha ( CaptchaVerificationReqVO reqVO ) {
// 如果验证码关闭,则不进行校验
if ( ! captchaEnable ) {
return ResponseModel . success ( ) ;
}
ValidationUtils . validate ( validator , reqVO , C aptchaVerificationReqVO . CodeEnableGroup . class ) ;
CaptchaVO captchaVO = new CaptchaVO ( ) ;
captchaVO . setCaptchaVerification ( reqVO . getCaptchaVerification ( ) ) ;
return c aptchaS ervice . verification ( captchaVO ) ;
}
private AuthLoginRespVO createTokenAfterLoginSuccess ( Long userId , String username , LoginLogTypeEnum logType ) {
// 插入登陆日志
createLoginLog ( userId , username , logType , LoginResultEnum . SUCCESS ) ;
// 创建访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . createAccessToken ( userId , getUserType ( ) . getValue ( ) ,
OAuth2ClientConstants . CLIENT_ID_DEFAULT , null ) ;
// 构建返回结果
return BeanUtils . toBean ( accessTokenDO , AuthLoginRespVO . class ) ;
}
@Override
public AuthLoginRespVO refreshToken ( String refreshToken ) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . refreshAccessToken ( refreshToken , OAuth2ClientConstants . CLIENT_ID_DEFAULT ) ;
return BeanUtils . toBean ( accessTokenDO , AuthLoginRespVO . class ) ;
}
@Override
public void logout ( String token , Integer logType ) {
// 删除访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . removeAccessToken ( token ) ;
if ( accessTokenDO = = null ) {
return ;
}
// 删除成功,则记录登出日志
createLogoutLog ( accessTokenDO . getUserId ( ) , accessTokenDO . getUserType ( ) , logType ) ;
}
private void createLogoutLog ( Long userId , Integer userType , Integer logType ) {
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO ( ) ;
reqDTO . s etLogType ( logType ) ;
reqDTO . setTraceId ( TracerUtils . getTraceId ( ) ) ;
reqDTO . setUserId ( userId ) ;
reqDTO . setUserType ( userType ) ;
if ( ObjectUtil . equal ( getUserType ( ) . getValue ( ) , userType ) ) {
reqDTO . setUsername ( getUsername ( userId ) ) ;
} else {
reqDTO . setUsername ( memberService . getMemberUserMobile ( userId ) ) ;
}
reqDTO . setUserAgent ( ServletUtils . getUserAgent ( ) ) ;
reqDTO . setUserIp ( ServletUtils . getClientIP ( ) ) ;
reqDTO . setResult ( LoginResultEnum . SUCCESS . getResult ( ) ) ;
loginLogService . createLoginLog ( reqDTO ) ;
}
private String getUsername ( Long userId ) {
if ( userId = = null ) {
return null ;
}
AdminUserDO user = userService . getUser ( userId ) ;
return user ! = null ? user . g etUsername ( ) : null ;
}
private UserTypeEnum getUserType ( ) {
return UserTypeEnum . ADMIN ;
}
@Override
public AuthLoginRespVO register ( AuthRegisterReqVO registerReqVO ) {
// 1. 校验验证码
validateCaptcha ( registerReqV O) ;
// 2. 校验用户名是否已存在
Long userId = userService . registerUser ( registerReqVO ) ;
// 3. 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( userId , registerReqVO . getUsername ( ) , LoginLogTypeEnum . LOGIN_USERNAME ) ;
}
@VisibleForTesting
void validateCaptcha ( AuthRegisterReqVO reqVO ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
// 验证不通过
if ( ! response . isSuccess ( ) ) {
throw exception ( AUTH_REGISTER_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
@Override
@Transactional ( rollbackFor = Exception . class )
public void resetPassword ( AuthResetPasswordReqVO reqVO ) {
AdminUserDO userByMobile = userService . getUserByMobile ( reqVO . getMobile ( ) ) ;
if ( userByMobile = = null ) {
throw exception ( USER_MOBILE_NOT_EXISTS ) ;
}
smsCodeApi . useSmsCode ( new SmsCodeUseReqDTO ( )
. setCode ( reqVO . getCode ( ) )
. setMobile ( reqVO . getMobile ( ) )
. setScene ( SmsSceneEnum . ADMIN_MEMBER_RESET_PASSWORD . getScene ( ) )
. setUsedIp ( getClientIP ( ) )
) . checkError ( ) ;
userService . updateUserPassword ( userByMobile . getId ( ) , reqVO . getPassword ( ) ) ;
}
}
package com.viewsh.module.system.service.auth ;
import cn.hutool.core.util.ObjectUtil ;
import com.viewsh.framework.common.enums.CommonStatusEnum ;
import com.viewsh.framework.common.enums.UserTypeEnum ;
import com.viewsh.framework.common.util.monitor.TracerUtils ;
import com.viewsh.framework.common.util.object.BeanUtils ;
import com.viewsh.framework.common.util.servlet.ServletUtils ;
import com.viewsh.framework.common.util.validation.ValidationUtils ;
import com.viewsh.framework.web.core.util.WebFrameworkUtils ;
import cn.hutool.core.util.StrUtil ;
import jakarta.servlet.http.HttpServletRequest ;
import com.viewsh.framework.datapermission .cor e.annotation.DataPermission ;
import com.viewsh.module.system.api.logger.dto.LoginLogCreate ReqDTO ;
import com.viewsh.module.system.api.sms.SmsCodeApi ;
import com.viewsh.module.system.api.sms.dto.code.SmsCodeUseReqDTO ;
import com.viewsh.module.system.api.social.dto.SocialUserBindReqDTO ;
import com.viewsh.module.system.api.social.dto.SocialUserRespDT O ;
import com.viewsh.module.system.controll er.a dmin.auth.vo.* ;
import com.viewsh.module.system.convert.auth.AuthConvert ;
import com.viewsh.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO ;
import com.viewsh.module.system.dal.dataobject.user.AdminUserDO ;
import com.viewsh.module.system.enums.logger.LoginLogTyp eEnum ;
import com.viewsh.module.system.enums .logger.LoginResultEnum ;
import com.viewsh.module.system.enums.oauth2.OAuth2ClientConstants ;
import com.viewsh.module.system.enums.sms.SmsSceneEnum ;
import com.viewsh.module.system.service.logger.LoginLog Service ;
import com.viewsh.module.system.service.member.Memb erService ;
import com.viewsh.module.system.service.social.SocialClient Service ;
import com.viewsh.module.system.service.oauth2.OAuth2TokenService ;
import com.viewsh.module.system.service.social.SocialUserService ;
import com.viewsh.module.system.service.user.AdminUser Service ;
import com.anji.captcha.model.common.ResponseModel ;
import com.anji.captcha.model.vo.CaptchaVO ;
import com.anji.captcha.service.CaptchaService ;
import com.google.common.annotations.VisibleForTesting ;
import jakarta.annotation.Resource ;
import jakarta.valid ation.Validator ;
import lombok.Setter ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Transactional ;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo ;
import cn.hutool.core.lang.Assert ;
import com.viewsh.module.system.enums.social.SocialTypeEnum ;
import java.util.Objects ;
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception ;
import static com.viewsh.framework.common.util.servlet.ServletUtils.getClientIP ;
import static com.viewsh.module.system.enums.ErrorCodeConstants.* ;
/**
* Auth Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class AdminAuth ServiceImpl implements AdminAuth Service {
@Resource
private AdminUserService userService ;
@Resource
private LoginLogService loginLogService ;
@Resource
private OAuth2TokenService oauth2TokenService ;
@Resource
private SocialUserService socialUserService ;
@Resource
private MemberService memberService ;
@Resource
private SocialClientService socialClientService ;
@Resource
private Validator validator ;
@Resource
private CaptchaService captchaService ;
@Resource
private SmsCodeApi smsCodeApi ;
/**
* 验证码的开关,默认为 true
*/
@Value ( " ${viewsh.captcha.enable:true} " )
@Setter // 为了单测:开启或者关闭验证码
private Boolean captchaEnable ;
@Override
public AdminUserDO authenticate ( String username , String password ) {
final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum . LOGIN_USERNAME ;
// 校验账号是否存在
AdminUserDO user = userService . getUserByUsername ( username ) ;
if ( user = = null ) {
createLoginLog ( null , username , logTypeEnum , LoginResultEnum . BAD_CREDENTIALS ) ;
throw exception ( AUTH_LOGIN_ BAD_CREDENTIALS) ;
}
if ( ! userService . isPasswordMatch ( password , user . getPassword ( ) ) ) {
createLoginLog ( user . getId ( ) , username , logTypeEnum , LoginResultEnum . BAD_CREDENTIALS ) ;
throw exception ( AUTH_LOGIN_BAD_CREDENTIALS ) ;
}
// 校验是否禁用
if ( CommonStatusEnum . isDisable ( user . getStatus ( ) ) ) {
createLoginLog ( user . getId ( ) , username , logTypeEnum , LoginResultEnum . USER_DISABLED ) ;
throw exception ( AUTH_LOGIN_USER_DISABLED ) ;
}
return user ;
}
@Override
@DataPermission ( enable = false )
public AuthLoginRespVO login ( AuthLoginReqVO reqVO ) {
// 校验验证码
validateCaptcha ( reqVO ) ;
// 使用账号密码,进行登录
AdminUserDO user = authenticate ( reqVO . getUsername ( ) , reqVO . getPassword ( ) ) ;
// 如果 socialType 非空,说明需要绑定社交用户
if ( reqVO . getSocialType ( ) ! = null ) {
socialUserService . bindSocialUser ( new SocialUserBindReqDTO ( user . getId ( ) , getUserType ( ) . getValue ( ) ,
reqVO . getSocialType ( ) , reqVO . getSocialCod e ( ) , reqVO . getSocialState ( ) ) ) ;
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , reqVO . getUsername ( ) , LoginLogTypeEnum . LOGIN_USERNAME ) ;
}
@Override
public void sendSmsCode ( AuthSmsSendReqVO reqVO ) {
// 如果是重置密码场景,需要校验图形验证码是否正确
if ( Objects . equals ( SmsSceneEnum . ADMIN_MEMBER_RESET_PASSWORD . getScene ( ) , reqVO . getScene ( ) ) ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
if ( ! response . isSuccess ( ) ) {
throw exception ( AUTH_REGISTER_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
// 登录场景,验证是否存在
if ( userService . getUserByMobile ( reqVO . getMobile ( ) ) = = null ) {
throw exception ( AUTH_MOBILE_NOT_EXISTS ) ;
}
// 发送验证码
smsCodeApi . sendSmsCode ( AuthConvert . INSTANCE . convert ( reqVO ) . setCreateIp ( getClientIP ( ) ) ) ;
}
@Override
public AuthLoginRespVO smsLogin ( AuthSmsLoginReqVO reqVO ) {
// 校验验证码
smsCodeApi . useSmsCode ( AuthConvert . INSTANCE . convert ( reqVO , SmsSceneEnum . ADMIN_MEMBER_LOGIN . getScene ( ) , getClientIP ( ) ) ) . checkError ( ) ;
// 获得用户信息
AdminUserDO user = userService . getUserByMobile ( reqVO . getMobile ( ) ) ;
if ( user = = null ) {
throw exception ( USER_NOT_EXISTS ) ;
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , reqVO . getMobile ( ) , LoginLogTypeEnum . LOGIN_MOBILE ) ;
}
private void createLoginLog ( Long userId , String username ,
LoginLogTypeEnum logTypeEnum , LoginResultEnum loginResult ) {
// 插入登录日志
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO ( ) ;
reqDTO . setLog Type ( logTypeEnum . getTyp e ( ) ) ;
reqDTO . setTraceId ( TracerUtils . getTraceId ( ) ) ;
reqDTO . setUserId ( userId ) ;
reqDTO . setUserType ( getUserType ( ) . getValue ( ) ) ;
reqDTO . setUsername ( username ) ;
reqDTO . setUserAgent ( ServletUtils . getUserAgent ( ) ) ;
reqDTO . setUserIp ( ServletUtils . getClientIP ( ) ) ;
reqDTO . s etResult( loginResult . getResult ( ) ) ;
loginLog Service . create LoginLog ( reqDTO ) ;
// 更新最后登录时间
if ( userId ! = null & & Objects . equals ( LoginResultEnum . SUCCESS . getResult ( ) , loginResult . getResult ( ) ) ) {
userService . updateUserLogin ( userId , ServletUtils . getClientIP ( ) ) ;
}
}
@Override
public AuthLoginRespVO socialLogin ( AuthSocialLoginReqVO reqVO ) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
SocialUserRespDTO socialUser = socialUserService . getSocialUserByCode ( UserTypeEnum . ADMIN . getValue ( ) , reqVO . getType ( ) ,
reqVO . getCode ( ) , reqVO . getState ( ) ) ;
if ( socialUser = = null | | socialUser . getUserId ( ) = = null ) {
throw exception ( AUTH_THIRD_LOGIN_NOT_BIND ) ;
}
// 获得用户
AdminUserDO user = userService . getUser ( socialUser . getUserId ( ) ) ;
if ( user = = null ) {
throw exception ( USER_NOT_EXISTS ) ;
}
// 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , user . getUsername ( ) , LoginLogTypeEnum . LOGIN_SOCIAL ) ;
}
@Override
@Transactional ( rollbackFor = Exception . class )
public AuthLoginRespVO weixinMiniAppLogin ( AuthWeixinMiniAppLoginReqVO reqVO ) {
// 1. 通过 phoneCode 获取手机号
WxMaPhoneNumberInfo phoneNumberInfo = socialClientService . getWxMa PhoneNumberInfo (
UserTypeEnum . ADMIN . getValue ( ) , reqVO . getPhoneCode ( ) ) ;
Assert . notNull ( phoneNumberInfo , " 获得手机信息失败,结果为空 " ) ;
String mobile = phoneNumberInfo . getPurePhoneNumber ( ) ;
// 2. 通过手机号查找管理员
AdminUserDO user = userService . getUserByMobile ( mobile ) ;
if ( user = = null ) {
throw exception ( AUTH_WEIXIN_MINI_APP_PHONE_NOT_FOUN D ) ;
}
if ( CommonStatusEnum . isDisable ( user . getStatus ( ) ) ) {
throw exception ( AUTH_LOGIN_USER_DISABLED ) ;
}
// 3. 通过 loginCode 获取社交用户(含 openid)
SocialUserRespDTO socialUser = socialUserService . getSocialUserByCode (
UserTypeEnum . ADMIN . getValue ( ) , SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ,
reqVO . getLoginCode ( ) , reqVO . getState ( ) ) ;
Assert . notNull ( socialUser , " 社交用户不能为空 " ) ;
// 4. 绑定冲突检测
// 4a. 当前 openid 是否已绑定其他管理员
if ( socialUser . getUserId ( ) ! = null & & ! socialUser . getUserId ( ) . equals ( user . getId ( ) ) ) {
throw exception ( AUTH_WEIXIN_MINI_APP_WECHAT_BINDTO_OTHER ) ;
}
// 4b. 目标管理员是否已绑定其他微信
SocialUserRespDTO existByUser = socialUserService . getSocialUserByUserId (
UserTypeEnum . ADMIN . getValue ( ) , user . getId ( ) ,
SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ) ;
if ( existByUser ! = null & & ! existByUser . getOpenid ( ) . equals ( socialUser . getOpenid ( ) ) ) {
throw exception ( AUTH_WEIXIN_MINI_APP_BINDTO_OTHER_WECHAT ) ;
}
// 5. 绑定社交用户(无冲突且未绑定时)
if ( existByUser = = null ) {
socialUserService . bindSocialUser ( new SocialUserBindReqDTO ( user . getId ( ) ,
getUserType ( ) . getValue ( ) , SocialTypeEnum . WECHAT_MINI_PROGRAM . getType ( ) ,
reqVO . getLoginCode ( ) , reqVO . getState ( ) ) ) ;
}
// 6. 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( user . getId ( ) , user . getUsername ( ) , LoginLogTypeEnum . LOGIN_SOCIAL ) ;
}
@VisibleForTesting
void validateCaptcha ( AuthLoginReqVO reqVO ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
// 校验验证码
if ( ! response . isSuccess ( ) ) {
// 创建登录失败日志(验证码不正确)
createLoginLog ( null , reqVO . getUsername ( ) , LoginLogTypeEnum . LOGIN_USERNAME , LoginResultEnum . CAPTCHA_CODE_ERROR ) ;
throw exception ( AUTH_LOGIN_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
private ResponseModel doValidateCaptcha ( CaptchaVerificationReqVO reqVO ) {
// 如果验证码关闭,则不进行校验
if ( ! c aptchaEnable ) {
return ResponseModel . success ( ) ;
}
ValidationUtils . validate ( validator , reqVO , C aptchaV erificationReqVO . CodeEnableGroup . class ) ;
CaptchaVO captchaVO = new CaptchaVO ( ) ;
captchaVO . setCaptchaVerification ( reqVO . getCaptchaVerification ( ) ) ;
return captchaService . verification ( captchaVO ) ;
}
private AuthLoginRespVO createTokenAfterLoginSuccess ( Long userId , String username , LoginLogTypeEnum logType ) {
// 插入登陆日志
createLoginLog ( userId , username , logType , LoginResultEnum . SUCCESS ) ;
// 创建访问令牌( client_id 优先取请求头 X-Client-Id, 缺省回退 default)
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . createAccessToken ( userId , getUserType ( ) . getValue ( ) ,
resolveClientId ( ) , null ) ;
// 构建返回结果
return BeanUtils . toBean ( accessTokenDO , AuthLoginRespVO . class ) ;
}
@Override
public AuthLoginRespVO refreshToken ( String refreshToken ) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . refreshAccessToken ( refreshToken , resolveClientId ( ) ) ;
return BeanUtils . toBean ( accessTokenDO , AuthLoginRespVO . class ) ;
}
/**
* 解析当前请求声明的 OAuth2 客户端编号。
*
* 用于密码登录、refresh-token 等"还没有 token 的入口"——前端通过请求头 X-Client-Id 声明自己身份,
* 决定 token 绑定到哪个客户端,进而影响 {@code AuthController#getPermissionInfo} 按 platform 过滤的菜单。
*
* 缺省回退到 {@link OAuth2ClientConstants#CLIENT_ID_DEFAULT}( "default"),即原生业务平台客户端;
* 在 DB 层通过 system_oauth2_client.platform='biz' 标识。
*/
private String resolveClientId ( ) {
HttpServletRequest request = WebFrameworkUtils . g etRequest ( ) ;
String clientId = WebFrameworkUtils . getClientId ( request ) ;
return StrUtil . isNotBlank ( clientId ) ? clientId : OAuth2ClientConstants . CLIENT_ID_DEFAULT ;
}
@Override
public void logout ( String token , Integer logType ) {
// 删除访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService . removeAccessToken ( token ) ;
if ( accessTokenDO = = null ) {
return ;
}
// 删除成功,则记录登出日志
createLogoutLog ( accessTokenDO . getUserId ( ) , accessTokenDO . getUserType ( ) , logType ) ;
}
private void createLogoutLog ( Long userId , Integer userType , Integer logType ) {
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO ( ) ;
reqDTO . setLogType ( logType ) ;
reqDTO . setTraceId ( TracerUtils . getTraceId ( ) ) ;
reqDTO . s etUserId ( userId ) ;
reqDTO . setUserType ( userType ) ;
if ( ObjectUtil . equal ( getUserType ( ) . getValue ( ) , userType ) ) {
reqDTO . setUsername ( getUsername ( userId ) ) ;
} else {
reqDTO . setUsername ( memberService . getMemberUserMobile ( userId ) ) ;
}
reqDTO . setUserAgent ( ServletUtils . getUserAgent ( ) ) ;
reqDTO . setUserIp ( ServletUtils . getClientIP ( ) ) ;
reqDTO . setResult ( LoginResultEnum . SUCCESS . getResult ( ) ) ;
loginLogService . createLoginLog ( reqDT O) ;
}
private String getUsername ( Long userId ) {
if ( userId = = null ) {
return null ;
}
AdminUserDO user = userService . getUser ( userId ) ;
return user ! = null ? user . getUsername ( ) : null ;
}
private UserTypeEnum getUserType ( ) {
return UserTypeEnum . ADMIN ;
}
@Override
public AuthLoginRespVO register ( AuthRegisterReqVO registerReqVO ) {
// 1. 校验验证码
validateCaptcha ( registerReqVO ) ;
// 2. 校验用户名是否已存在
Long userId = userService . registerUser ( registerReqVO ) ;
// 3. 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess ( userId , registerReqVO . getUsername ( ) , LoginLogTypeEnum . LOGIN_USERNAME ) ;
}
@VisibleForTesting
void validateCaptcha ( AuthRegisterReqVO reqVO ) {
ResponseModel response = doValidateCaptcha ( reqVO ) ;
// 验证不通过
if ( ! response . isSuccess ( ) ) {
throw exception ( AUTH_REGISTER_CAPTCHA_CODE_ERROR , response . getRepMsg ( ) ) ;
}
}
@Override
@Transactional ( rollbackFor = Exception . class )
public void resetPassword ( AuthResetPasswordReqVO reqVO ) {
AdminUserDO userByMobile = userService . getUserByMobile ( reqVO . getMobile ( ) ) ;
if ( userByMobile = = null ) {
throw exception ( USER_MOBILE_NOT_EXISTS ) ;
}
smsCodeApi . useSmsCode ( new SmsCodeUseReqDTO ( )
. setCode ( reqVO . getCode ( ) )
. setMobile ( reqVO . getMobile ( ) )
. setScene ( SmsSceneEnum . ADMIN_MEMBER_RESET_PASSWORD . getScene ( ) )
. setUsedIp ( getClientIP ( ) )
) . checkError ( ) ;
userService . updateUserPassword ( userByMobile . getId ( ) , reqVO . getPassword ( ) ) ;
}
}