!137 简化三方登录的实现,降低理解成本

Merge pull request !137 from 芋道源码/feature/1.6.2
pull/2/head
芋道源码 2022-04-26 16:37:49 +00:00 committed by Gitee
commit 0bf5b20fd7
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
46 changed files with 649 additions and 590 deletions

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.framework.social.config; package cn.iocoder.yudao.framework.social.config;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import com.xkcoding.http.HttpUtil;
import com.xkcoding.http.support.hutool.HutoolImpl;
import com.xkcoding.justauth.autoconfigure.JustAuthProperties; import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache; import me.zhyd.oauth.cache.AuthStateCache;
@ -23,6 +26,9 @@ public class YudaoSocialAutoConfiguration {
@Bean @Bean
@ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
// 需要修改 HttpUtil 使用的实现,避免类报错
HttpUtil.setHttp(new HutoolImpl());
// 创建 YudaoAuthRequestFactory
return new YudaoAuthRequestFactory(properties, authStateCache); return new YudaoAuthRequestFactory(properties, authStateCache);
} }

View File

@ -23,7 +23,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
@Api(tags = "用户 APP - 认证") @Api(tags = "用户 APP - 认证")
@RestController @RestController
@RequestMapping("/member/") @RequestMapping("/member/auth")
@Validated @Validated
@Slf4j @Slf4j
public class AppAuthController { public class AppAuthController {
@ -33,7 +33,6 @@ public class AppAuthController {
@PostMapping("/login") @PostMapping("/login")
@ApiOperation("使用手机 + 密码登录") @ApiOperation("使用手机 + 密码登录")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) { public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent()); String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果 // 返回结果
@ -42,7 +41,6 @@ public class AppAuthController {
@PostMapping("/sms-login") @PostMapping("/sms-login")
@ApiOperation("使用手机 + 验证码登录") @ApiOperation("使用手机 + 验证码登录")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) { public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent()); String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果 // 返回结果
@ -51,7 +49,6 @@ public class AppAuthController {
@PostMapping("/send-sms-code") @PostMapping("/send-sms-code")
@ApiOperation(value = "发送手机验证码") @ApiOperation(value = "发送手机验证码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) { public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
authService.sendSmsCode(getLoginUserId(), reqVO); authService.sendSmsCode(getLoginUserId(), reqVO);
return success(true); return success(true);
@ -60,7 +57,6 @@ public class AppAuthController {
@PostMapping("/reset-password") @PostMapping("/reset-password")
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用") @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
@PreAuthenticated @PreAuthenticated
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) { public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
authService.resetPassword(reqVO); authService.resetPassword(reqVO);
return success(true); return success(true);
@ -87,35 +83,18 @@ public class AppAuthController {
return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri)); return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
} }
@PostMapping("/social-login") @PostMapping("/social-quick-login")
@ApiOperation(value = "社交登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户") @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) { public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent()); String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
return success(AppAuthLoginRespVO.builder().token(token).build()); return success(AppAuthLoginRespVO.builder().token(token).build());
} }
@PostMapping("/social-login2") @PostMapping("/social-bind-login")
@ApiOperation(value = "社交登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定") @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) { String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
return success(AppAuthLoginRespVO.builder().token(token).build()); return success(AppAuthLoginRespVO.builder().token(token).build());
} }
@PostMapping("/social-bind")
@ApiOperation(value = "社交绑定,使用 code 授权码", notes = "使用在用户已经登录的情况下")
@PreAuthenticated
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
authService.socialBind(getLoginUserId(), reqVO);
return CommonResult.success(true);
}
@DeleteMapping("/social-unbind")
@ApiOperation("取消社交绑定")
@PreAuthenticated
public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
authService.unbindSocialUser(getLoginUserId(), reqVO);
return CommonResult.success(true);
}
} }

View File

@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
@ApiModel("用户 APP - 社交登录 Request VO使用 code 授权码 + 账号密码") @ApiModel("用户 APP - 社交绑定登录 Request VO使用 code 授权码 + 账号密码")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AppAuthSocialLogin2ReqVO { public class AppAuthSocialBindLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)
@ -45,5 +45,4 @@ public class AppAuthSocialLogin2ReqVO {
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String smsCode; private String smsCode;
} }

View File

