Просмотр исходного кода

!137 简化三方登录的实现,降低理解成本
Merge pull request !137 from 芋道源码/feature/1.6.2

芋道源码 3 лет назад
Родитель
Сommit
0bf5b20fd7
46 измененных файлов с 644 добавлено и 585 удалено
  1. 6 0
      yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
  2. 9 30
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
  3. 2 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindLoginReqVO.java
  4. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialQuickLoginReqVO.java
  5. 42 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java
  6. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java
  7. 6 6
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java
  8. 4 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
  9. 19 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java
  10. 4 18
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
  11. 5 20
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
  12. 1 12
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java
  13. 3 12
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
  14. 0 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
  15. 9 23
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
  16. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java
  17. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java
  18. 42 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java
  19. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java
  20. 6 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java
  21. 2 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java
  22. 3 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
  23. 19 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java
  24. 48 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java
  25. 11 22
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java
  26. 38 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java
  27. 10 10
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java
  28. 0 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
  29. 0 39
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java
  30. 9 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java
  31. 4 12
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
  32. 12 27
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
  33. 5 28
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
  34. 74 134
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
  35. 178 87
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java
  36. 1 0
      yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
  37. 17 4
      yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
  38. 0 4
      yudao-server/src/main/resources/application-dev.yaml
  39. 0 4
      yudao-server/src/main/resources/application-local.yaml
  40. 9 34
      yudao-ui-admin/src/api/login.js
  41. 1 1
      yudao-ui-admin/src/api/menu.js
  42. 26 0
      yudao-ui-admin/src/api/system/socialUser.js
  43. 3 3
      yudao-ui-admin/src/store/modules/user.js
  44. 0 6
      yudao-ui-admin/src/utils/constants.js
  45. 0 1
      yudao-ui-admin/src/views/login.vue
  46. 6 5
      yudao-ui-admin/src/views/system/user/profile/userSocial.vue

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java

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

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

+ 2 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLogin2ReqVO.java → 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;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
 
