Commit 0bf5b20fd7c3cf27c4f0e5bf07ca14ec8afed832

Authored by 芋道源码
Committed by Gitee
2 parents 78275a16 c4020779

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

Merge pull request !137 from 芋道源码/feature/1.6.2
Showing 46 changed files with 645 additions and 586 deletions
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.social.config;
2 2  
  3 +import cn.hutool.core.util.ReflectUtil;
3 4 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
  5 +import com.xkcoding.http.HttpUtil;
  6 +import com.xkcoding.http.support.hutool.HutoolImpl;
4 7 import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
5 8 import lombok.extern.slf4j.Slf4j;
6 9 import me.zhyd.oauth.cache.AuthStateCache;
... ... @@ -23,6 +26,9 @@ public class YudaoSocialAutoConfiguration {
23 26 @Bean
24 27 @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
25 28 public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
  29 + // 需要修改 HttpUtil 使用的实现,避免类报错
  30 + HttpUtil.setHttp(new HutoolImpl());
  31 + // 创建 YudaoAuthRequestFactory
26 32 return new YudaoAuthRequestFactory(properties, authStateCache);
27 33 }
28 34  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
... ... @@ -23,7 +23,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
23 23  
24 24 @Api(tags = "用户 APP - 认证")
25 25 @RestController
26   -@RequestMapping("/member/")
  26 +@RequestMapping("/member/auth")
27 27 @Validated
28 28 @Slf4j
29 29 public class AppAuthController {
... ... @@ -33,7 +33,6 @@ public class AppAuthController {
33 33  
34 34 @PostMapping("/login")
35 35 @ApiOperation("使用手机 + 密码登录")
36   - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
37 36 public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
38 37 String token = authService.login(reqVO, getClientIP(), getUserAgent());
39 38 // 返回结果
... ... @@ -42,7 +41,6 @@ public class AppAuthController {
42 41  
43 42 @PostMapping("/sms-login")
44 43 @ApiOperation("使用手机 + 验证码登录")
45   - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
46 44 public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
47 45 String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
48 46 // 返回结果
... ... @@ -51,7 +49,6 @@ public class AppAuthController {
51 49  
52 50 @PostMapping("/send-sms-code")
53 51 @ApiOperation(value = "发送手机验证码")
54   - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
55 52 public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
56 53 authService.sendSmsCode(getLoginUserId(), reqVO);
57 54 return success(true);
... ... @@ -60,7 +57,6 @@ public class AppAuthController {
60 57 @PostMapping("/reset-password")
61 58 @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
62 59 @PreAuthenticated
63   - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
64 60 public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
65 61 authService.resetPassword(reqVO);
66 62 return success(true);
... ... @@ -87,35 +83,18 @@ public class AppAuthController {
87 83 return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
88 84 }
89 85  
90   - @PostMapping("/social-login")
91   - @ApiOperation(value = "社交登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
92   - public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
93   - String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
  86 + @PostMapping("/social-quick-login")
  87 + @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户")
  88 + public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) {
  89 + String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent());
94 90 return success(AppAuthLoginRespVO.builder().token(token).build());
95 91 }
96 92  
97   - @PostMapping("/social-login2")
98   - @ApiOperation(value = "社交登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
99   - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
100   - public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) {
101   - String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
  93 + @PostMapping("/social-bind-login")
  94 + @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定")
  95 + public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) {
  96 + String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
102 97 return success(AppAuthLoginRespVO.builder().token(token).build());
103 98 }
104 99  
105   - @PostMapping("/social-bind")
106   - @ApiOperation(value = "社交绑定,使用 code 授权码", notes = "使用在用户已经登录的情况下")
107   - @PreAuthenticated
108   - public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
109   - authService.socialBind(getLoginUserId(), reqVO);
110   - return CommonResult.success(true);
111   - }
112   -
113   - @DeleteMapping("/social-unbind")
114   - @ApiOperation("取消社交绑定")
115   - @PreAuthenticated
116   - public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
117   - authService.unbindSocialUser(getLoginUserId(), reqVO);
118   - return CommonResult.success(true);
119   - }
120   -
121 100 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLogin2ReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindLoginReqVO.java
... ... @@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty;
14 14 import javax.validation.constraints.NotNull;
15 15 import javax.validation.constraints.Pattern;
16 16  
17   -@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码 + 账号密码")
  17 +@ApiModel("用户 APP - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码")
18 18 @Data
19 19 @NoArgsConstructor
20 20 @AllArgsConstructor
21 21 @Builder
22   -public class AppAuthSocialLogin2ReqVO {
  22 +public class AppAuthSocialBindLoginReqVO {
23 23  
24 24 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
25 25 @InEnum(SocialTypeEnum.class)
... ... @@ -45,5 +45,4 @@ public class AppAuthSocialLogin2ReqVO {
45 45 @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
46 46 private String smsCode;
47 47  
48   -
49 48 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialQuickLoginReqVO.java
... ... @@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
12 12 import javax.validation.constraints.NotEmpty;
13 13 import javax.validation.constraints.NotNull;
14 14  
15   -@ApiModel("用户 APP - 社交绑定 Request VO,使用 code 授权码")
  15 +@ApiModel("用户 APP - 社交快捷登录 Request VO,使用 code 授权码")
16 16 @Data
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AppAuthSocialBindReqVO {
  20 +public class AppAuthSocialQuickLoginReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.social;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
  7 +import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert;
  8 +import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
  9 +import io.swagger.annotations.Api;
  10 +import io.swagger.annotations.ApiOperation;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.*;
  13 +
  14 +import javax.annotation.Resource;
  15 +import javax.validation.Valid;
  16 +
  17 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  18 +
  19 +@Api(tags = "用户 App - 社交用户")
  20 +@RestController
  21 +@RequestMapping("/system/social-user")
  22 +@Validated
  23 +public class AppSocialUserController {
  24 +
  25 + @Resource
  26 + private SocialUserApi socialUserApi;
  27 +
  28 + @PostMapping("/bind")
  29 + @ApiOperation("社交绑定,使用 code 授权码")
  30 + public CommonResult<Boolean> socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) {
  31 + socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
  32 + return CommonResult.success(true);
  33 + }
  34 +
  35 + @DeleteMapping("/unbind")
  36 + @ApiOperation("取消社交绑定")
  37 + public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
  38 + socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
  39 + return CommonResult.success(true);
  40 + }
  41 +
  42 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java
1   -package cn.iocoder.yudao.module.member.controller.app.auth.vo;
  1 +package cn.iocoder.yudao.module.member.controller.app.social.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
... ... @@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
12 12 import javax.validation.constraints.NotEmpty;
13 13 import javax.validation.constraints.NotNull;
14 14  
15   -@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码")
  15 +@ApiModel("用户 APP - 社交绑定 Request VO,使用 code 授权码")
16 16 @Data
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AppAuthSocialLoginReqVO {
  20 +public class AppSocialUserBindReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialUnbindReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java
1   -package cn.iocoder.yudao.module.member.controller.app.auth.vo;
  1 +package cn.iocoder.yudao.module.member.controller.app.social.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
... ... @@ -12,20 +12,20 @@ import lombok.NoArgsConstructor;
12 12 import javax.validation.constraints.NotEmpty;
13 13 import javax.validation.constraints.NotNull;
14 14  
15   -@ApiModel("用户 APP - 取消社交绑定 Request VO,使用 code 授权码")
  15 +@ApiModel("用户 APP - 取消社交绑定 Request VO")
16 16 @Data
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AppAuthSocialUnbindReqVO {
  20 +public class AppSocialUserUnbindReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
24 24 @NotNull(message = "社交平台的类型不能为空")
25 25 private Integer type;
26 26  
27   - @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
28   - @NotEmpty(message = "社交的全局编号不能为空")
29   - private String unionId;
  27 + @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
  28 + @NotEmpty(message = "社交用户的 openid 不能为空")
  29 + private String openid;
30 30  
31 31 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.convert.auth;
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.security.core.LoginUser;
5 5 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
  6 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
6 7 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
7 8 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
8 9 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
... ... @@ -26,10 +27,9 @@ public interface AuthConvert {
26 27 return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
27 28 }
28 29  
29   - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindReqVO reqVO);
30   - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLogin2ReqVO reqVO);
31   - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO);
32   - SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO);
  30 + SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO);
  31 + SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO);
  32 + SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
33 33  
34 34 SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO);
35 35 SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.convert.social;
  2 +
  3 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
  4 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  6 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +@Mapper
  11 +public interface SocialUserConvert {
  12 +
  13 + SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
  14 +
  15 + SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO);
  16 +
  17 + SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
  18 +
  19 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.service.auth;
2 2  
3 3 import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
4 4 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
  5 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
5 7  
6 8 import javax.validation.Valid;
7 9  
... ... @@ -43,7 +45,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
43 45 * @param userAgent 用户 UA
44 46 * @return 身份令牌,使用 JWT 方式
45 47 */
46   - String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
  48 + String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