@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ApiModel("用户 APP - 社交登录 Request VO使用 code 授权码") @ApiModel("用户 APP - 社交快捷登录 Request VO使用 code 授权码")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AppAuthSocialLoginReqVO { public class AppAuthSocialQuickLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.member.controller.app.social;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert;
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "用户 App - 社交用户")
@RestController
@RequestMapping("/system/social-user")
@Validated
public class AppSocialUserController {
@Resource
private SocialUserApi socialUserApi;
@PostMapping("/bind")
@ApiOperation("社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) {
socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
return CommonResult.success(true);
}
@DeleteMapping("/unbind")
@ApiOperation("取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
return CommonResult.success(true);
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.member.controller.app.auth.vo; package cn.iocoder.yudao.module.member.controller.app.social.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -17,7 +17,7 @@ import javax.validation.constraints.NotNull;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AppAuthSocialBindReqVO { public class AppSocialUserBindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.member.controller.app.auth.vo; package cn.iocoder.yudao.module.member.controller.app.social.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -12,20 +12,20 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ApiModel("用户 APP - 取消社交绑定 Request VO,使用 code 授权码") @ApiModel("用户 APP - 取消社交绑定 Request VO")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AppAuthSocialUnbindReqVO { public class AppSocialUserUnbindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空") @NotNull(message = "社交平台的类型不能为空")
private Integer type; private Integer type;
@ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
@NotEmpty(message = "社交的全局编号不能为空") @NotEmpty(message = "社交用户的 openid 不能为空")
private String unionId; private String openid;
} }

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.convert.auth;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
@ -26,10 +27,9 @@ public interface AuthConvert {
return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue()); return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
} }
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLogin2ReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO); SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO);
SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO); SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO);
SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp); SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.member.convert.social;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SocialUserConvert {
SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.service.auth;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import javax.validation.Valid; import javax.validation.Valid;
@ -43,7 +45,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
* @param userAgent UA * @param userAgent UA
* @return 使 JWT * @return 使 JWT
*/ */
String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent); String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
/** /**
* 使 + * 使 +
@ -53,23 +55,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
* @param userAgent UA * @param userAgent UA
* @return 使 JWT * @return 使 JWT
*/ */
String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent); String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
/**
* 使 code
*
* @param userId
* @param reqVO
*/
void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO);
/**
*
*
* @param userId
* @param reqVO
*/
void unbindSocialUser(Long userId, @Valid AppAuthSocialUnbindReqVO reqVO);
/** /**
* URL * URL

View File

@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
@ -108,7 +110,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
} }
@Override @Override
public String socialLogin(AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent) { public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(), Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState()); reqVO.getCode(), reqVO.getState());
@ -125,25 +127,19 @@ public class MemberAuthServiceImpl implements MemberAuthService {
// 创建 LoginUser 对象 // 创建 LoginUser 对象
LoginUser loginUser = AuthConvert.INSTANCE.convert(user); LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
// 绑定社交用户(更新)
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
// 缓存登录用户到 Redis 中,返回 sessionId 编号 // 缓存登录用户到 Redis 中,返回 sessionId 编号
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
} }
@Override @Override
public String socialLogin2(AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) { public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
// 校验社交平台的认证信息是否正确
socialUserApi.checkSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
// 使用手机号、手机验证码登录 // 使用手机号、手机验证码登录
AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder() AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
.mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build(); .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
String sessionId = this.smsLogin(loginReqVO, userIp, userAgent); String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
LoginUser loginUser = userSessionApi.getLoginUser(sessionId); LoginUser loginUser = userSessionApi.getLoginUser(sessionId);
// 绑定社交用户(新增) // 绑定社交用户
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
return sessionId; return sessionId;
} }
@ -155,17 +151,6 @@ public class MemberAuthServiceImpl implements MemberAuthService {
return userSessionApi.createUserSession(loginUser, userIp, userAgent); return userSessionApi.createUserSession(loginUser, userIp, userAgent);
} }
@Override
public void socialBind(Long userId, AppAuthSocialBindReqVO reqVO) {
// 绑定社交用户(新增)
socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
}
@Override
public void unbindSocialUser(Long userId, AppAuthSocialUnbindReqVO reqVO) {
socialUserApi.unbindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
}
@Override @Override
public String getSocialAuthorizeUrl(Integer type, String redirectUri) { public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
return socialUserApi.getAuthorizeUrl(type, redirectUri); return socialUserApi.getAuthorizeUrl(type, redirectUri);

View File

@ -37,21 +37,10 @@ public interface SocialUserApi {
*/ */
void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO); void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
/**
*
* {@link ServiceException}
*
* @param type
* @param code
* @param state state
*/
void checkSocialUser(Integer type, String code, String state);
/** /**
* *
* MemberUser AdminUser id * MemberUser AdminUser id
* {@link #checkSocialUser(Integer, String, String)} * {@link ServiceException}
* {@link ServiceException}
* *
* @param userType * @param userType
* @param type * @param type

View File

@ -1,13 +1,14 @@
package cn.iocoder.yudao.module.system.enums.social; package cn.iocoder.yudao.module.system.enums.social;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* *
@ -53,9 +54,6 @@ public enum SocialTypeEnum implements IntArrayValuable {
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray();
public static final List<Integer> WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type, WECHAT_MP.type, WECHAT_OPEN.type,
WECHAT_MINI_PROGRAM.type);
/** /**
* *
*/ */
@ -74,11 +72,4 @@ public enum SocialTypeEnum implements IntArrayValuable {
return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
} }
public static List<Integer> getRelationTypes(Integer type) {
if (WECHAT_ALL.contains(type)) {
return WECHAT_ALL;
}
return ListUtil.toList(type);
}
} }

View File

@ -36,11 +36,6 @@ public class SocialUserApiImpl implements SocialUserApi {
reqDTO.getType(), reqDTO.getUnionId()); reqDTO.getType(), reqDTO.getUnionId());
} }
@Override
public void checkSocialUser(Integer type, String code, String state) {
socialUserService.checkSocialUser(type, code, state);
}
@Override @Override
public Long getBindUserId(Integer userType, Integer type, String code, String state) { public Long getBindUserId(Integer userType, Integer type, String code, String state) {
return socialUserService.getBindUserId(userType, type, code, state); return socialUserService.getBindUserId(userType, type, code, state);

View File

@ -36,7 +36,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
@Api(tags = "管理后台 - 认证") @Api(tags = "管理后台 - 认证")
@RestController @RestController
@RequestMapping("/system") // 暂时不跟 /auth 结尾 @RequestMapping("/system/auth") // 暂时不跟 /auth 结尾
@Validated @Validated
@Slf4j @Slf4j
public class AuthController { public class AuthController {
@ -80,7 +80,7 @@ public class AuthController {
return success(AuthConvert.INSTANCE.convert(user, roleList, menuList)); return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
} }
@GetMapping("list-menus") @GetMapping("/list-menus")
@ApiOperation("获得登录用户的菜单列表") @ApiOperation("获得登录用户的菜单列表")
public CommonResult<List<AuthMenuRespVO>> getMenus() { public CommonResult<List<AuthMenuRespVO>> getMenus() {
// 获得用户拥有的菜单列表 // 获得用户拥有的菜单列表
@ -105,36 +105,22 @@ public class AuthController {
return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri)); return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
} }
@PostMapping("/social-login") @PostMapping("/social-quick-login")
@ApiOperation("社交登录,使用 code 授权码") @ApiOperation("社交快捷登录,使用 code 授权码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AuthLoginRespVO> socialLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) { public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent()); String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果 // 返回结果
return success(AuthLoginRespVO.builder().token(token).build()); return success(AuthLoginRespVO.builder().token(token).build());
} }
@PostMapping("/social-login2") @PostMapping("/social-bind-login")
@ApiOperation("社交登录,使用 code 授权码 + 账号密码") @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AuthLoginRespVO> socialLogin2(@RequestBody @Valid AuthSocialLogin2ReqVO reqVO) { public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent()); String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果 // 返回结果
return success(AuthLoginRespVO.builder().token(token).build()); return success(AuthLoginRespVO.builder().token(token).build());
} }
@PostMapping("/social-bind")
@ApiOperation("社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid AuthSocialBindReqVO reqVO) {
authService.socialBind(getLoginUserId(), reqVO);
return CommonResult.success(true);
}
@DeleteMapping("/social-unbind")
@ApiOperation("取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody AuthSocialUnbindReqVO reqVO) {
socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getUnionId());
return CommonResult.success(true);
}
} }

View File

@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
@ApiModel("管理后台 - 社交登录 Request VO使用 code 授权码 + 账号密码") @ApiModel("管理后台 - 社交绑定登录 Request VO使用 code 授权码 + 账号密码")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AuthSocialLogin2ReqVO { public class AuthSocialBindLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)

View File