-@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码 + 账号密码")
+@ApiModel("用户 APP - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码")
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-public class AppAuthSocialLogin2ReqVO {
+public class AppAuthSocialBindLoginReqVO {
 
     @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
     @InEnum(SocialTypeEnum.class)
@@ -45,5 +45,4 @@ public class AppAuthSocialLogin2ReqVO {
     @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
     private String smsCode;
 
-
 }

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java → 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;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
-@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码")
+@ApiModel("用户 APP - 社交快捷登录 Request VO,使用 code 授权码")
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-public class AppAuthSocialLoginReqVO {
+public class AppAuthSocialQuickLoginReqVO {
 
     @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
     @InEnum(SocialTypeEnum.class)

+ 42 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java

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

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindReqVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java

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

+ 6 - 6
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialUnbindReqVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java

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

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

+ 19 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java

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

+ 4 - 18
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;
 
 import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
 import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
 
 import javax.validation.Valid;
 
@@ -43,7 +45,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
      * @param userAgent 用户 UA
      * @return 身份令牌,使用 JWT 方式
      */
-    String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
+    String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
 
     /**
      * 社交登录,使用 手机号 + 手机验证码
@@ -53,23 +55,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService {
      * @param userAgent 用户 UA
      * @return 身份令牌,使用 JWT 方式
      */
-    String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
-
-    /**
-     * 社交绑定,使用 code 授权码
-     *
-     * @param userId 用户编号
-     * @param reqVO 绑定信息
-     */
-    void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO);
-
-    /**
-     * 取消社交绑定
-     *
-     * @param userId 用户编号
-     * @param reqVO 解绑信息
-     */
-    void unbindSocialUser(Long userId, @Valid AppAuthSocialUnbindReqVO reqVO);
+    String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
 
     /**
      * 获得社交认证 URL

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

+ 1 - 12
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java

@@ -37,21 +37,10 @@ public interface SocialUserApi {
      */
     void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
 
-    /**
-     * 校验社交用户的认证信息是否正确
-     * 如果校验不通过,则抛出 {@link ServiceException} 业务异常
-     *
-     * @param type 社交平台的类型
-     * @param code 授权码
-     * @param state state
-     */
-    void checkSocialUser(Integer type, String code, String state);
-
     /**
      * 获得社交用户的绑定用户编号
      * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
-     * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。
-     * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
+     * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
      *
      * @param userType 用户类型
      * @param type 社交平台的类型

+ 3 - 12
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java

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

+ 0 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java

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

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

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLogin2ReqVO.java → 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;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
 
-@ApiModel("管理后台 - 社交登录 Request VO,使用 code 授权码 + 账号密码")
+@ApiModel("管理后台 - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码")
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-public class AuthSocialLogin2ReqVO {
+public class AuthSocialBindLoginReqVO {
 
     @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
     @InEnum(SocialTypeEnum.class)

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLoginReqVO.java → 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;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
-@ApiModel("管理后台 - 社交登录 Request VO,使用 code 授权码")
+@ApiModel("管理后台 - 社交快捷登录 Request VO,使用 code 授权码")
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-public class AuthSocialLoginReqVO {
+public class AuthSocialQuickLoginReqVO {
 
     @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值")
     @InEnum(SocialTypeEnum.class)

+ 42 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java

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

+ 2 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java

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

+ 6 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialUnbindReqVO.java → yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java

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

+ 2 - 2
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 {
         @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SocialTypeEnum 枚举类")
         private Integer type;
 
-        @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
-        private String unionId;
+        @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
+        private String openid;
 
     }
 

+ 3 - 4
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 {
         return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
     }
 
-    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindReqVO reqVO);
-    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLogin2ReqVO reqVO);
-    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO);
-    SocialUserUnbindReqDTO convert(Long userId, Integer userType, AuthSocialUnbindReqVO reqVO);
+    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialBindLoginReqVO reqVO);
+
+    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialQuickLoginReqVO reqVO);
 
 }

+ 19 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java

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

+ 48 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.social;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 社交用户的绑定
+ * 即 {@link SocialUserDO} 与 UserDO 的关联表
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "system_social_user_bind", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SocialUserBindDO extends BaseDO {
+
+    /**
+     * 关联的用户编号
+     *
+     * 关联 UserDO 的编号
+     */
+    private Long userId;
+    /**
+     * 用户类型
+     *
+     * 枚举 {@link UserTypeEnum}
+     */
+    private Integer userType;
+
+    /**
+     * 社交平台的用户编号
+     *
+     * 关联 {@link SocialUserDO#getId()}
+     */
+    private Long socialUserId;
+    /**
+     * 社交平台的类型
+     *
+     * 冗余 {@link SocialUserDO#getType()}
+     */
+    private Integer socialType;
+
+}

+ 11 - 22
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java

@@ -1,15 +1,13 @@
 package cn.iocoder.yudao.module.system.dal.dataobject.social;
 
-import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
 /**
  * 社交用户
- * 通过 {@link SocialUserDO#getUserId()} 关联到对应的 {@link AdminUserDO}
  *
  * @author weir
  */
@@ -26,21 +24,10 @@ public class SocialUserDO extends BaseDO {
      */
     @TableId
     private Long id;
-    /**
-     * 关联的用户编号
-     */
-    private Long userId;
-    /**
-     * 用户类型
-     *
-     * 枚举 {@link UserTypeEnum}
-     */
-    private Integer userType;
-
     /**
      * 社交平台的类型
      *
-     * 枚举 {@link UserTypeEnum}
+     * 枚举 {@link SocialTypeEnum}
      */
     private Integer type;
 
@@ -52,13 +39,6 @@ public class SocialUserDO extends BaseDO {
      * 社交 token
      */
     private String token;
-    /**
-     * 社交的全局编号
-     *
-     * 例如说,微信平台的 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
-     * 如果没有 unionId 的平台,直接使用 openid 作为该字段的值
-     */
-    private String unionId;
     /**
      * 原始 Token 数据,一般是 JSON 格式
      */
@@ -77,6 +57,15 @@ public class SocialUserDO extends BaseDO {
      */
     private String rawUserInfo;
 
+    /**
+     * 最后一次的认证 code
+     */
+    private String code;
+    /**
+     * 最后一次的认证 state
+     */
+    private String state;
+
 }
 
 

+ 38 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java

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

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

+ 0 - 4
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 {
             "login_user:%s", // 参数为 sessionId
             STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
 
-    RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交登陆的授权用户",
-            "social_auth_user:%d:%s", // 参数为 type,code
-            STRING, AuthUser.class, Duration.ofDays(1));
-
     RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
             "social_auth_state:%s", // 参数为 state
             STRING, String.class, Duration.ofHours(24)); // 值为 state

+ 0 - 39
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java

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

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

+ 4 - 12
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 {
     String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent);
 
     /**
-     * 社交登录,使用 code 授权码
+     * 社交快捷登录,使用 code 授权码
      *
      * @param reqVO 登录信息
      * @param userIp 用户 IP
      * @param userAgent 用户 UA
      * @return 身份令牌,使用 JWT 方式
      */
-    String socialLogin(@Valid AuthSocialLoginReqVO reqVO, String userIp, String userAgent);
+    String socialLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent);
 
     /**
-     * 社交登录,使用 code 授权码 + 账号密码
+     * 社交绑定登录,使用 code 授权码 + 账号密码
      *
      * @param reqVO 登录信息
      * @param userIp 用户 IP
      * @param userAgent 用户 UA
      * @return 身份令牌,使用 JWT 方式
      */
-    String socialLogin2(@Valid AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
-
-    /**
-     * 社交绑定,使用 code 授权码
-     *
-     * @param userId 用户编号
-     * @param reqVO 绑定信息
-     */
-    void socialBind(Long userId, @Valid AuthSocialBindReqVO reqVO);
+    String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent);
 
 }

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

+ 5 - 28
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java

@@ -1,11 +1,9 @@
 package cn.iocoder.yudao.module.system.service.social;
 
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
-import me.zhyd.oauth.model.AuthUser;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
@@ -28,7 +26,7 @@ public interface SocialUserService {
     String getAuthorizeUrl(Integer type, String redirectUri);
 
     /**
-     * 获得授权的用户
+     * 授权获得对应的社交用户
      * 如果授权失败,则会抛出 {@link ServiceException} 异常
      *
      * @param type 社交平台的类型 {@link SocialTypeEnum}
@@ -37,17 +35,7 @@ public interface SocialUserService {
      * @return 授权用户
      */
     @NotNull
-    AuthUser getAuthUser(Integer type, String code, String state);
-
-    /**
-     * 获得社交用户的 unionId 编号
-     *
-     * @param authUser 社交用户
-     * @return unionId 编号
-     */
-    default String getAuthUserUnionId(AuthUser authUser) {
-        return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
-    }
+    SocialUserDO authSocialUser(Integer type, String code, String state);
 
     /**
      * 获得指定用户的社交用户列表
@@ -71,25 +59,14 @@ public interface SocialUserService {
      * @param userId 用户编号
      * @param userType 全局用户类型
      * @param type 社交平台的类型 {@link SocialTypeEnum}
-     * @param unionId 社交平台的 unionId
-     */
-    void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId);
-
-    /**
-     * 校验社交用户的认证信息是否正确
-     * 如果校验不通过,则抛出 {@link ServiceException} 业务异常
-     *
-     * @param type 社交平台的类型
-     * @param code 授权码
-     * @param state state
+     * @param openid 社交平台的 openid
      */
-    void checkSocialUser(Integer type, String code, String state);
+    void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
 
     /**
      * 获得社交用户的绑定用户编号
      * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
-     * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。
-     * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
+     * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
      *
      * @param userType 用户类型
      * @param type 社交平台的类型

+ 74 - 134
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java

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

+ 178 - 87
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java

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

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql

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

+ 17 - 4
yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql

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

+ 0 - 4
yudao-server/src/main/resources/application-dev.yaml

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

+ 0 - 4
yudao-server/src/main/resources/application-local.yaml

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

+ 9 - 34
yudao-ui-admin/src/api/login.js

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

+ 1 - 1
yudao-ui-admin/src/api/menu.js

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

+ 26 - 0
yudao-ui-admin/src/api/system/socialUser.js

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

+ 3 - 3
yudao-ui-admin/src/store/modules/user.js

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

+ 0 - 6
yudao-ui-admin/src/utils/constants.js

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

+ 0 - 1
yudao-ui-admin/src/views/login.vue

@@ -176,7 +176,6 @@ export default {
       });
     },
     doSocialLogin(socialTypeEnum) {
-      // console.log("开始Oauth登录...%o", socialTypeEnum.code);
       // 设置登录中
       this.loading = true;
       // 计算 redirectUri

+ 6 - 5
yudao-ui-admin/src/views/system/user/profile/userSocial.vue

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