47 49  
48 50 /**
49 51 * 社交登录,使用 手机号 + 手机验证码
... ... @@ -53,23 +55,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
53 55 * @param userAgent 用户 UA
54 56 * @return 身份令牌,使用 JWT 方式
55 57 */
56   - String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
57   -
58   - /**
59   - * 社交绑定,使用 code 授权码
60   - *
61   - * @param userId 用户编号
62   - * @param reqVO 绑定信息
63   - */
64   - void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO);
65   -
66   - /**
67   - * 取消社交绑定
68   - *
69   - * @param userId 用户编号
70   - * @param reqVO 解绑信息
71   - */
72   - void unbindSocialUser(Long userId, @Valid AppAuthSocialUnbindReqVO reqVO);
  58 + String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
73 59  
74 60 /**
75 61 * 获得社交认证 URL
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
... ... @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
8 8 import cn.iocoder.yudao.framework.security.core.LoginUser;
9 9 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
10 10 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
  11 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
  12 +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
11 13 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
12 14 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
13 15 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
... ... @@ -108,7 +110,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
108 110 }
109 111  
110 112 @Override
111   - public String socialLogin(AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
  113 + public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
112 114 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
113 115 Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
114 116 reqVO.getCode(), reqVO.getState());
... ... @@ -125,25 +127,19 @@ public class MemberAuthServiceImpl implements MemberAuthService {
125 127 // 创建 LoginUser 对象
126 128 LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
127 129  
128   - // 绑定社交用户(更新)
129   - socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
130   -
131 130 // 缓存登录用户到 Redis 中,返回 sessionId 编号
132 131 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
133 132 }
134 133  
135 134 @Override
136   - public String socialLogin2(AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
137   - // 校验社交平台的认证信息是否正确
138   - socialUserApi.checkSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
139   -
  135 + public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
140 136 // 使用手机号、手机验证码登录
141 137 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
142 138 .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
143 139 String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
144 140 LoginUser loginUser = userSessionApi.getLoginUser(sessionId);
145 141  
146   - // 绑定社交用户(新增)
  142 + // 绑定社交用户
147 143 socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
148 144 return sessionId;
149 145 }
... ... @@ -156,17 +152,6 @@ public class MemberAuthServiceImpl implements MemberAuthService {
156 152 }
157 153  
158 154 @Override
159   - public void socialBind(Long userId, AppAuthSocialBindReqVO reqVO) {
160   - // 绑定社交用户(新增)
161   - socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
162   - }
163   -
164   - @Override
165   - public void unbindSocialUser(Long userId, AppAuthSocialUnbindReqVO reqVO) {
166   - socialUserApi.unbindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
167   - }
168   -
169   - @Override
170 155 public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
171 156 return socialUserApi.getAuthorizeUrl(type, redirectUri);
172 157 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
... ... @@ -38,20 +38,9 @@ public interface SocialUserApi {
38 38 void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
39 39  
40 40 /**
41   - * 校验社交用户的认证信息是否正确
42   - * 如果校验不通过,则抛出 {@link ServiceException} 业务异常
43   - *
44   - * @param type 社交平台的类型
45   - * @param code 授权码
46   - * @param state state
47   - */
48   - void checkSocialUser(Integer type, String code, String state);
49   -
50   - /**
51 41 * 获得社交用户的绑定用户编号
52 42 * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
53   - * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。
54   - * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
  43 + * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
55 44 *
56 45 * @param userType 用户类型
57 46 * @param type 社交平台的类型
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
1 1 package cn.iocoder.yudao.module.system.enums.social;
2 2  
3   -import cn.hutool.core.collection.ListUtil;
4 3 import cn.hutool.core.util.ArrayUtil;
5 4 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
6 5 import lombok.AllArgsConstructor;
7 6 import lombok.Getter;
8 7  
9 8 import java.util.Arrays;
10   -import java.util.List;
  9 +import java.util.Collection;
  10 +import java.util.Set;
  11 +import java.util.stream.Collectors;
11 12  
12 13 /**
13 14 * 社交平台的类型枚举
... ... @@ -53,9 +54,6 @@ public enum SocialTypeEnum implements IntArrayValuable {
53 54  
54 55 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray();
55 56  
56   - public static final List<Integer> WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type, WECHAT_MP.type, WECHAT_OPEN.type,
57   - WECHAT_MINI_PROGRAM.type);
58   -
59 57 /**
60 58 * 类型
61 59 */
... ... @@ -74,11 +72,4 @@ public enum SocialTypeEnum implements IntArrayValuable {
74 72 return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
75 73 }
76 74  
77   - public static List<Integer> getRelationTypes(Integer type) {
78   - if (WECHAT_ALL.contains(type)) {
79   - return WECHAT_ALL;
80   - }
81   - return ListUtil.toList(type);
82   - }
83   -
84 75 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
... ... @@ -37,11 +37,6 @@ public class SocialUserApiImpl implements SocialUserApi {
37 37 }
38 38  
39 39 @Override
40   - public void checkSocialUser(Integer type, String code, String state) {
41   - socialUserService.checkSocialUser(type, code, state);
42   - }
43   -
44   - @Override
45 40 public Long getBindUserId(Integer userType, Integer type, String code, String state) {
46 41 return socialUserService.getBindUserId(userType, type, code, state);
47 42 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
... ... @@ -36,7 +36,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
36 36  
37 37 @Api(tags = "管理后台 - 认证")
38 38 @RestController
39   -@RequestMapping("/system") // 暂时不跟 /auth 结尾
  39 +@RequestMapping("/system/auth") // 暂时不跟 /auth 结尾
40 40 @Validated
41 41 @Slf4j
42 42 public class AuthController {
... ... @@ -80,7 +80,7 @@ public class AuthController {
80 80 return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
81 81 }
82 82  
83   - @GetMapping("list-menus")
  83 + @GetMapping("/list-menus")
84 84 @ApiOperation("获得登录用户的菜单列表")
85 85 public CommonResult<List<AuthMenuRespVO>> getMenus() {
86 86 // 获得用户拥有的菜单列表
... ... @@ -105,36 +105,22 @@ public class AuthController {
105 105 return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
106 106 }
107 107  
108   - @PostMapping("/social-login")
109   - @ApiOperation("社交登录,使用 code 授权码")
  108 + @PostMapping("/social-quick-login")
  109 + @ApiOperation("社交快捷登录,使用 code 授权码")
110 110 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
111   - public CommonResult<AuthLoginRespVO> socialLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) {
  111 + public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) {
112 112 String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
113 113 // 返回结果
114 114 return success(AuthLoginRespVO.builder().token(token).build());
115 115 }
116 116  
117   - @PostMapping("/social-login2")
118   - @ApiOperation("社交登录,使用 code 授权码 + 账号密码")
  117 + @PostMapping("/social-bind-login")
  118 + @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码")
119 119 @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
120   - public CommonResult<AuthLoginRespVO> socialLogin2(@RequestBody @Valid AuthSocialLogin2ReqVO reqVO) {
121   - String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
  120 + public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) {
  121 + String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent());
122 122 // 返回结果
123 123 return success(AuthLoginRespVO.builder().token(token).build());
124 124 }
125 125  
126   - @PostMapping("/social-bind")
127   - @ApiOperation("社交绑定,使用 code 授权码")
128   - public CommonResult<Boolean> socialBind(@RequestBody @Valid AuthSocialBindReqVO reqVO) {
129   - authService.socialBind(getLoginUserId(), reqVO);
130   - return CommonResult.success(true);
131   - }
132   -
133   - @DeleteMapping("/social-unbind")
134   - @ApiOperation("取消社交绑定")
135   - public CommonResult<Boolean> socialUnbind(@RequestBody AuthSocialUnbindReqVO reqVO) {
136   - socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getUnionId());
137   - return CommonResult.success(true);
138   - }
139   -
140 126 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLogin2ReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java
... ... @@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty;
14 14 import javax.validation.constraints.NotNull;
15 15 import javax.validation.constraints.Pattern;
16 16  
17   -@ApiModel("管理后台 - 社交登录 Request VO,使用 code 授权码 + 账号密码")
  17 +@ApiModel("管理后台 - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码")
18 18 @Data
19 19 @NoArgsConstructor
20 20 @AllArgsConstructor
21 21 @Builder
22   -public class AuthSocialLogin2ReqVO {
  22 +public class AuthSocialBindLoginReqVO {
23 23  
24 24 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
25 25 @InEnum(SocialTypeEnum.class)
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLoginReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java
... ... @@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
12 12 import javax.validation.constraints.NotEmpty;
13 13 import javax.validation.constraints.NotNull;
14 14  
15   -@ApiModel("管理后台 - 社交登录 Request VO,使用 code 授权码")
  15 +@ApiModel("管理后台 - 社交快捷登录 Request VO,使用 code 授权码")
16 16 @Data
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AuthSocialLoginReqVO {
  20 +public class AuthSocialQuickLoginReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.socail;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserBindReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO;
  7 +import cn.iocoder.yudao.module.system.convert.social.SocialUserConvert;
  8 +import cn.iocoder.yudao.module.system.service.social.SocialUserService;
  9 +import io.swagger.annotations.Api;
  10 +import io.swagger.annotations.ApiOperation;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.*;
  13 +
  14 +import javax.annotation.Resource;
  15 +import javax.validation.Valid;
  16 +
  17 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  18 +
  19 +@Api(tags = "管理后台 - 社交用户")
  20 +@RestController
  21 +@RequestMapping("/system/social-user")
  22 +@Validated
  23 +public class SocialUserController {
  24 +
  25 + @Resource
  26 + private SocialUserService socialUserService;
  27 +
  28 + @PostMapping("/bind")
  29 + @ApiOperation("社交绑定,使用 code 授权码")
  30 + public CommonResult<Boolean> socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) {
  31 + socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO));
  32 + return CommonResult.success(true);
  33 + }
  34 +
  35 + @DeleteMapping("/unbind")
  36 + @ApiOperation("取消社交绑定")
  37 + public CommonResult<Boolean> socialUnbind(@RequestBody SocialUserUnbindReqVO reqVO) {
  38 + socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getOpenid());
  39 + return CommonResult.success(true);
  40 + }
  41 +
  42 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java