@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 社交登录 Request VO使用 code 授权码") @ApiModel("管理后台 - 社交快捷登录 Request VO使用 code 授权码")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AuthSocialLoginReqVO { public class AuthSocialQuickLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.system.controller.admin.socail;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserBindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO;
import cn.iocoder.yudao.module.system.convert.social.SocialUserConvert;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 社交用户")
@RestController
@RequestMapping("/system/social-user")
@Validated
public class SocialUserController {
@Resource
private SocialUserService socialUserService;
@PostMapping("/bind")
@ApiOperation("社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) {
socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO));
return CommonResult.success(true);
}
@DeleteMapping("/unbind")
@ApiOperation("取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody SocialUserUnbindReqVO reqVO) {
socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getOpenid());
return CommonResult.success(true);
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; package cn.iocoder.yudao.module.system.controller.admin.socail.vo;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
@ -17,7 +17,7 @@ import javax.validation.constraints.NotNull;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AuthSocialBindReqVO { public class SocialUserBindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; package cn.iocoder.yudao.module.system.controller.admin.socail.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -12,20 +12,20 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 取消社交绑定 Request VO,使用 code 授权码") @ApiModel("管理后台 - 取消社交绑定 Request VO")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class AuthSocialUnbindReqVO { public class SocialUserUnbindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
@InEnum(SocialTypeEnum.class) @InEnum(SocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空") @NotNull(message = "社交平台的类型不能为空")
private Integer type; private Integer type;
@ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
@NotEmpty(message = "社交的全局编号不能为空") @NotEmpty(message = "社交用户的 openid 不能为空")
private String unionId; private String openid;
} }

View File

@ -96,8 +96,8 @@ public class UserProfileRespVO extends UserBaseVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SocialTypeEnum 枚举类") @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SocialTypeEnum 枚举类")
private Integer type; private Integer type;
@ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
private String unionId; private String openid;
} }

View File

@ -72,9 +72,8 @@ public interface AuthConvert {
return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId())); return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
} }
SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindLoginReqVO reqVO);
SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLogin2ReqVO reqVO);
SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO); SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialQuickLoginReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, AuthSocialUnbindReqVO reqVO);
} }

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.system.convert.social;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserBindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SocialUserConvert {
SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO);
SocialUserUnbindReqDTO convert(Long userId, Integer userType, SocialUserUnbindReqVO reqVO);
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.system.dal.dataobject.social;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
*
* {@link SocialUserDO} UserDO
*
* @author
*/
@TableName(value = "system_social_user_bind", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SocialUserBindDO extends BaseDO {
/**
*
*
* UserDO
*/
private Long userId;
/**
*
*
* {@link UserTypeEnum}
*/
private Integer userType;
/**
*
*
* {@link SocialUserDO#getId()}
*/
private Long socialUserId;
/**
*
*
* {@link SocialUserDO#getType()}
*/
private Integer socialType;
}

View File

@ -1,15 +1,13 @@
package cn.iocoder.yudao.module.system.dal.dataobject.social; package cn.iocoder.yudao.module.system.dal.dataobject.social;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
/** /**
* *
* {@link SocialUserDO#getUserId()} {@link AdminUserDO}
* *
* @author weir * @author weir
*/ */
@ -26,21 +24,10 @@ public class SocialUserDO extends BaseDO {
*/ */
@TableId @TableId
private Long id; private Long id;
/**
*
*/
private Long userId;
/**
*
*
* {@link UserTypeEnum}
*/
private Integer userType;
/** /**
* *
* *
* {@link UserTypeEnum} * {@link SocialTypeEnum}
*/ */
private Integer type; private Integer type;
@ -52,13 +39,6 @@ public class SocialUserDO extends BaseDO {
* token * token
*/ */
private String token; private String token;
/**
*
*
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
* unionId 使 openid
*/
private String unionId;
/** /**
* Token JSON * Token JSON
*/ */
@ -77,6 +57,15 @@ public class SocialUserDO extends BaseDO {
*/ */
private String rawUserInfo; private String rawUserInfo;
/**
* code
*/
private String code;
/**
* state
*/
private String state;
} }

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SocialUserBindMapper extends BaseMapperX<SocialUserBindDO> {
default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) {
delete(new LambdaQueryWrapperX<SocialUserBindDO>()
.eq(SocialUserBindDO::getUserType, userType)
.eq(SocialUserBindDO::getUserId, userId)
.eq(SocialUserBindDO::getSocialType, socialType));
}
default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {
delete(new LambdaQueryWrapperX<SocialUserBindDO>()
.eq(SocialUserBindDO::getUserType, userType)
.eq(SocialUserBindDO::getSocialUserId, socialUserId));
}
default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {
return selectOne(new LambdaQueryWrapperX<SocialUserBindDO>()
.eq(SocialUserBindDO::getUserType, userType)
.eq(SocialUserBindDO::getSocialUserId, socialUserId));
}
default List<SocialUserBindDO> selectListByUserIdAndUserType(Long userId, Integer userType) {
return selectList(new LambdaQueryWrapperX<SocialUserBindDO>()
.eq(SocialUserBindDO::getUserId, userId)
.eq(SocialUserBindDO::getUserType, userType));
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -11,18 +12,17 @@ import java.util.List;
@Mapper @Mapper
public interface SocialUserMapper extends BaseMapperX<SocialUserDO> { public interface SocialUserMapper extends BaseMapperX<SocialUserDO> {
default List<SocialUserDO> selectListByTypeAndUnionId(Integer userType, Collection<Integer> types, String unionId) { default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) {
return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType) return selectOne(new LambdaQueryWrapper<SocialUserDO>()
.in("type", types).eq("union_id", unionId)); .eq(SocialUserDO::getType, type)
.eq(SocialUserDO::getCode, code)
.eq(SocialUserDO::getState, state));
} }
default List<SocialUserDO> selectListByTypeAndUserId(Integer userType, Collection<Integer> types, Long userId) { default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) {
return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType) return selectOne(new LambdaQueryWrapper<SocialUserDO>()
.in("type", types).eq("user_id", userId)); .eq(SocialUserDO::getType, type)
} .eq(SocialUserDO::getOpenid, openid));
default List<SocialUserDO> selectListByUserId(Integer userType, Long userId) {
return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType).eq("user_id", userId));
} }
} }

View File

@ -23,10 +23,6 @@ public interface RedisKeyConstants {
"login_user:%s", // 参数为 sessionId "login_user:%s", // 参数为 sessionId
STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交登陆的授权用户",
"social_auth_user:%d:%s", // 参数为 typecode
STRING, AuthUser.class, Duration.ofDays(1));
RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
"social_auth_state:%s", // 参数为 state "social_auth_state:%s", // 参数为 state
STRING, String.class, Duration.ofHours(24)); // 值为 state STRING, String.class, Duration.ofHours(24)); // 值为 state

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.system.dal.redis.social;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.SOCIAL_AUTH_USER;
/**
* {@link me.zhyd.oauth.model.AuthUser} RedisDAO
*
* @author
*/
@Repository
public class SocialAuthUserRedisDAO {
@Resource
private StringRedisTemplate stringRedisTemplate;
public AuthUser get(Integer type, AuthCallback authCallback) {
String redisKey = formatKey(type, authCallback.getCode());
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), AuthUser.class);
}
public void set(Integer type, AuthCallback authCallback, AuthUser authUser) {
String redisKey = formatKey(type, authCallback.getCode());
stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), SOCIAL_AUTH_USER.getTimeout());
}
private static String formatKey(Integer type, String code) {
return String.format(SOCIAL_AUTH_USER.getKeyTemplate(), type, code);
}
}