1   -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
  1 +package cn.iocoder.yudao.module.system.controller.admin.socail.vo;
2 2  
3 3 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
4 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
... ... @@ -17,7 +17,7 @@ import javax.validation.constraints.NotNull;
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AuthSocialBindReqVO {
  20 +public class SocialUserBindReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialUnbindReqVO.java renamed to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java
1   -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth;
  1 +package cn.iocoder.yudao.module.system.controller.admin.socail.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
... ... @@ -12,20 +12,20 @@ import lombok.NoArgsConstructor;
12 12 import javax.validation.constraints.NotEmpty;
13 13 import javax.validation.constraints.NotNull;
14 14  
15   -@ApiModel("管理后台 - 取消社交绑定 Request VO,使用 code 授权码")
  15 +@ApiModel("管理后台 - 取消社交绑定 Request VO")
16 16 @Data
17 17 @NoArgsConstructor
18 18 @AllArgsConstructor
19 19 @Builder
20   -public class AuthSocialUnbindReqVO {
  20 +public class SocialUserUnbindReqVO {
21 21  
22 22 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
23 23 @InEnum(SocialTypeEnum.class)
24 24 @NotNull(message = "社交平台的类型不能为空")
25 25 private Integer type;
26 26  
27   - @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
28   - @NotEmpty(message = "社交的全局编号不能为空")
29   - private String unionId;
  27 + @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
  28 + @NotEmpty(message = "社交用户的 openid 不能为空")
  29 + private String openid;
30 30  
31 31 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java
... ... @@ -96,8 +96,8 @@ public class UserProfileRespVO extends UserBaseVO {
96 96 @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SocialTypeEnum 枚举类")
97 97 private Integer type;
98 98  
99   - @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
100   - private String unionId;
  99 + @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
  100 + private String openid;
101 101  
102 102 }
103 103  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
... ... @@ -72,9 +72,8 @@ public interface AuthConvert {
72 72 return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
73 73 }
74 74  
75   - SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindReqVO reqVO);
76   - SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLogin2ReqVO reqVO);
77   - SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO);
78   - SocialUserUnbindReqDTO convert(Long userId, Integer userType, AuthSocialUnbindReqVO reqVO);
  75 + SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindLoginReqVO reqVO);
  76 +
  77 + SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialQuickLoginReqVO reqVO);
79 78  
80 79 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.convert.social;
  2 +
  3 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  4 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
  5 +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserBindReqVO;
  6 +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +@Mapper
  11 +public interface SocialUserConvert {
  12 +
  13 + SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
  14 +
  15 + SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO);
  16 +
  17 + SocialUserUnbindReqDTO convert(Long userId, Integer userType, SocialUserUnbindReqVO reqVO);
  18 +
  19 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.social;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import com.baomidou.mybatisplus.annotation.TableName;
  6 +import lombok.*;
  7 +
  8 +/**
  9 + * 社交用户的绑定
  10 + * 即 {@link SocialUserDO} 与 UserDO 的关联表
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@TableName(value = "system_social_user_bind", autoResultMap = true)
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@Builder
  18 +@NoArgsConstructor
  19 +@AllArgsConstructor
  20 +public class SocialUserBindDO extends BaseDO {
  21 +
  22 + /**
  23 + * 关联的用户编号
  24 + *
  25 + * 关联 UserDO 的编号
  26 + */
  27 + private Long userId;
  28 + /**
  29 + * 用户类型
  30 + *
  31 + * 枚举 {@link UserTypeEnum}
  32 + */
  33 + private Integer userType;
  34 +
  35 + /**
  36 + * 社交平台的用户编号
  37 + *
  38 + * 关联 {@link SocialUserDO#getId()}
  39 + */
  40 + private Long socialUserId;
  41 + /**
  42 + * 社交平台的类型
  43 + *
  44 + * 冗余 {@link SocialUserDO#getType()}
  45 + */
  46 + private Integer socialType;
  47 +
  48 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java
1 1 package cn.iocoder.yudao.module.system.dal.dataobject.social;
2 2  
3   -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
4   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
6 5 import com.baomidou.mybatisplus.annotation.TableId;
7 6 import com.baomidou.mybatisplus.annotation.TableName;
8 7 import lombok.*;
9 8  
10 9 /**
11 10 * 社交用户
12   - * 通过 {@link SocialUserDO#getUserId()} 关联到对应的 {@link AdminUserDO}
13 11 *
14 12 * @author weir
15 13 */
... ... @@ -27,20 +25,9 @@ public class SocialUserDO extends BaseDO {
27 25 @TableId
28 26 private Long id;
29 27 /**
30   - * 关联的用户编号
31   - */
32   - private Long userId;
33   - /**
34   - * 用户类型
35   - *
36   - * 枚举 {@link UserTypeEnum}
37   - */
38   - private Integer userType;
39   -
40   - /**
41 28 * 社交平台的类型
42 29 *
43   - * 枚举 {@link UserTypeEnum}
  30 + * 枚举 {@link SocialTypeEnum}
44 31 */
45 32 private Integer type;
46 33  
... ... @@ -53,13 +40,6 @@ public class SocialUserDO extends BaseDO {
53 40 */
54 41 private String token;
55 42 /**
56   - * 社交的全局编号
57   - *
58   - * 例如说,微信平台的 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
59   - * 如果没有 unionId 的平台,直接使用 openid 作为该字段的值
60   - */
61   - private String unionId;
62   - /**
63 43 * 原始 Token 数据,一般是 JSON 格式
64 44 */
65 45 private String rawTokenInfo;
... ... @@ -77,6 +57,15 @@ public class SocialUserDO extends BaseDO {
77 57 */
78 58 private String rawUserInfo;
79 59  
  60 + /**
  61 + * 最后一次的认证 code
  62 + */
  63 + private String code;
  64 + /**
  65 + * 最后一次的认证 state
  66 + */
  67 + private String state;
  68 +
80 69 }
81 70  
82 71  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.social;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Mapper
  11 +public interface SocialUserBindMapper extends BaseMapperX<SocialUserBindDO> {
  12 +
  13 + default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) {
  14 + delete(new LambdaQueryWrapperX<SocialUserBindDO>()
  15 + .eq(SocialUserBindDO::getUserType, userType)
  16 + .eq(SocialUserBindDO::getUserId, userId)
  17 + .eq(SocialUserBindDO::getSocialType, socialType));
  18 + }
  19 +
  20 + default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {
  21 + delete(new LambdaQueryWrapperX<SocialUserBindDO>()
  22 + .eq(SocialUserBindDO::getUserType, userType)
  23 + .eq(SocialUserBindDO::getSocialUserId, socialUserId));
  24 + }
  25 +
  26 + default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {
  27 + return selectOne(new LambdaQueryWrapperX<SocialUserBindDO>()
  28 + .eq(SocialUserBindDO::getUserType, userType)
  29 + .eq(SocialUserBindDO::getSocialUserId, socialUserId));
  30 + }
  31 +
  32 + default List<SocialUserBindDO> selectListByUserIdAndUserType(Long userId, Integer userType) {
  33 + return selectList(new LambdaQueryWrapperX<SocialUserBindDO>()
  34 + .eq(SocialUserBindDO::getUserId, userId)
  35 + .eq(SocialUserBindDO::getUserType, userType));
  36 + }
  37 +
  38 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
2 2  
3 3 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
5 6 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 7 import org.apache.ibatis.annotations.Mapper;
7 8  
... ... @@ -11,18 +12,17 @@ import java.util.List;
11 12 @Mapper
12 13 public interface SocialUserMapper extends BaseMapperX<SocialUserDO> {
13 14  
14   - default List<SocialUserDO> selectListByTypeAndUnionId(Integer userType, Collection<Integer> types, String unionId) {
15   - return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType)
16   - .in("type", types).eq("union_id", unionId));
  15 + default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) {
  16 + return selectOne(new LambdaQueryWrapper<SocialUserDO>()
  17 + .eq(SocialUserDO::getType, type)
  18 + .eq(SocialUserDO::getCode, code)
  19 + .eq(SocialUserDO::getState, state));
17 20 }
18 21  
19   - default List<SocialUserDO> selectListByTypeAndUserId(Integer userType, Collection<Integer> types, Long userId) {
20   - return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType)
21   - .in("type", types).eq("user_id", userId));
22   - }
23   -
24   - default List<SocialUserDO> selectListByUserId(Integer userType, Long userId) {
25   - return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType).eq("user_id", userId));
  22 + default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) {
  23 + return selectOne(new LambdaQueryWrapper<SocialUserDO>()
  24 + .eq(SocialUserDO::getType, type)
  25 + .eq(SocialUserDO::getOpenid, openid));
26 26 }
27 27  
28 28 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
... ... @@ -23,10 +23,6 @@ public interface RedisKeyConstants {
23 23 "login_user:%s", // 参数为 sessionId
24 24 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
25 25  
26   - RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交登陆的授权用户",
27   - "social_auth_user:%d:%s", // 参数为 type,code
28   - STRING, AuthUser.class, Duration.ofDays(1));
29   -
30 26 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
31 27 "social_auth_state:%s", // 参数为 state
32 28 STRING, String.class, Duration.ofHours(24)); // 值为 state
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java deleted
1   -package cn.iocoder.yudao.module.system.dal.redis.social;
2   -
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import me.zhyd.oauth.model.AuthCallback;
5   -import me.zhyd.oauth.model.AuthUser;
6   -import org.springframework.data.redis.core.StringRedisTemplate;
7   -import org.springframework.stereotype.Repository;
8   -
9   -import javax.annotation.Resource;
10   -
11   -import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.SOCIAL_AUTH_USER;
12   -
13   -
14   -/**
15   - * 社交 {@link me.zhyd.oauth.model.AuthUser} 的 RedisDAO
16   - *
17   - * @author 芋道源码
18   - */
19   -@Repository
20   -public class SocialAuthUserRedisDAO {
21   -
22   - @Resource
23   - private StringRedisTemplate stringRedisTemplate;
24   -
25   - public AuthUser get(Integer type, AuthCallback authCallback) {
26   - String redisKey = formatKey(type, authCallback.getCode());
27   - return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), AuthUser.class);
28   - }
29   -
30   - public void set(Integer type, AuthCallback authCallback, AuthUser authUser) {
31   - String redisKey = formatKey(type, authCallback.getCode());
32   - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), SOCIAL_AUTH_USER.getTimeout());
33   - }
34   -
35   - private static String formatKey(Integer type, String code) {
36   - return String.format(SOCIAL_AUTH_USER.getKeyTemplate(), type, code);
37   - }
38   -
39   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java
... ... @@ -18,14 +18,18 @@ public class SecurityConfiguration {
18 18  
19 19 @Override
20 20 public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
21   - // 登录的接口,可匿名访问
22   - registry.antMatchers(buildAdminApi("/system/login")).anonymous();
  21 + // 登录的接口
  22 + registry.antMatchers(buildAdminApi("/system/auth/login")).permitAll();
  23 + // 社交登陆的接口
  24 + registry.antMatchers(buildAdminApi("/system/auth/social-auth-redirect")).permitAll();
  25 + registry.antMatchers(buildAdminApi("/system/auth/social-quick-login")).permitAll();
  26 + registry.antMatchers(buildAdminApi("/system/auth/social-bind-login")).permitAll();
23 27 // 验证码的接口
24   - registry.antMatchers(buildAdminApi("/system/captcha/**")).anonymous();
  28 + registry.antMatchers(buildAdminApi("/system/captcha/**")).permitAll();
25 29 // 获得租户编号的接口
26   - registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).anonymous();
  30 + registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).permitAll();
27 31 // 短信回调 API
28   - registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous();
  32 + registry.antMatchers(buildAdminApi("/system/sms/callback/**")).permitAll();
29 33 }
30 34  
31 35 };
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
... ... @@ -25,31 +25,23 @@ public interface AdminAuthService extends SecurityAuthFrameworkService {
25 25 String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
26 26  
27 27 /**
28   - * 社交登录,使用 code 授权码
  28 + * 社交快捷登录,使用 code 授权码
29 29 *
30 30 * @param reqVO 登录信息
31 31 * @param userIp 用户 IP
32 32 * @param userAgent 用户 UA
33 33 * @return 身份令牌,使用 JWT 方式
34 34 */
35   - String socialLogin(@Valid AuthSocialLoginReqVO reqVO, String userIp, String userAgent);
  35 + String socialLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
36 36  
37 37 /**
38   - * 社交登录,使用 code 授权码 + 账号密码
  38 + * 社交绑定登录,使用 code 授权码 + 账号密码
39 39 *
40 40 * @param reqVO 登录信息
41 41 * @param userIp 用户 IP
42 42 * @param userAgent 用户 UA
43 43 * @return 身份令牌,使用 JWT 方式
44 44 */
45   - String socialLogin2(@Valid AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
46   -
47   - /**
48   - * 社交绑定,使用 code 授权码
49   - *
50   - * @param userId 用户编号
51   - * @param reqVO 绑定信息
52   - */
53   - void socialBind(Long userId, @Valid AuthSocialBindReqVO reqVO);
  45 + String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
54 46  
55 47 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
... ... @@ -9,9 +9,8 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
9 9 import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
10 10 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
11 11 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO;
12   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO;
13   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO;
14   -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLoginReqVO;
  12 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindLoginReqVO;
  13 +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialQuickLoginReqVO;
15 14 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
16 15 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
17 16 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
... ... @@ -22,7 +21,6 @@ import cn.iocoder.yudao.module.system.service.permission.PermissionService;
22 21 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
23 22 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
24 23 import lombok.extern.slf4j.Slf4j;
25   -import me.zhyd.oauth.model.AuthUser;
26 24 import org.springframework.beans.factory.annotation.Autowired;
27 25 import org.springframework.context.annotation.Lazy;
28 26 import org.springframework.security.authentication.AuthenticationManager;
... ... @@ -82,7 +80,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
82 80 throw new UsernameNotFoundException(username);
83 81 }
84 82 // 创建 LoginUser 对象
85   - return this.buildLoginUser(user);
  83 + return buildLoginUser(user);
86 84 }
87 85  
88 86 @Override
... ... @@ -92,19 +90,19 @@ public class AdminAuthServiceImpl implements AdminAuthService {
92 90 if (user == null) {
93 91 throw new UsernameNotFoundException(String.valueOf(userId));
94 92 }
95   - this.createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
  93 + createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS);
96 94  
97 95 // 创建 LoginUser 对象
98   - return this.buildLoginUser(user);
  96 + return buildLoginUser(user);
99 97 }
100 98  
101 99 @Override
102 100 public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) {
103 101 // 判断验证码是否正确
104   - this.verifyCaptcha(reqVO);
  102 + verifyCaptcha(reqVO);
105 103  
106 104 // 使用账号密码,进行登录
107   - LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
  105 + LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
108 106  
109 107 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
110 108 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent);
... ... @@ -192,7 +190,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
192 190 }
193 191  
194 192 @Override
195   - public String socialLogin(AuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
  193 + public String socialLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) {
196 194 // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
197 195 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
198 196 reqVO.getCode(), reqVO.getState());
... ... @@ -207,25 +205,18 @@ public class AdminAuthServiceImpl implements AdminAuthService {
207 205 }
208 206  
209 207 // 创建 LoginUser 对象
210   - LoginUser loginUser = this.buildLoginUser(user);
211   -
212   - // 绑定社交用户(更新)
213   - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
  208 + LoginUser loginUser = buildLoginUser(user);
214 209  
215 210 // 缓存登录用户到 Redis 中,返回 sessionId 编号
216 211 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent);
217 212 }
218 213  
219 214 @Override
220   - public String socialLogin2(AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
221   - // 使用 code 授权码,进行登录
222   - AuthUser authUser = socialUserService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
223   - Assert.notNull(authUser, "授权用户不为空");
224   -
  215 + public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) {
225 216 // 使用账号密码,进行登录。
226   - LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
  217 + LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword());
227 218  
228   - // 绑定社交用户(新增)
  219 + // 绑定社交用户