View File

@ -18,14 +18,18 @@ public class SecurityConfiguration {
@Override @Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) { public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// 登录的接口,可匿名访问 // 登录的接口
registry.antMatchers(buildAdminApi("/system/login")).anonymous(); registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
// 社交登陆的接口
registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
registry.antMatchers(buildAdminApi("/system/auth/social-bind-login")).permitAll();
// 验证码的接口 // 验证码的接口
registry.antMatchers(buildAdminApi("/system/captcha/**")).anonymous(); registry.antMatchers(buildAdminApi("/system/captcha/**")).permitAll();
// 获得租户编号的接口 // 获得租户编号的接口
registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).anonymous(); registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).permitAll();
// 短信回调 API // 短信回调 API
registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous(); registry.antMatchers(buildAdminApi("/system/sms/callback/**")).permitAll();
} }
}; };

View File

@ -25,31 +25,23 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
/** /**
* 使 code * 使 code
* *
* @param reqVO * @param reqVO
* @param userIp IP * @param userIp IP
* @param userAgent UA * @param userAgent UA
* @return 使 JWT * @return 使 JWT
*/ */
String socialLogin(@Valid AuthSocialLoginReqVO reqVO, String userIp, String userAgent); String socialLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
/** /**
* 使 code + * 使 code +
* *
* @param reqVO * @param reqVO
* @param userIp IP * @param userIp IP
* @param userAgent UA * @param userAgent UA
* @return 使 JWT * @return 使 JWT
*/ */
String socialLogin2(@Valid AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent); String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
/**
* 使 code
*
* @param userId
* @param reqVO
*/
void socialBind(Long userId, @Valid AuthSocialBindReqVO reqVO);
} }

View File