229 220 socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO));
230 221  
231 222 // 缓存登录用户到 Redis 中,返回 sessionId 编号
... ... @@ -240,12 +231,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
240 231 }
241 232  
242 233 @Override
243   - public void socialBind(Long userId, AuthSocialBindReqVO reqVO) {
244   - // 绑定社交用户(新增)
245   - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO));
246   - }
247   -
248   - @Override
249 234 public void logout(String token) {
250 235 // 查询用户信息
251 236 LoginUser loginUser = userSessionService.getLoginUser(token);
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.framework.common.exception.ServiceException;
5 4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
6 5 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
7 6 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
8   -import me.zhyd.oauth.model.AuthUser;
9 7  
10 8 import javax.validation.Valid;
11 9 import javax.validation.constraints.NotNull;
... ... @@ -28,7 +26,7 @@ public interface SocialUserService {
28 26 String getAuthorizeUrl(Integer type, String redirectUri);
29 27  
30 28 /**
31   - * 获得授权的用户
  29 + * 授权获得对应的社交用户
32 30 * 如果授权失败,则会抛出 {@link ServiceException} 异常
33 31 *
34 32 * @param type 社交平台的类型 {@link SocialTypeEnum}
... ... @@ -37,17 +35,7 @@ public interface SocialUserService {
37 35 * @return 授权用户
38 36 */
39 37 @NotNull
40   - AuthUser getAuthUser(Integer type, String code, String state);
41   -
42   - /**
43   - * 获得社交用户的 unionId 编号
44   - *
45   - * @param authUser 社交用户
46   - * @return unionId 编号
47   - */
48   - default String getAuthUserUnionId(AuthUser authUser) {
49   - return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
50   - }
  38 + SocialUserDO authSocialUser(Integer type, String code, String state);
51 39  
52 40 /**
53 41 * 获得指定用户的社交用户列表
... ... @@ -71,25 +59,14 @@ public interface SocialUserService {
71 59 * @param userId 用户编号
72 60 * @param userType 全局用户类型
73 61 * @param type 社交平台的类型 {@link SocialTypeEnum}
74   - * @param unionId 社交平台的 unionId
75   - */
76   - void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId);
77   -
78   - /**
79   - * 校验社交用户的认证信息是否正确
80   - * 如果校验不通过,则抛出 {@link ServiceException} 业务异常
81   - *
82   - * @param type 社交平台的类型
83   - * @param code 授权码
84   - * @param state state
  62 + * @param openid 社交平台的 openid
85 63 */
86   - void checkSocialUser(Integer type, String code, String state);
  64 + void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
87 65  
88 66 /**
89 67 * 获得社交用户的绑定用户编号
90 68 * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
91   - * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。
92   - * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
  69 + * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
93 70 *
94 71 * @param userType 用户类型
95 72 * @param type 社交平台的类型
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  4 +import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
6 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
7 8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
  9 +import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
8 10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
9   -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
10 11 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
11   -import com.google.common.annotations.VisibleForTesting;
12 12 import com.xkcoding.justauth.AuthRequestFactory;
13 13 import lombok.extern.slf4j.Slf4j;
14 14 import me.zhyd.oauth.model.AuthCallback;
... ... @@ -21,10 +21,11 @@ import org.springframework.transaction.annotation.Transactional;
21 21 import org.springframework.validation.annotation.Validated;
22 22  
23 23 import javax.annotation.Resource;
  24 +import java.util.Collections;
24 25 import java.util.List;
25   -import java.util.Objects;
26 26  
27 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 29 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
29 30 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
30 31  
... ... @@ -42,8 +43,7 @@ public class SocialUserServiceImpl implements SocialUserService {
42 43 private AuthRequestFactory authRequestFactory;
43 44  
44 45 @Resource
45   - private SocialAuthUserRedisDAO authSocialUserRedisDAO;
46   -
  46 + private SocialUserBindMapper socialUserBindMapper;
47 47 @Resource
48 48 private SocialUserMapper socialUserMapper;
49 49  
... ... @@ -57,171 +57,111 @@ public class SocialUserServiceImpl implements SocialUserService {
57 57 }
58 58  
59 59 @Override
60   - public AuthUser getAuthUser(Integer type, String code, String state) {
61   - AuthCallback authCallback = buildAuthCallback(code, state);
62   - // 从缓存中获取
63   - AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback);
64   - if (authUser != null) {
65   - return authUser;
  60 + public SocialUserDO authSocialUser(Integer type, String code, String state) {
  61 + // 优先从 DB 中获取,因为 code 有且可以使用一次。
  62 + // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
  63 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
  64 + if (socialUser != null) {
  65 + return socialUser;
66 66 }
67 67  
68 68 // 请求获取
69   - authUser = this.getAuthUser0(type, authCallback);
70   - // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
71   - authSocialUserRedisDAO.set(type, authCallback, authUser);
72   - return authUser;
73   - }
74   -
75   - /**
76   - * 获得 unionId 对应的某个社交平台的“所有”社交用户
77   - * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的
78   - *
79   - * @param type 社交平台的类型 {@link SocialTypeEnum}
80   - * @param unionId 社交平台的 unionId
81   - * @param userType 全局用户类型
82   - * @return 社交用户列表
83   - */
84   - private List<SocialUserDO> getAllSocialUserList(Integer type, String unionId, Integer userType) {
85   - List<Integer> types = SocialTypeEnum.getRelationTypes(type);
86   - return socialUserMapper.selectListByTypeAndUnionId(userType, types, unionId);
87   - }
88   -
89   - @Override
90   - public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
91   - return socialUserMapper.selectListByUserId(userType, userId);
92   - }
93   -
94   - @Override
95   - public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
96   - // 使用 code 授权
97   - AuthUser authUser = getAuthUser(reqDTO.getType(), reqDTO.getCode(),
98   - reqDTO.getState());
99   - if (authUser == null) {
100   - throw exception(SOCIAL_USER_NOT_FOUND);
101   - }
102   -
103   - // 绑定社交用户(新增)
104   - bindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(),
105   - reqDTO.getType(), authUser);
106   - }
  69 + AuthUser authUser = getAuthUser(type, code, state);
  70 + Assert.notNull(authUser, "三方用户不能为空");
107 71  
108   - /**
109   - * 绑定社交用户
110   - * @param userId 用户编号
111   - * @param userType 用户类型
112   - * @param type 社交平台的类型 {@link SocialTypeEnum}
113   - * @param authUser 授权用户
114   - */
115   - @Transactional(rollbackFor = Exception.class)
116   - protected void bindSocialUser(Long userId, Integer userType, Integer type, AuthUser authUser) {
117   - // 获得 unionId 对应的 SocialUserDO 列表
118   - String unionId = getAuthUserUnionId(authUser);
119   - List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
120   -
121   - // 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑
122   - this.unbindOldSocialUser(userId, userType, type, unionId);
123   -
124   - // 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新
125   - // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改
126   - // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可
127   - socialUsers.forEach(socialUser -> {
128   - if (Objects.equals(socialUser.getUserId(), userId)) {
129   - return;
130   - }
131   - socialUserMapper.updateById(new SocialUserDO().setId(socialUser.getId()).setUserId(userId));
132   - });
133   -
134   - // 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新
135   - SocialUserDO socialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
136   - SocialUserDO saveSocialUser = SocialUserDO.builder() // 新增和更新的通用属性
137   - .token(authUser.getToken().getAccessToken()).rawTokenInfo(toJsonString(authUser.getToken()))
138   - .nickname(authUser.getNickname()).avatar(authUser.getAvatar()).rawUserInfo(toJsonString(authUser.getRawUserInfo()))
139   - .build();
  72 + // 保存到 DB 中
  73 + socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
140 74 if (socialUser == null) {
141   - saveSocialUser.setUserId(userId).setUserType(userType)
142   - .setType(type).setOpenid(authUser.getUuid()).setUnionId(unionId);
143   - socialUserMapper.insert(saveSocialUser);
  75 + socialUser = new SocialUserDO();
  76 + }
  77 + socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询
  78 + .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
  79 + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));
  80 + if (socialUser.getId() == null) {
  81 + socialUserMapper.insert(socialUser);
144 82 } else {
145   - saveSocialUser.setId(socialUser.getId());
146   - socialUserMapper.updateById(saveSocialUser);
  83 + socialUserMapper.updateById(socialUser);
147 84 }
  85 + return socialUser;
148 86 }
149 87  
150 88 @Override
151   - public void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId) {
152   - // 获得 unionId 对应的所有 SocialUserDO 社交用户
153   - List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
154   - if (CollUtil.isEmpty(socialUsers)) {
155   - return;
  89 + public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
  90 + // 获得绑定
  91 + List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType);
  92 + if (CollUtil.isEmpty(socialUserBinds)) {
  93 + return Collections.emptyList();
156 94 }
157   - // 校验,是否解绑的是非自己的
158   - socialUsers.forEach(socialUser -> {
159   - if (!Objects.equals(socialUser.getUserId(), userId)) {
160   - throw exception(SOCIAL_USER_UNBIND_NOT_SELF);
161   - }
162   - });
163   -
164   - // 解绑
165   - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SocialUserDO::getId));
  95 + // 获得社交用户
  96 + return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId));
166 97 }
167 98  
168 99 @Override
169   - public void checkSocialUser(Integer type, String code, String state) {
170   - AuthUser authUser = getAuthUser(type, code, state);
171   - if (authUser == null) {
172   - throw exception(SOCIAL_USER_NOT_FOUND);
173   - }
  100 + @Transactional
  101 + public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
  102 + // 获得社交用户
  103 + SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
  104 + Assert.notNull(socialUser, "社交用户不能为空");
  105 +
  106 + // 社交用户可能之前绑定过别的用户,需要进行解绑
  107 + socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId());
  108 +
  109 + // 用户可能之前已经绑定过该社交类型,需要进行解绑
  110 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(),
  111 + socialUser.getType());
  112 +
  113 + // 绑定当前登录的社交用户
  114 + SocialUserBindDO socialUserBind = SocialUserBindDO.builder()
  115 + .userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
  116 + .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();
  117 + socialUserBindMapper.insert(socialUserBind);
174 118 }
175 119  
176 120 @Override
177   - public Long getBindUserId(Integer userType, Integer type, String code, String state) {
178   - AuthUser authUser = getAuthUser(type, code, state);
179   - if (authUser == null) {
  121 + public void unbindSocialUser(Long userId, Integer userType, Integer type, String openid) {
  122 + // 获得 openid 对应的 SocialUserDO 社交用户
  123 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(type, openid);
  124 + if (socialUser == null) {
180 125 throw exception(SOCIAL_USER_NOT_FOUND);
181 126 }
182 127  
183   - // 如果未绑定 SocialUserDO 用户,则无法自动登录,进行报错
184   - String unionId = getAuthUserUnionId(authUser);
185   - List<SocialUserDO> socialUsers = getAllSocialUserList(type, unionId, userType);
186   - if (CollUtil.isEmpty(socialUsers)) {
187   - throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
188   - }
189   - return socialUsers.get(0).getUserId();
  128 + // 获得对应的社交绑定关系
  129 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType());
190 130 }
191 131  
192   - @VisibleForTesting
193   - public void unbindOldSocialUser(Long userId, Integer userType, Integer type, String newUnionId) {
194   - List<Integer> types = SocialTypeEnum.getRelationTypes(type);
195   - List<SocialUserDO> oldSocialUsers = socialUserMapper.selectListByTypeAndUserId(userType, types, userId);
196   - // 如果新老的 unionId 是一致的,说明无需解绑
197   - if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) {
198   - return;
  132 + @Override
  133 + public Long getBindUserId(Integer userType, Integer type, String code, String state) {
  134 + // 获得社交用户
  135 + SocialUserDO socialUser = authSocialUser(type, code, state);
  136 + Assert.notNull(socialUser, "社交用户不能为空");
  137 +
  138 + // 如果未绑定的社交用户,则无法自动登录,进行报错
  139 + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType,
  140 + socialUser.getId());
  141 + if (socialUserBind == null) {
  142 + throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
199 143 }
200   -
201   - // 解绑
202   - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SocialUserDO::getId));
  144 + return socialUserBind.getUserId();
203 145 }
204 146  
205 147 /**
206 148 * 请求社交平台,获得授权的用户
207 149 *
208 150 * @param type 社交平台的类型
209   - * @param authCallback 授权回调
  151 + * @param code 授权码
  152 + * @param state 授权 state
210 153 * @return 授权的用户
211 154 */
212   - private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) {
  155 + private AuthUser getAuthUser(Integer type, String code, String state) {
213 156 AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
  157 + AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
214 158 AuthResponse<?> authResponse = authRequest.login(authCallback);
215   - log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback),
216   - toJsonString(authResponse));
  159 + log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type,
  160 + toJsonString(authCallback), toJsonString(authResponse));
217 161 if (!authResponse.ok()) {
218 162 throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());
219 163 }
220 164 return (AuthUser) authResponse.getData();
221 165 }
222 166  
223   - private static AuthCallback buildAuthCallback(String code, String state) {
224   - return AuthCallback.builder().code(code).state(state).build();
225   - }
226   -
227 167 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
  5 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  6 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
3 7 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
  8 +import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
4 9 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
5   -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
6 10 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
7   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
8   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
9 11 import com.xkcoding.justauth.AuthRequestFactory;
  12 +import me.zhyd.oauth.enums.AuthResponseStatus;
  13 +import me.zhyd.oauth.model.AuthCallback;
  14 +import me.zhyd.oauth.model.AuthResponse;
10 15 import me.zhyd.oauth.model.AuthUser;
  16 +import me.zhyd.oauth.request.AuthRequest;
  17 +import me.zhyd.oauth.utils.AuthStateUtils;
11 18 import org.junit.jupiter.api.Test;
  19 +import org.mockito.MockedStatic;
12 20 import org.springframework.boot.test.mock.mockito.MockBean;
13 21 import org.springframework.context.annotation.Import;
14 22  
15 23 import javax.annotation.Resource;
16 24 import java.util.List;
17 25  
18   -import static cn.hutool.core.util.RandomUtil.randomEle;
  26 +import static cn.hutool.core.util.RandomUtil.randomLong;
19 27 import static cn.hutool.core.util.RandomUtil.randomString;
20 28 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
21   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  29 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  30 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
22 31 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  32 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE;
  33 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;
23 34 import static org.junit.jupiter.api.Assertions.assertEquals;
  35 +import static org.mockito.Mockito.*;
24 36  
25   -@Import({SocialUserServiceImpl.class, SocialAuthUserRedisDAO.class})
  37 +@Import(SocialUserServiceImpl.class)
26 38 public class SocialUserServiceTest extends BaseDbAndRedisUnitTest {
27 39  
28 40 @Resource
29   - private SocialUserServiceImpl socialService;
  41 + private SocialUserServiceImpl socialUserService;
30 42  
31 43 @Resource
32 44 private SocialUserMapper socialUserMapper;
  45 + @Resource
  46 + private SocialUserBindMapper socialUserBindMapper;
33 47  
34 48 @MockBean
35 49 private AuthRequestFactory authRequestFactory;
36 50  
37   - /**
38   - * 情况一,创建 SocialUserDO 的情况
39   - */
40 51 @Test
41   - public void testBindSocialUser_create() {
42   - // mock 数据
  52 + public void testGetAuthorizeUrl() {
  53 + try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {
  54 + // 准备参数
  55 + Integer type = SocialTypeEnum.WECHAT_MP.getType();
  56 + String redirectUri = "sss";
  57 + // mock 获得对应的 AuthRequest 实现
  58 + AuthRequest authRequest = mock(AuthRequest.class);
  59 + when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
  60 + // mock 方法
  61 + authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman");
  62 + when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy");
  63 +
  64 + // 调用
  65 + String url = socialUserService.getAuthorizeUrl(type, redirectUri);
  66 + // 断言
  67 + assertEquals("https://www.iocoder.cn/?redirect_uri=sss", url);
  68 + }
  69 + }
  70 +
  71 + @Test
  72 + public void testAuthSocialUser_exists() {
43 73 // 准备参数
44   - Long userId = randomLongId();
45   - Integer type = randomEle(SocialTypeEnum.values()).getType();
46   - AuthUser authUser = randomPojo(AuthUser.class);
  74 + Integer type = SocialTypeEnum.GITEE.getType();
  75 + String code = "tudou";
  76 + String state = "yuanma";
47 77 // mock 方法
  78 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
  79 + socialUserMapper.insert(socialUser);
48 80  
49 81 // 调用
50   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  82 + SocialUserDO result = socialUserService.authSocialUser(type, code, state);
51 83 // 断言
52   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
53   - assertEquals(1, socialUsers.size());
54   - assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
  84 + assertPojoEquals(socialUser, result);
55 85 }
56 86  
57   - /**
58   - * 情况二,更新 SocialUserDO 的情况
59   - */
60 87 @Test
61   - public void testBindSocialUser_update() {
62   - // mock 数据
63   - SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
64   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
65   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
66   - });
67   - socialUserMapper.insert(dbSocialUser);
  88 + public void testAuthSocialUser_authFailure() {
68 89 // 准备参数
69   - Long userId = dbSocialUser.getUserId();
70   - Integer type = dbSocialUser.getType();
71   - AuthUser authUser = randomPojo(AuthUser.class);
  90 + Integer type = SocialTypeEnum.GITEE.getType();
  91 + // mock 方法
  92 + AuthRequest authRequest = mock(AuthRequest.class);
  93 + when(authRequestFactory.get(anyString())).thenReturn(authRequest);
  94 + AuthResponse<?> authResponse = new AuthResponse<>(0, "模拟失败", null);
  95 + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
  96 +
  97 + // 调用并断言
  98 + assertServiceException(
  99 + () -> socialUserService.authSocialUser(type, randomString(10), randomString(10)),
  100 + SOCIAL_USER_AUTH_FAILURE, "模拟失败");
  101 + }
  102 +
  103 + @Test
  104 + public void testAuthSocialUser_insert() {
  105 + // 准备参数
  106 + Integer type = SocialTypeEnum.GITEE.getType();
  107 + String code = "tudou";
  108 + String state = "yuanma";
72 109 // mock 方法
  110 + AuthRequest authRequest = mock(AuthRequest.class);
  111 + when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
  112 + AuthUser authUser = randomPojo(AuthUser.class);
  113 + AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
  114 + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
73 115  
74 116 // 调用
75   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  117 + SocialUserDO result = socialUserService.authSocialUser(type, code, state);
76 118 // 断言
77   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
78   - assertEquals(1, socialUsers.size());
79   - assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
  119 + assertBindSocialUser(type, result, authResponse.getData());
  120 + assertEquals(code, result.getCode());
  121 + assertEquals(state, result.getState());
80 122 }
81 123  
82   - /**
83   - * 情况一和二都存在的,逻辑二的场景
84   - */
85 124 @Test
86   - public void testBindSocialUser_userId() {
87   - // mock 数据
88   - SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
89   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
90   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
91   - });
92   - socialUserMapper.insert(dbSocialUser);
  125 + public void testAuthSocialUser_update() {
93 126 // 准备参数
94   - Long userId = randomLongId();
95   - Integer type = dbSocialUser.getType();
96   - AuthUser authUser = randomPojo(AuthUser.class);
  127 + Integer type = SocialTypeEnum.GITEE.getType();
  128 + String code = "tudou";
  129 + String state = "yuanma";
  130 + // mock 数据
  131 + socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(type).setOpenid("test_openid"));