@ -9,9 +9,8 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialQuickLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLoginReqVO;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
@ -22,7 +21,6 @@ import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
@ -82,7 +80,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
throw new UsernameNotFoundException(username); throw new UsernameNotFoundException(username);
} }
// 创建 LoginUser 对象 // 创建 LoginUser 对象
return this.buildLoginUser(user); return buildLoginUser(user);
} }
@Override @Override
@ -92,19 +90,19 @@ public class AdminAuthServiceImpl implements AdminAuthService {
if (user == null) { if (user == null) {
throw new UsernameNotFoundException(String.valueOf(userId)); throw new UsernameNotFoundException(String.valueOf(userId));
} }
this.createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
// 创建 LoginUser 对象 // 创建 LoginUser 对象
return this.buildLoginUser(user); return buildLoginUser(user);
} }
@Override @Override
public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
// 判断验证码是否正确 // 判断验证码是否正确
this.verifyCaptcha(reqVO); verifyCaptcha(reqVO);
// 使用账号密码,进行登录 // 使用账号密码,进行登录
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
// 缓存登陆用户到 Redis 中,返回 sessionId 编号 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
@ -192,7 +190,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
} }
@Override @Override
public String socialLogin(AuthSocialLoginReqVO reqVO, String userIp, String userAgent) { public String socialLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState()); reqVO.getCode(), reqVO.getState());
@ -207,25 +205,18 @@ public class AdminAuthServiceImpl implements AdminAuthService {
} }
// 创建 LoginUser 对象 // 创建 LoginUser 对象
LoginUser loginUser = this.buildLoginUser(user); LoginUser loginUser = buildLoginUser(user);
// 绑定社交用户(更新)
socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
// 缓存登录用户到 Redis 中,返回 sessionId 编号 // 缓存登录用户到 Redis 中,返回 sessionId 编号
return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
} }
@Override @Override
public String socialLogin2(AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) { public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码,进行登录
AuthUser authUser = socialUserService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
Assert.notNull(authUser, "授权用户不为空");
// 使用账号密码,进行登录。 // 使用账号密码,进行登录。
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
// 绑定社交用户(新增) // 绑定社交用户
socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
// 缓存登录用户到 Redis 中,返回 sessionId 编号 // 缓存登录用户到 Redis 中,返回 sessionId 编号
@ -239,12 +230,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
return userSessionService.createUserSession(loginUser, userIp, userAgent); return userSessionService.createUserSession(loginUser, userIp, userAgent);
} }
@Override
public void socialBind(Long userId, AuthSocialBindReqVO reqVO) {
// 绑定社交用户(新增)
socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
}
@Override @Override
public void logout(String token) { public void logout(String token) {
// 查询用户信息 // 查询用户信息

View File

@ -1,11 +1,9 @@
package cn.iocoder.yudao.module.system.service.social; package cn.iocoder.yudao.module.system.service.social;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import me.zhyd.oauth.model.AuthUser;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -28,7 +26,7 @@ public interface SocialUserService {
String getAuthorizeUrl(Integer type, String redirectUri); String getAuthorizeUrl(Integer type, String redirectUri);
/** /**
* *
* {@link ServiceException} * {@link ServiceException}
* *
* @param type {@link SocialTypeEnum} * @param type {@link SocialTypeEnum}
@ -37,17 +35,7 @@ public interface SocialUserService {
* @return * @return
*/ */
@NotNull @NotNull
AuthUser getAuthUser(Integer type, String code, String state); SocialUserDO authSocialUser(Integer type, String code, String state);
/**
* unionId
*
* @param authUser
* @return unionId
*/
default String getAuthUserUnionId(AuthUser authUser) {
return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
}
/** /**
* *
@ -71,25 +59,14 @@ public interface SocialUserService {
* @param userId * @param userId
* @param userType * @param userType
* @param type {@link SocialTypeEnum} * @param type {@link SocialTypeEnum}
* @param unionId unionId * @param openid openid
*/ */
void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId); void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
/**
*
* {@link ServiceException}
*
* @param type
* @param code
* @param state state
*/
void checkSocialUser(Integer type, String code, String state);
/** /**
* *
* MemberUser AdminUser id * MemberUser AdminUser id
* {@link #checkSocialUser(Integer, String, String)} * {@link ServiceException}
* {@link ServiceException}
* *
* @param userType * @param userType
* @param type * @param type

View File

@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.system.service.social; package cn.iocoder.yudao.module.system.service.social;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper; import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import com.google.common.annotations.VisibleForTesting;
import com.xkcoding.justauth.AuthRequestFactory; import com.xkcoding.justauth.AuthRequestFactory;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthCallback;
@ -21,10 +21,11 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@ -42,8 +43,7 @@ public class SocialUserServiceImpl implements SocialUserService {
private AuthRequestFactory authRequestFactory; private AuthRequestFactory authRequestFactory;
@Resource @Resource
private SocialAuthUserRedisDAO authSocialUserRedisDAO; private SocialUserBindMapper socialUserBindMapper;
@Resource @Resource
private SocialUserMapper socialUserMapper; private SocialUserMapper socialUserMapper;
@ -57,171 +57,111 @@ public class SocialUserServiceImpl implements SocialUserService {
} }
@Override @Override
public AuthUser getAuthUser(Integer type, String code, String state) { public SocialUserDO authSocialUser(Integer type, String code, String state) {
AuthCallback authCallback = buildAuthCallback(code, state); // 优先从 DB 中获取,因为 code 有且可以使用一次。
// 从缓存中获取 // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback); SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
if (authUser != null) { if (socialUser != null) {
return authUser; return socialUser;
} }
// 请求获取 // 请求获取
authUser = this.getAuthUser0(type, authCallback); AuthUser authUser = getAuthUser(type, code, state);
// 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 Assert.notNull(authUser, "三方用户不能为空");
authSocialUserRedisDAO.set(type, authCallback, authUser);
return authUser;
}
/** // 保存到 DB 中
* unionId socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
* PC unionId if (socialUser == null) {
* socialUser = new SocialUserDO();
* @param type {@link SocialTypeEnum} }
* @param unionId unionId socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
* @param userType .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
* @return .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
*/ if (socialUser.getId() == null) {
private List<SocialUserDO> getAllSocialUserList(Integer type, String unionId, Integer userType) { socialUserMapper.insert(socialUser);
List<Integer> types = SocialTypeEnum.getRelationTypes(type); } else {
return socialUserMapper.selectListByTypeAndUnionId(userType, types, unionId); socialUserMapper.updateById(socialUser);
}
return socialUser;
} }
@Override @Override
public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) { public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
return socialUserMapper.selectListByUserId(userType, userId); // 获得绑定
List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType);
if (CollUtil.isEmpty(socialUserBinds)) {
return Collections.emptyList();
}
// 获得社交用户
return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId));
} }
@Override @Override
@Transactional
public void bindSocialUser(SocialUserBindReqDTO reqDTO) { public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
// 使用 code 授权 // 获得社交用户
AuthUser authUser = getAuthUser(reqDTO.getType(), reqDTO.getCode(), SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
reqDTO.getState()); Assert.notNull(socialUser, "社交用户不能为空");
if (authUser == null) {
throw exception(SOCIAL_USER_NOT_FOUND); // 社交用户可能之前绑定过别的用户,需要进行解绑
socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId());
// 用户可能之前已经绑定过该社交类型,需要进行解绑
socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(),
socialUser.getType());
// 绑定当前登录的社交用户
SocialUserBindDO socialUserBind = SocialUserBindDO.builder()
.userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
.socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();
socialUserBindMapper.insert(socialUserBind);
} }
// 绑定社交用户(新增) @Override
bindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(), public void unbindSocialUser(Long userId, Integer userType, Integer type, String openid) {
reqDTO.getType(), authUser); // 获得 openid 对应的 SocialUserDO 社交用户
} SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(type, openid);
/**
*
* @param userId
* @param userType
* @param type {@link SocialTypeEnum}
* @param authUser
*/
@Transactional(rollbackFor = Exception.class)
protected void bindSocialUser(Long userId, Integer userType, Integer type, AuthUser authUser) {
// 获得 unionId 对应的 SocialUserDO 列表
String unionId = getAuthUserUnionId(authUser);
List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
// 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑
this.unbindOldSocialUser(userId, userType, type, unionId);
// 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新
// 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId则其它也要跟着修改
// 考虑到 socialUsers 一般比较少,直接 for 循环更新即可
socialUsers.forEach(socialUser -> {
if (Objects.equals(socialUser.getUserId(), userId)) {
return;
}
socialUserMapper.updateById(new SocialUserDO().setId(socialUser.getId()).setUserId(userId));
});
// 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新
SocialUserDO socialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
SocialUserDO saveSocialUser = SocialUserDO.builder() // 新增和更新的通用属性
.token(authUser.getToken().getAccessToken()).rawTokenInfo(toJsonString(authUser.getToken()))
.nickname(authUser.getNickname()).avatar(authUser.getAvatar()).rawUserInfo(toJsonString(authUser.getRawUserInfo()))
.build();
if (socialUser == null) { if (socialUser == null) {
saveSocialUser.setUserId(userId).setUserType(userType)
.setType(type).setOpenid(authUser.getUuid()).setUnionId(unionId);
socialUserMapper.insert(saveSocialUser);
} else {
saveSocialUser.setId(socialUser.getId());
socialUserMapper.updateById(saveSocialUser);
}
}
@Override
public void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId) {
// 获得 unionId 对应的所有 SocialUserDO 社交用户
List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
if (CollUtil.isEmpty(socialUsers)) {
return;
}
// 校验,是否解绑的是非自己的
socialUsers.forEach(socialUser -> {
if (!Objects.equals(socialUser.getUserId(), userId)) {
throw exception(SOCIAL_USER_UNBIND_NOT_SELF);
}
});
// 解绑
socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SocialUserDO::getId));
}
@Override
public void checkSocialUser(Integer type, String code, String state) {
AuthUser authUser = getAuthUser(type, code, state);
if (authUser == null) {
throw exception(SOCIAL_USER_NOT_FOUND); throw exception(SOCIAL_USER_NOT_FOUND);
} }
// 获得对应的社交绑定关系
socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType());
} }
@Override @Override
public Long getBindUserId(Integer userType, Integer type, String code, String state) { public Long getBindUserId(Integer userType, Integer type, String code, String state) {
AuthUser authUser = getAuthUser(type, code, state); // 获得社交用户
if (authUser == null) { SocialUserDO socialUser = authSocialUser(type, code, state);
throw exception(SOCIAL_USER_NOT_FOUND); Assert.notNull(socialUser, "社交用户不能为空");
}
// 如果未绑定 SocialUserDO 用户,则无法自动登录,进行报错 // 如果未绑定的社交用户,则无法自动登录,进行报错
String unionId = getAuthUserUnionId(authUser); SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType,
List<SocialUserDO> socialUsers = getAllSocialUserList(type, unionId, userType); socialUser.getId());
if (CollUtil.isEmpty(socialUsers)) { if (socialUserBind == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND); throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
} }
return socialUsers.get(0).getUserId(); return socialUserBind.getUserId();
}
@VisibleForTesting
public void unbindOldSocialUser(Long userId, Integer userType, Integer type, String newUnionId) {
List<Integer> types = SocialTypeEnum.getRelationTypes(type);
List<SocialUserDO> oldSocialUsers = socialUserMapper.selectListByTypeAndUserId(userType, types, userId);
// 如果新老的 unionId 是一致的,说明无需解绑
if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) {
return;
}
// 解绑
socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SocialUserDO::getId));
} }
/** /**
* *
* *
* @param type * @param type
* @param authCallback * @param code
* @param state state
* @return * @return
*/ */
private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) { private AuthUser getAuthUser(Integer type, String code, String state) {
AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
AuthResponse<?> authResponse = authRequest.login(authCallback); AuthResponse<?> authResponse = authRequest.login(authCallback);
log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback), log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type,
toJsonString(authResponse)); toJsonString(authCallback), toJsonString(authResponse));
if (!authResponse.ok()) { if (!authResponse.ok()) {
throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg()); throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
} }
return (AuthUser) authResponse.getData(); return (AuthUser) authResponse.getData();
} }
private static AuthCallback buildAuthCallback(String code, String state) {
return AuthCallback.builder().code(code).state(state).build();
}
} }