97 132 // mock 方法
  133 + AuthRequest authRequest = mock(AuthRequest.class);
  134 + when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest);
  135 + AuthUser authUser = randomPojo(AuthUser.class);
  136 + authUser.getToken().setOpenId("test_openid");
  137 + AuthResponse<AuthUser> authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser);
  138 + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse);
98 139  
99 140 // 调用
100   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  141 + SocialUserDO result = socialUserService.authSocialUser(type, code, state);
101 142 // 断言
102   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
103   - assertEquals(1, socialUsers.size());
  143 + assertBindSocialUser(type, result, authResponse.getData());
  144 + assertEquals(code, result.getCode());
  145 + assertEquals(state, result.getState());
104 146 }
105 147  
106   - private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId,
107   - Integer type) {
  148 + private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) {
108 149 assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
109 150 assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
110 151 assertEquals(authUser.getNickname(), socialUser.getNickname());
111 152 assertEquals(authUser.getAvatar(), socialUser.getAvatar());
112 153 assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
113   - assertEquals(userId, socialUser.getUserId());
114   - assertEquals(UserTypeEnum.ADMIN.getValue(), socialUser.getUserType());
115 154 assertEquals(type, socialUser.getType());
116 155 assertEquals(authUser.getUuid(), socialUser.getOpenid());
117   - assertEquals(socialService.getAuthUserUnionId(authUser), socialUser.getUnionId());
118 156 }
119 157  
120   - /**
121   - * 情况一,如果新老的 unionId 是一致的,无需解绑
122   - */
123 158 @Test
124   - public void testUnbindOldSocialUser_no() {
125   - // mock 数据
126   - SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
127   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
128   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
129   - });
130   - socialUserMapper.insert(oldSocialUser);
  159 + public void testGetSocialUserList() {
  160 + Long userId = 1L;
  161 + Integer userType = UserTypeEnum.ADMIN.getValue();
  162 + // mock 获得社交用户
  163 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType());
  164 + socialUserMapper.insert(socialUser); // 可被查到
  165 + socialUserMapper.insert(randomPojo(SocialUserDO.class)); // 不可被查到
  166 + // mock 获得绑定
  167 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到
  168 + .setUserId(userId).setUserType(userType).setSocialType(SocialTypeEnum.GITEE.getType())
  169 + .setSocialUserId(socialUser.getId()));
  170 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到
  171 + .setUserId(2L).setUserType(userType).setSocialType(SocialTypeEnum.DINGTALK.getType()));
  172 +
  173 + // 调用
  174 + List<SocialUserDO> result = socialUserService.getSocialUserList(userId, userType);
  175 + // 断言
  176 + assertEquals(1, result.size());
  177 + assertPojoEquals(socialUser, result.get(0));
  178 + }
  179 +
  180 + @Test
  181 + public void testBindSocialUser() {
131 182 // 准备参数
132   - Long userId = oldSocialUser.getUserId();
133   - Integer type = oldSocialUser.getType();
134   - String newUnionId = oldSocialUser.getUnionId();
  183 + SocialUserBindReqDTO reqDTO = new SocialUserBindReqDTO()
  184 + .setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())
  185 + .setType(SocialTypeEnum.GITEE.getType()).setCode("test_code").setState("test_state");
  186 + // mock 数据:获得社交用户
  187 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(reqDTO.getType())
  188 + .setCode(reqDTO.getCode()).setState(reqDTO.getState());
  189 + socialUserMapper.insert(socialUser);
  190 + // mock 数据:用户可能之前已经绑定过该社交类型
  191 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())
  192 + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(-1L));
  193 + // mock 数据:社交用户可能之前绑定过别的用户
  194 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserType(UserTypeEnum.ADMIN.getValue())
  195 + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));
135 196  
136 197 // 调用
137   - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
  198 + socialUserService.bindSocialUser(reqDTO);
138 199 // 断言
139   - assertEquals(1L, socialUserMapper.selectCount(null).longValue());
  200 + List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList();
  201 + assertEquals(1, socialUserBinds.size());
140 202 }
141 203  
  204 + @Test
  205 + public void testUnbindSocialUser_success() {
  206 + // 准备参数
  207 + Long userId = 1L;
  208 + Integer userType = UserTypeEnum.ADMIN.getValue();
  209 + Integer type = SocialTypeEnum.GITEE.getType();
  210 + String openid = "test_openid";
  211 + // mock 数据:社交用户
  212 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setOpenid(openid);
  213 + socialUserMapper.insert(socialUser);
  214 + // mock 数据:社交绑定关系
  215 + SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType)
  216 + .setUserId(userId).setSocialType(type);
  217 + socialUserBindMapper.insert(socialUserBind);
  218 +
  219 + // 调用
  220 + socialUserService.unbindSocialUser(userId, userType, type, openid);
  221 + // 断言
  222 + assertEquals(0, socialUserBindMapper.selectCount(null).intValue());
  223 + }
142 224  
143   - /**
144   - * 情况二,如果新老的 unionId 不一致的,需解绑
145   - */
146 225 @Test
147   - public void testUnbindOldSocialUser_yes() {
148   - // mock 数据
149   - SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
150   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
151   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
152   - });
153   - socialUserMapper.insert(oldSocialUser);
  226 + public void testUnbindSocialUser_notFound() {
  227 + // 调用,并断言
  228 + assertServiceException(
  229 + () -> socialUserService.unbindSocialUser(randomLong(), UserTypeEnum.ADMIN.getValue(),
  230 + SocialTypeEnum.GITEE.getType(), "test_openid"),
  231 + SOCIAL_USER_NOT_FOUND);
  232 + }
  233 +
  234 + @Test
  235 + public void testGetBindUserId() {
154 236 // 准备参数
155   - Long userId = oldSocialUser.getUserId();
156   - Integer type = oldSocialUser.getType();
157   - String newUnionId = randomString(10);
  237 + Integer userType = UserTypeEnum.ADMIN.getValue();
  238 + Integer type = SocialTypeEnum.GITEE.getType();
  239 + String code = "tudou";
  240 + String state = "yuanma";
  241 + // mock 社交用户
  242 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
  243 + socialUserMapper.insert(socialUser);
  244 + // mock 社交用户的绑定
  245 + Long userId = randomLong();
  246 + SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)
  247 + .setSocialType(type).setSocialUserId(socialUser.getId());
  248 + socialUserBindMapper.insert(socialUserBind);
158 249  
159 250 // 调用
160   - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
  251 + Long result = socialUserService.getBindUserId(userType, type, code, state);
161 252 // 断言
162   - assertEquals(0L, socialUserMapper.selectCount(null).longValue());
  253 + assertEquals(userId, result);
163 254 }
164 255  
165 256 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
... ... @@ -15,6 +15,7 @@ DELETE FROM &quot;system_sms_template&quot;;
15 15 DELETE FROM "system_sms_log";
16 16 DELETE FROM "system_error_code";
17 17 DELETE FROM "system_social_user";
  18 +DELETE FROM "system_social_user_bind";