View File

@ -1,165 +1,256 @@
package cn.iocoder.yudao.module.system.service.social; package cn.iocoder.yudao.module.system.service.social;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import com.xkcoding.justauth.AuthRequestFactory; import com.xkcoding.justauth.AuthRequestFactory;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.hutool.core.util.RandomUtil.randomLong;
import static cn.hutool.core.util.RandomUtil.randomString; import static cn.hutool.core.util.RandomUtil.randomString;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
@Import({SocialUserServiceImpl.class, SocialAuthUserRedisDAO.class}) @Import(SocialUserServiceImpl.class)
public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { public class SocialUserServiceTest extends BaseDbAndRedisUnitTest {
@Resource @Resource
private SocialUserServiceImpl socialService; private SocialUserServiceImpl socialUserService;
@Resource @Resource
private SocialUserMapper socialUserMapper; private SocialUserMapper socialUserMapper;
@Resource
private SocialUserBindMapper socialUserBindMapper;
@MockBean @MockBean
private AuthRequestFactory authRequestFactory; private AuthRequestFactory authRequestFactory;
/**
* SocialUserDO
*/
@Test @Test
public void testBindSocialUser_create() { public void testGetAuthorizeUrl() {
// mock 数据 try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {
// 准备参数 // 准备参数
Long userId = randomLongId(); Integer type = SocialTypeEnum.WECHAT_MP.getType();
Integer type = randomEle(SocialTypeEnum.values()).getType(); String redirectUri = "sss";
AuthUser authUser = randomPojo(AuthUser.class); // mock 获得对应的 AuthRequest 实现
AuthRequest authRequest = mock(AuthRequest.class);
when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
// mock 方法 // mock 方法
authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman");
when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy");
// 调用 // 调用
socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); String url = socialUserService.getAuthorizeUrl(type, redirectUri);
// 断言 // 断言
List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId); assertEquals("https://www.iocoder.cn/?redirect_uri=sss", url);
assertEquals(1, socialUsers.size()); }
assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
} }
/**
* SocialUserDO
*/
@Test @Test
public void testBindSocialUser_update() { public void testAuthSocialUser_exists() {
// mock 数据
SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
});
socialUserMapper.insert(dbSocialUser);
// 准备参数 // 准备参数
Long userId = dbSocialUser.getUserId(); Integer type = SocialTypeEnum.GITEE.getType();
Integer type = dbSocialUser.getType(); String code = "tudou";
AuthUser authUser = randomPojo(AuthUser.class); String state = "yuanma";
// mock 方法 // mock 方法
SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
socialUserMapper.insert(socialUser);
// 调用 // 调用
socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); SocialUserDO result = socialUserService.authSocialUser(type, code, state);
// 断言 // 断言
List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId); assertPojoEquals(socialUser, result);
assertEquals(1, socialUsers.size());
assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
} }
/**
*
*/
@Test @Test
public void testBindSocialUser_userId() { public void testAuthSocialUser_authFailure() {
// mock 数据
SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
});
socialUserMapper.insert(dbSocialUser);
// 准备参数 // 准备参数
Long userId = randomLongId(); Integer type = SocialTypeEnum.GITEE.getType();
Integer type = dbSocialUser.getType();
AuthUser authUser = randomPojo(AuthUser.class);
// mock 方法 // mock 方法
AuthRequest authRequest = mock(AuthRequest.class);
when(authRequestFactory.get(anyString())).thenReturn(authRequest);
AuthResponse<?> authResponse = new AuthResponse<>(0, "模拟失败", null);
when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
// 调用并断言
assertServiceException(
() -> socialUserService.authSocialUser(type, randomString(10), randomString(10)),
SOCIAL_USER_AUTH_FAILURE, "模拟失败");
}
@Test
public void testAuthSocialUser_insert() {
// 准备参数
Integer type = SocialTypeEnum.GITEE.getType();
String code = "tudou";
String state = "yuanma";
// mock 方法
AuthRequest authRequest = mock(AuthRequest.class);
when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
AuthUser authUser = randomPojo(AuthUser.class);
AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
// 调用 // 调用
socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); SocialUserDO result = socialUserService.authSocialUser(type, code, state);
// 断言 // 断言
List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId); assertBindSocialUser(type, result, authResponse.getData());
assertEquals(1, socialUsers.size()); assertEquals(code, result.getCode());
assertEquals(state, result.getState());
} }
private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId, @Test
Integer type) { public void testAuthSocialUser_update() {
// 准备参数
Integer type = SocialTypeEnum.GITEE.getType();
String code = "tudou";
String state = "yuanma";
// mock 数据
socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(type).setOpenid("test_openid"));
// mock 方法
AuthRequest authRequest = mock(AuthRequest.class);
when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
AuthUser authUser = randomPojo(AuthUser.class);
authUser.getToken().setOpenId("test_openid");
AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
// 调用
SocialUserDO result = socialUserService.authSocialUser(type, code, state);
// 断言
assertBindSocialUser(type, result, authResponse.getData());
assertEquals(code, result.getCode());
assertEquals(state, result.getState());
}
private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) {
assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken()); assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo()); assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
assertEquals(authUser.getNickname(), socialUser.getNickname()); assertEquals(authUser.getNickname(), socialUser.getNickname());
assertEquals(authUser.getAvatar(), socialUser.getAvatar()); assertEquals(authUser.getAvatar(), socialUser.getAvatar());
assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo()); assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
assertEquals(userId, socialUser.getUserId());
assertEquals(UserTypeEnum.ADMIN.getValue(), socialUser.getUserType());
assertEquals(type, socialUser.getType()); assertEquals(type, socialUser.getType());
assertEquals(authUser.getUuid(), socialUser.getOpenid()); assertEquals(authUser.getUuid(), socialUser.getOpenid());
assertEquals(socialService.getAuthUserUnionId(authUser), socialUser.getUnionId());
} }
/**
* unionId
*/
@Test @Test
public void testUnbindOldSocialUser_no() { public void testGetSocialUserList() {
// mock 数据 Long userId = 1L;
SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> { Integer userType = UserTypeEnum.ADMIN.getValue();
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); // mock 获得社交用户
socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType()); SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType());
}); socialUserMapper.insert(socialUser); // 可被查到
socialUserMapper.insert(oldSocialUser); socialUserMapper.insert(randomPojo(SocialUserDO.class)); // 不可被查到
// 准备参数 // mock 获得绑定
Long userId = oldSocialUser.getUserId(); socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到
Integer type = oldSocialUser.getType(); .setUserId(userId).setUserType(userType).setSocialType(SocialTypeEnum.GITEE.getType())
String newUnionId = oldSocialUser.getUnionId(); .setSocialUserId(socialUser.getId()));
socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到
.setUserId(2L).setUserType(userType).setSocialType(SocialTypeEnum.DINGTALK.getType()));
// 调用 // 调用
socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); List<SocialUserDO> result = socialUserService.getSocialUserList(userId, userType);
// 断言 // 断言
assertEquals(1L, socialUserMapper.selectCount(null).longValue()); assertEquals(1, result.size());
assertPojoEquals(socialUser, result.get(0));
} }
/**
* unionId
*/
@Test @Test
public void testUnbindOldSocialUser_yes() { public void testBindSocialUser() {
// mock 数据
SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
});
socialUserMapper.insert(oldSocialUser);
// 准备参数 // 准备参数
Long userId = oldSocialUser.getUserId(); SocialUserBindReqDTO reqDTO = new SocialUserBindReqDTO()
Integer type = oldSocialUser.getType(); .setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())
String newUnionId = randomString(10); .setType(SocialTypeEnum.GITEE.getType()).setCode("test_code").setState("test_state");
// mock 数据:获得社交用户
SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(reqDTO.getType())
.setCode(reqDTO.getCode()).setState(reqDTO.getState());
socialUserMapper.insert(socialUser);
// mock 数据:用户可能之前已经绑定过该社交类型
socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())
.setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(-1L));
// mock 数据:社交用户可能之前绑定过别的用户
socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserType(UserTypeEnum.ADMIN.getValue())
.setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));
// 调用 // 调用
socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); socialUserService.bindSocialUser(reqDTO);
// 断言 // 断言
assertEquals(0L, socialUserMapper.selectCount(null).longValue()); List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList();
assertEquals(1, socialUserBinds.size());
}
@Test
public void testUnbindSocialUser_success() {
// 准备参数
Long userId = 1L;
Integer userType = UserTypeEnum.ADMIN.getValue();
Integer type = SocialTypeEnum.GITEE.getType();
String openid = "test_openid";
// mock 数据:社交用户
SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setOpenid(openid);
socialUserMapper.insert(socialUser);
// mock 数据:社交绑定关系
SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType)
.setUserId(userId).setSocialType(type);
socialUserBindMapper.insert(socialUserBind);
// 调用
socialUserService.unbindSocialUser(userId, userType, type, openid);
// 断言
assertEquals(0, socialUserBindMapper.selectCount(null).intValue());
}
@Test
public void testUnbindSocialUser_notFound() {
// 调用,并断言
assertServiceException(
() -> socialUserService.unbindSocialUser(randomLong(), UserTypeEnum.ADMIN.getValue(),
SocialTypeEnum.GITEE.getType(), "test_openid"),
SOCIAL_USER_NOT_FOUND);
}
@Test
public void testGetBindUserId() {
// 准备参数
Integer userType = UserTypeEnum.ADMIN.getValue();
Integer type = SocialTypeEnum.GITEE.getType();
String code = "tudou";
String state = "yuanma";
// mock 社交用户
SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
socialUserMapper.insert(socialUser);
// mock 社交用户的绑定
Long userId = randomLong();
SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)
.setSocialType(type).setSocialUserId(socialUser.getId());
socialUserBindMapper.insert(socialUserBind);
// 调用
Long result = socialUserService.getBindUserId(userType, type, code, state);
// 断言
assertEquals(userId, result);
} }
} }

View File

@ -15,6 +15,7 @@ DELETE FROM "system_sms_template";
DELETE FROM "system_sms_log"; DELETE FROM "system_sms_log";
DELETE FROM "system_error_code"; DELETE FROM "system_error_code";
DELETE FROM "system_social_user"; DELETE FROM "system_social_user";
DELETE FROM "system_social_user_bind";
DELETE FROM "system_tenant"; DELETE FROM "system_tenant";
DELETE FROM "system_tenant_package"; DELETE FROM "system_tenant_package";
DELETE FROM "system_sensitive_word"; DELETE FROM "system_sensitive_word";

View File

@ -378,16 +378,15 @@ CREATE TABLE IF NOT EXISTS "system_error_code" (
CREATE TABLE IF NOT EXISTS "system_social_user" ( CREATE TABLE IF NOT EXISTS "system_social_user" (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL DEFAULT '0',
"type" tinyint NOT NULL, "type" tinyint NOT NULL,
"openid" varchar(32) NOT NULL, "openid" varchar(64) NOT NULL,
"token" varchar(256) DEFAULT NULL, "token" varchar(256) DEFAULT NULL,
"union_id" varchar(32) NOT NULL,
"raw_token_info" varchar(1024) NOT NULL, "raw_token_info" varchar(1024) NOT NULL,
"nickname" varchar(32) NOT NULL, "nickname" varchar(32) NOT NULL,
"avatar" varchar(255) DEFAULT NULL, "avatar" varchar(255) DEFAULT NULL,
"raw_user_info" varchar(1024) NOT NULL, "raw_user_info" varchar(1024) NOT NULL,
"code" varchar(64) NOT NULL,
"state" varchar(64),
"creator" varchar(64) DEFAULT '', "creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '', "updater" varchar(64) DEFAULT '',
@ -396,6 +395,20 @@ CREATE TABLE IF NOT EXISTS "system_social_user" (
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT ''; ) COMMENT '';
CREATE TABLE IF NOT EXISTS "system_social_user_bind" (
"id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"social_type" tinyint NOT NULL,
"social_user_id" number NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '';
CREATE TABLE IF NOT EXISTS "system_tenant" ( CREATE TABLE IF NOT EXISTS "system_tenant" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL, "name" varchar(63) NOT NULL,

View File

@ -186,10 +186,6 @@ yudao:
justauth: justauth:
enabled: true enabled: true
type: type:
GITEE: # Gitee
client-id: ee61f0374a4c6c404a8717094caa7a410d76950e45ff60348015830c519ba5c1
client-secret: 7c044a5671be3b051414db0cf2cec6ad702dd298d2416ba24ceaf608e6fa26f9
ignore-check-redirect-uri: true
DINGTALK: # 钉钉 DINGTALK: # 钉钉
client-id: dingvrnreaje3yqvzhxg client-id: dingvrnreaje3yqvzhxg
client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI

View File

@ -196,10 +196,6 @@ yudao:
justauth: justauth:
enabled: true enabled: true
type: type:
GITEE: # Gitee
client-id: ee61f0374a4c6c404a8717094caa7a410d76950e45ff60348015830c519ba5c1
client-secret: 7c044a5671be3b051414db0cf2cec6ad702dd298d2416ba24ceaf608e6fa26f9
ignore-check-redirect-uri: true
DINGTALK: # 钉钉 DINGTALK: # 钉钉
client-id: dingvrnreaje3yqvzhxg client-id: dingvrnreaje3yqvzhxg
client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI

View File

@ -9,7 +9,7 @@ export function login(username, password, code, uuid) {
uuid uuid
} }
return request({ return request({
url: '/system/login', url: '/system/auth/login',
method: 'post', method: 'post',
data: data data: data
}) })
@ -18,7 +18,7 @@ export function login(username, password, code, uuid) {
// 获取用户详细信息 // 获取用户详细信息
export function getInfo() { export function getInfo() {
return request({ return request({
url: '/system/get-permission-info', url: '/system/auth/get-permission-info',
method: 'get' method: 'get'
}) })
} }
@ -43,15 +43,15 @@ export function getCodeImg() {
// 社交授权的跳转 // 社交授权的跳转
export function socialAuthRedirect(type, redirectUri) { export function socialAuthRedirect(type, redirectUri) {
return request({ return request({
url: '/system/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri, url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri,
method: 'get' method: 'get'
}) })
} }
// 社交登录,使用 code 授权码 // 社交快捷登录,使用 code 授权码
export function socialLogin(type, code, state) { export function socialQuickLogin(type, code, state) {
return request({ return request({
url: '/system/social-login', url: '/system/auth/social-quick-login',
method: 'post', method: 'post',
data: { data: {
type, type,
@ -61,10 +61,10 @@ export function socialLogin(type, code, state) {
}) })
} }
// 社交登录,使用 code 授权码 + + 账号密码 // 社交绑定登录,使用 code 授权码 + + 账号密码
export function socialLogin2(type, code, state, username, password) { export function socialBindLogin(type, code, state, username, password) {
return request({ return request({
url: '/system/social-login2', url: '/system/auth/social-bind-login',
method: 'post', method: 'post',
data: { data: {
type, type,
@ -75,28 +75,3 @@ export function socialLogin2(type, code, state, username, password) {
} }
}) })
} }
// 社交绑定,使用 code 授权码
export function socialBind(type, code, state) {
return request({
url: '/system/social-bind',
method: 'post',
data: {
type,
code,
state,
}
})
}
// 取消社交绑定
export function socialUnbind(type, unionId) {
return request({
url: '/system/social-unbind',
method: 'delete',
data: {
type,
unionId
}
})
}

View File

@ -3,7 +3,7 @@ import request from '@/utils/request'
// 获取路由 // 获取路由
export const getRouters = () => { export const getRouters = () => {
return request({ return request({
url: '/system/list-menus', url: '/system/auth/list-menus',
method: 'get' method: 'get'
}) })
} }

View File

@ -0,0 +1,26 @@
import request from "@/utils/request";
// 社交绑定,使用 code 授权码
export function socialBind(type, code, state) {
return request({
url: '/system/social-user/bind',
method: 'post',
data: {
type,
code,
state,
}
})
}
// 取消社交绑定
export function socialUnbind(type, openid) {
return request({
url: '/system/social-user/unbind',
method: 'delete',
data: {
type,
openid
}
})
}

View File

@ -1,4 +1,4 @@
import {login, logout, getInfo, socialLogin, socialLogin2} from '@/api/login' import {login, logout, getInfo, socialQuickLogin, socialBindLogin} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken } from '@/utils/auth'
const user = { const user = {
@ -57,7 +57,7 @@ const user = {
const state = userInfo.state const state = userInfo.state
const type = userInfo.type const type = userInfo.type
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
socialLogin(type, code, state).then(res => { socialQuickLogin(type, code, state).then(res => {
res = res.data; res = res.data;
setToken(res.token) setToken(res.token)
commit('SET_TOKEN', res.token) commit('SET_TOKEN', res.token)
@ -76,7 +76,7 @@ const user = {
const username = userInfo.username.trim() const username = userInfo.username.trim()
const password = userInfo.password const password = userInfo.password
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
socialLogin2(type, code, state, username, password).then(res => { socialBindLogin(type, code, state, username, password).then(res => {
res = res.data; res = res.data;
setToken(res.token) setToken(res.token)
commit('SET_TOKEN', res.token) commit('SET_TOKEN', res.token)

View File

@ -71,12 +71,6 @@ export const InfraApiErrorLogProcessStatusEnum = {
* 用户的社交平台的类型枚举 * 用户的社交平台的类型枚举
*/ */
export const SystemUserSocialTypeEnum = { export const SystemUserSocialTypeEnum = {
// GITEE: {
// title: "码云",
// type: 10,
// source: "gitee",
// img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/gitee.png",
// },
DINGTALK: { DINGTALK: {
title: "钉钉", title: "钉钉",
type: 20, type: 20,

View File

@ -176,7 +176,6 @@ export default {
}); });
}, },
doSocialLogin(socialTypeEnum) { doSocialLogin(socialTypeEnum) {
// console.log("Oauth...%o", socialTypeEnum.code);
// //
this.loading = true; this.loading = true;
// redirectUri // redirectUri

View File

@ -7,7 +7,7 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="left" > <el-table-column label="操作" align="left" >
<template slot-scope="scope"> <template slot-scope="scope">
<div v-if="scope.row.unionId"> <div v-if="scope.row.openid">
已绑定 已绑定
<el-button size="large" type="text" @click="unbind(scope.row)">()</el-button> <el-button size="large" type="text" @click="unbind(scope.row)">()</el-button>
</div> </div>
@ -23,7 +23,8 @@
<script> <script>
import {SystemUserSocialTypeEnum} from "@/utils/constants"; import {SystemUserSocialTypeEnum} from "@/utils/constants";
import {socialAuthRedirect, socialBind, socialUnbind} from "@/api/login"; import {socialAuthRedirect} from "@/api/login";
import {socialBind, socialUnbind} from "@/api/system/socialUser";
export default { export default {
props: { props: {
@ -50,7 +51,7 @@ export default {
if (this.user.socialUsers) { if (this.user.socialUsers) {
for (const j in this.user.socialUsers) { for (const j in this.user.socialUsers) {
if (socialUser.type === this.user.socialUsers[j].type) { if (socialUser.type === this.user.socialUsers[j].type) {
socialUser.unionId = this.user.socialUsers[j].unionId; socialUser.openid = this.user.socialUsers[j].openid;
break; break;
} }
} }
@ -86,9 +87,9 @@ export default {
}); });
}, },
unbind(socialUser) { unbind(socialUser) {
socialUnbind(socialUser.type, socialUser.unionId).then(resp => { socialUnbind(socialUser.type, socialUser.openid).then(resp => {
this.$modal.msgSuccess("解绑成功"); this.$modal.msgSuccess("解绑成功");
socialUser.unionId = undefined; socialUser.openid = undefined;
}); });
}, },
close() { close() {