18 19 DELETE FROM "system_tenant";
19 20 DELETE FROM "system_tenant_package";
20 21 DELETE FROM "system_sensitive_word";
... ...
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
... ... @@ -378,16 +378,15 @@ CREATE TABLE IF NOT EXISTS &quot;system_error_code&quot; (
378 378  
379 379 CREATE TABLE IF NOT EXISTS "system_social_user" (
380 380 "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
381   - "user_id" bigint NOT NULL,
382   - "user_type" tinyint NOT NULL DEFAULT '0',
383 381 "type" tinyint NOT NULL,
384   - "openid" varchar(32) NOT NULL,
  382 + "openid" varchar(64) NOT NULL,
385 383 "token" varchar(256) DEFAULT NULL,
386   - "union_id" varchar(32) NOT NULL,
387 384 "raw_token_info" varchar(1024) NOT NULL,
388 385 "nickname" varchar(32) NOT NULL,
389 386 "avatar" varchar(255) DEFAULT NULL,
390 387 "raw_user_info" varchar(1024) NOT NULL,
  388 + "code" varchar(64) NOT NULL,
  389 + "state" varchar(64),
391 390 "creator" varchar(64) DEFAULT '',
392 391 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
393 392 "updater" varchar(64) DEFAULT '',
... ... @@ -396,6 +395,20 @@ CREATE TABLE IF NOT EXISTS &quot;system_social_user&quot; (
396 395 PRIMARY KEY ("id")
397 396 ) COMMENT '社交用户';
398 397  
  398 +CREATE TABLE IF NOT EXISTS "system_social_user_bind" (
  399 + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  400 + "user_id" bigint NOT NULL,
  401 + "user_type" tinyint NOT NULL,
  402 + "social_type" tinyint NOT NULL,
  403 + "social_user_id" number NOT NULL,
  404 + "creator" varchar(64) DEFAULT '',
  405 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  406 + "updater" varchar(64) DEFAULT '',
  407 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  408 + "deleted" bit NOT NULL DEFAULT FALSE,
  409 + PRIMARY KEY ("id")
  410 +) COMMENT '社交用户的绑定';
  411 +
399 412 CREATE TABLE IF NOT EXISTS "system_tenant" (
400 413 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
401 414 "name" varchar(63) NOT NULL,
... ...
yudao-server/src/main/resources/application-dev.yaml
... ... @@ -186,10 +186,6 @@ yudao:
186 186 justauth:
187 187 enabled: true
188 188 type:
189   - GITEE: # Gitee
190   - client-id: ee61f0374a4c6c404a8717094caa7a410d76950e45ff60348015830c519ba5c1
191   - client-secret: 7c044a5671be3b051414db0cf2cec6ad702dd298d2416ba24ceaf608e6fa26f9
192   - ignore-check-redirect-uri: true
193 189 DINGTALK: # 钉钉
194 190 client-id: dingvrnreaje3yqvzhxg
195 191 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -196,10 +196,6 @@ yudao:
196 196 justauth:
197 197 enabled: true
198 198 type:
199   - GITEE: # Gitee
200   - client-id: ee61f0374a4c6c404a8717094caa7a410d76950e45ff60348015830c519ba5c1
201   - client-secret: 7c044a5671be3b051414db0cf2cec6ad702dd298d2416ba24ceaf608e6fa26f9
202   - ignore-check-redirect-uri: true
203 199 DINGTALK: # 钉钉
204 200 client-id: dingvrnreaje3yqvzhxg
205 201 client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
... ...
yudao-ui-admin/src/api/login.js
... ... @@ -9,7 +9,7 @@ export function login(username, password, code, uuid) {
9 9 uuid
10 10 }
11 11 return request({
12   - url: '/system/login',
  12 + url: '/system/auth/login',
13 13 method: 'post',
14 14 data: data
15 15 })
... ... @@ -18,7 +18,7 @@ export function login(username, password, code, uuid) {
18 18 // 获取用户详细信息
19 19 export function getInfo() {
20 20 return request({
21   - url: '/system/get-permission-info',
  21 + url: '/system/auth/get-permission-info',
22 22 method: 'get'
23 23 })
24 24 }
... ... @@ -43,15 +43,15 @@ export function getCodeImg() {
43 43 // 社交授权的跳转
44 44 export function socialAuthRedirect(type, redirectUri) {
45 45 return request({
46   - url: '/system/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri,
  46 + url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri,
47 47 method: 'get'
48 48 })
49 49 }
50 50  
51   -// 社交登录,使用 code 授权码
52   -export function socialLogin(type, code, state) {
  51 +// 社交快捷登录,使用 code 授权码
  52 +export function socialQuickLogin(type, code, state) {
53 53 return request({
54   - url: '/system/social-login',
  54 + url: '/system/auth/social-quick-login',
55 55 method: 'post',
56 56 data: {
57 57 type,
... ... @@ -61,10 +61,10 @@ export function socialLogin(type, code, state) {
61 61 })
62 62 }
63 63  
64   -// 社交登录,使用 code 授权码 + + 账号密码
65   -export function socialLogin2(type, code, state, username, password) {
  64 +// 社交绑定登录,使用 code 授权码 + + 账号密码
  65 +export function socialBindLogin(type, code, state, username, password) {
66 66 return request({
67   - url: '/system/social-login2',
  67 + url: '/system/auth/social-bind-login',
68 68 method: 'post',
69 69 data: {
70 70 type,
... ... @@ -75,28 +75,3 @@ export function socialLogin2(type, code, state, username, password) {
75 75 }
76 76 })
77 77 }
78   -
79   -// 社交绑定,使用 code 授权码
80   -export function socialBind(type, code, state) {
81   - return request({
82   - url: '/system/social-bind',
83   - method: 'post',
84   - data: {
85   - type,
86   - code,
87   - state,
88   - }
89   - })
90   -}
91   -
92   -// 取消社交绑定
93   -export function socialUnbind(type, unionId) {
94   - return request({
95   - url: '/system/social-unbind',
96   - method: 'delete',
97   - data: {
98   - type,
99   - unionId
100   - }
101   - })
102   -}
... ...
yudao-ui-admin/src/api/menu.js
... ... @@ -3,7 +3,7 @@ import request from &#39;@/utils/request&#39;
3 3 // 获取路由
4 4 export const getRouters = () => {
5 5 return request({
6   - url: '/system/list-menus',
  6 + url: '/system/auth/list-menus',
7 7 method: 'get'
8 8 })
9 9 }
... ...
yudao-ui-admin/src/api/system/socialUser.js 0 → 100644
  1 +import request from "@/utils/request";
  2 +
  3 +// 社交绑定,使用 code 授权码
  4 +export function socialBind(type, code, state) {
  5 + return request({
  6 + url: '/system/social-user/bind',
  7 + method: 'post',
  8 + data: {
  9 + type,
  10 + code,
  11 + state,
  12 + }
  13 + })
  14 +}
  15 +
  16 +// 取消社交绑定
  17 +export function socialUnbind(type, openid) {
  18 + return request({
  19 + url: '/system/social-user/unbind',
  20 + method: 'delete',
  21 + data: {
  22 + type,
  23 + openid
  24 + }
  25 + })
  26 +}
... ...
yudao-ui-admin/src/store/modules/user.js
1   -import {login, logout, getInfo, socialLogin, socialLogin2} from '@/api/login'
  1 +import {login, logout, getInfo, socialQuickLogin, socialBindLogin} from '@/api/login'
2 2 import { getToken, setToken, removeToken } from '@/utils/auth'
3 3  
4 4 const user = {
... ... @@ -57,7 +57,7 @@ const user = {
57 57 const state = userInfo.state
58 58 const type = userInfo.type
59 59 return new Promise((resolve, reject) => {
60   - socialLogin(type, code, state).then(res => {
  60 + socialQuickLogin(type, code, state).then(res => {
61 61 res = res.data;
62 62 setToken(res.token)
63 63 commit('SET_TOKEN', res.token)
... ... @@ -76,7 +76,7 @@ const user = {
76 76 const username = userInfo.username.trim()
77 77 const password = userInfo.password
78 78 return new Promise((resolve, reject) => {
79   - socialLogin2(type, code, state, username, password).then(res => {
  79 + socialBindLogin(type, code, state, username, password).then(res => {
80 80 res = res.data;
81 81 setToken(res.token)
82 82 commit('SET_TOKEN', res.token)
... ...
yudao-ui-admin/src/utils/constants.js
... ... @@ -71,12 +71,6 @@ export const InfraApiErrorLogProcessStatusEnum = {
71 71 * 用户的社交平台的类型枚举
72 72 */
73 73 export const SystemUserSocialTypeEnum = {
74   - // GITEE: {
75   - // title: "码云",
76   - // type: 10,
77   - // source: "gitee",
78   - // img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/gitee.png",
79   - // },
80 74 DINGTALK: {
81 75 title: "钉钉",
82 76 type: 20,
... ...
yudao-ui-admin/src/views/login.vue
... ... @@ -176,7 +176,6 @@ export default {
176 176 });
177 177 },
178 178 doSocialLogin(socialTypeEnum) {
179   - // console.log("开始Oauth登录...%o", socialTypeEnum.code);
180 179 // 设置登录中
181 180 this.loading = true;
182 181 // 计算 redirectUri
... ...
yudao-ui-admin/src/views/system/user/profile/userSocial.vue
... ... @@ -7,7 +7,7 @@
7 7 </el-table-column>
8 8 <el-table-column label="操作" align="left" >
9 9 <template slot-scope="scope">
10   - <div v-if="scope.row.unionId">
  10 + <div v-if="scope.row.openid">
11 11 已绑定
12 12 <el-button size="large" type="text" @click="unbind(scope.row)">(解绑)</el-button>
13 13 </div>
... ... @@ -23,7 +23,8 @@
23 23 <script>
24 24  
25 25 import {SystemUserSocialTypeEnum} from "@/utils/constants";
26   -import {socialAuthRedirect, socialBind, socialUnbind} from "@/api/login";
  26 +import {socialAuthRedirect} from "@/api/login";
  27 +import {socialBind, socialUnbind} from "@/api/system/socialUser";
27 28  
28 29 export default {
29 30 props: {
... ... @@ -50,7 +51,7 @@ export default {
50 51 if (this.user.socialUsers) {
51 52 for (const j in this.user.socialUsers) {
52 53 if (socialUser.type === this.user.socialUsers[j].type) {
53   - socialUser.unionId = this.user.socialUsers[j].unionId;
  54 + socialUser.openid = this.user.socialUsers[j].openid;
54 55 break;
55 56 }
56 57 }
... ... @@ -86,9 +87,9 @@ export default {
86 87 });
87 88 },
88 89 unbind(socialUser) {
89   - socialUnbind(socialUser.type, socialUser.unionId).then(resp => {
  90 + socialUnbind(socialUser.type, socialUser.openid).then(resp => {
90 91 this.$modal.msgSuccess("解绑成功");
91   - socialUser.unionId = undefined;
  92 + socialUser.openid = undefined;
92 93 });
93 94 },
94 95 close() {
... ...