Commit 705a5ff6459ec811264c66e501eae7c11fbcd93b

Authored by YunaiV
1 parent 6e404697

重构社交登录的时候,增加独立的社交绑定表

Showing 12 changed files with 324 additions and 368 deletions
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
... ... @@ -22,45 +22,48 @@ public enum SocialTypeEnum implements IntArrayValuable {
22 22 * Gitee
23 23 * 文档链接:https://gitee.com/api/v5/oauth_doc#/
24 24 */
25   - GITEE(10, "GITEE"),
  25 + GITEE(10, 1, "GITEE"),
26 26 /**
27 27 * 钉钉
28 28 * 文档链接:https://developers.dingtalk.com/document/app/obtain-identity-credentials
29 29 */
30   - DINGTALK(20, "DINGTALK"),
  30 + DINGTALK(20, 2, "DINGTALK"),
31 31  
32 32 /**
33 33 * 企业微信
34 34 * 文档链接:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html
35 35 */
36   - WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"),
  36 + WECHAT_ENTERPRISE(30, 3, "WECHAT_ENTERPRISE"),
37 37 /**
38 38 * 微信公众平台 - 移动端 H5
39 39 * 文档链接:https://www.cnblogs.com/juewuzhe/p/11905461.html
40 40 */
41   - WECHAT_MP(31, "WECHAT_MP"),
  41 + WECHAT_MP(31, 3, "WECHAT_MP"),
42 42 /**
43 43 * 微信开放平台 - 网站应用 PC 端扫码授权登录
44 44 * 文档链接:https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证
45 45 */
46   - WECHAT_OPEN(32, "WECHAT_OPEN"),
  46 + WECHAT_OPEN(32, 3, "WECHAT_OPEN"),
47 47 /**
48 48 * 微信小程序
49 49 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
50 50 */
51   - WECHAT_MINI_PROGRAM(33, "WECHAT_MINI_PROGRAM"),
  51 + WECHAT_MINI_PROGRAM(33, 3, "WECHAT_MINI_PROGRAM"),
52 52 ;
53 53  
54 54 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray();
55 55  
56   - public static final List<Integer> WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type, WECHAT_MP.type, WECHAT_OPEN.type,
57   - WECHAT_MINI_PROGRAM.type);
58   -
59 56 /**
60 57 * 类型
61 58 */
62 59 private final Integer type;
63 60 /**
  61 + * 平台
  62 + *
  63 + * 例如说,微信平台下,有企业微信、公众平台、开放平台、小程序等
  64 + */
  65 + private final Integer platform;
  66 + /**
64 67 * 类型的标识
65 68 */
66 69 private final String source;
... ... @@ -74,11 +77,4 @@ public enum SocialTypeEnum implements IntArrayValuable {
74 77 return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
75 78 }
76 79  
77   - public static List<Integer> getRelationTypes(Integer type) {
78   - if (WECHAT_ALL.contains(type)) {
79   - return WECHAT_ALL;
80   - }
81   - return ListUtil.toList(type);
82   - }
83   -
84 80 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java
... ... @@ -38,7 +38,7 @@ public class SocialUserApiImpl implements SocialUserApi {
38 38  
39 39 @Override
40 40 public void checkSocialUser(Integer type, String code, String state) {
41   - socialUserService.checkSocialUser(type, code, state);
  41 + socialUserService.authSocialUser(type, code, state);
42 42 }
43 43  
44 44 @Override
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.dataobject.social;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  5 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
  7 +import lombok.*;
  8 +
  9 +/**
  10 + * 社交用户的绑定
  11 + * 即 {@link SocialUserDO} 与 UserDO 的关联表
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@TableName(value = "system_social_user_bind", autoResultMap = true)
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@Builder
  19 +@NoArgsConstructor
  20 +@AllArgsConstructor
  21 +public class SocialUserBindDO extends BaseDO {
  22 +
  23 + /**
  24 + * 关联的用户编号
  25 + */
  26 + private Long userId;
  27 + /**
  28 + * 用户类型
  29 + *
  30 + * 枚举 {@link UserTypeEnum}
  31 + */
  32 + private Integer userType;
  33 +
  34 + /**
  35 + * 社交平台
  36 + *
  37 + * 枚举 {@link SocialTypeEnum#getPlatform()}
  38 + */
  39 + private Integer platform;
  40 + /**
  41 + * 社交的全局编号
  42 + */
  43 + private String unionId;
  44 +
  45 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.social;
3 3 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
4 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  6 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
8 9 import lombok.*;
... ... @@ -27,20 +28,9 @@ public class SocialUserDO extends BaseDO {
27 28 @TableId
28 29 private Long id;
29 30 /**
30   - * 关联的用户编号
31   - */
32   - private Long userId;
33   - /**
34   - * 用户类型
35   - *
36   - * 枚举 {@link UserTypeEnum}
37   - */
38   - private Integer userType;
39   -
40   - /**
41 31 * 社交平台的类型
42 32 *
43   - * 枚举 {@link UserTypeEnum}
  33 + * 枚举 {@link SocialTypeEnum}
44 34 */
45 35 private Integer type;
46 36  
... ... @@ -77,6 +67,15 @@ public class SocialUserDO extends BaseDO {
77 67 */
78 68 private String rawUserInfo;
79 69  
  70 + /**
  71 + * 最后一次的认证 code
  72 + */
  73 + private String code;
  74 + /**
  75 + * 最后一次的认证 state
  76 + */
  77 + private String state;
  78 +
80 79 }
81 80  
82 81  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.dal.mysql.social;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +@Mapper
  9 +public interface SocialUserBindMapper extends BaseMapperX<SocialUserBindDO> {
  10 +
  11 + default void deleteByUserTypeAndUserIdAndPlatformAndUnionId(Integer userType, Long userId,
  12 + Integer platform, String unionId) {
  13 + delete(new LambdaQueryWrapperX<SocialUserBindDO>()
  14 + .eq(SocialUserBindDO::getUserType, userType)
  15 + .eq(SocialUserBindDO::getUserId, userId)
  16 + .eq(SocialUserBindDO::getPlatform, platform)
  17 + .eq(SocialUserBindDO::getUnionId, unionId));
  18 + }
  19 +
  20 + default SocialUserBindDO selectByUserTypeAndPlatformAndUnionId(Integer userType,
  21 + Integer platform, String unionId) {
  22 + return selectOne(new LambdaQueryWrapperX<SocialUserBindDO>()
  23 + .eq(SocialUserBindDO::getUserType, userType)
  24 + .eq(SocialUserBindDO::getPlatform, platform)
  25 + .eq(SocialUserBindDO::getUnionId, unionId));
  26 + }
  27 +
  28 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
2 2  
3 3 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
5 6 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 7 import org.apache.ibatis.annotations.Mapper;
7 8  
... ... @@ -11,14 +12,17 @@ import java.util.List;
11 12 @Mapper
12 13 public interface SocialUserMapper extends BaseMapperX<SocialUserDO> {
13 14  
14   - default List<SocialUserDO> selectListByTypeAndUnionId(Integer userType, Collection<Integer> types, String unionId) {
15   - return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType)
16   - .in("type", types).eq("union_id", unionId));
  15 + default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) {
  16 + return selectOne(new LambdaQueryWrapper<SocialUserDO>()
  17 + .eq(SocialUserDO::getType, type)
  18 + .eq(SocialUserDO::getCode, code)
  19 + .eq(SocialUserDO::getState, state));
17 20 }
18 21  
19   - default List<SocialUserDO> selectListByTypeAndUserId(Integer userType, Collection<Integer> types, Long userId) {
20   - return selectList(new QueryWrapper<SocialUserDO>().eq("user_type", userType)
21   - .in("type", types).eq("user_id", userId));
  22 + default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) {
  23 + return selectOne(new LambdaQueryWrapper<SocialUserDO>()
  24 + .eq(SocialUserDO::getType, type)
  25 + .eq(SocialUserDO::getCode, openid));
22 26 }
23 27  
24 28 default List<SocialUserDO> selectListByUserId(Integer userType, Long userId) {
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java
... ... @@ -23,10 +23,6 @@ public interface RedisKeyConstants {
23 23 "login_user:%s", // 参数为 sessionId
24 24 STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
25 25  
26   - RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交登陆的授权用户",
27   - "social_auth_user:%d:%s", // 参数为 type,code
28   - STRING, AuthUser.class, Duration.ofDays(1));
29   -
30 26 RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
31 27 "social_auth_state:%s", // 参数为 state
32 28 STRING, String.class, Duration.ofHours(24)); // 值为 state
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java deleted
1   -package cn.iocoder.yudao.module.system.dal.redis.social;
2   -
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import me.zhyd.oauth.model.AuthCallback;
5   -import me.zhyd.oauth.model.AuthUser;
6   -import org.springframework.data.redis.core.StringRedisTemplate;
7   -import org.springframework.stereotype.Repository;
8   -
9   -import javax.annotation.Resource;
10   -
11   -import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.SOCIAL_AUTH_USER;
12   -
13   -
14   -/**
15   - * 社交 {@link me.zhyd.oauth.model.AuthUser} 的 RedisDAO
16   - *
17   - * @author 芋道源码
18   - */
19   -@Repository
20   -public class SocialAuthUserRedisDAO {
21   -
22   - @Resource
23   - private StringRedisTemplate stringRedisTemplate;
24   -
25   - public AuthUser get(Integer type, AuthCallback authCallback) {
26   - String redisKey = formatKey(type, authCallback.getCode());
27   - return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), AuthUser.class);
28   - }
29   -
30   - public void set(Integer type, AuthCallback authCallback, AuthUser authUser) {
31   - String redisKey = formatKey(type, authCallback.getCode());
32   - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), SOCIAL_AUTH_USER.getTimeout());
33   - }
34   -
35   - private static String formatKey(Integer type, String code) {
36   - return String.format(SOCIAL_AUTH_USER.getKeyTemplate(), type, code);
37   - }
38   -
39   -}
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
... ... @@ -219,8 +219,7 @@ public class AdminAuthServiceImpl implements AdminAuthService {
219 219 @Override
220 220 public String socialLogin2(AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
221 221 // 使用 code 授权码,进行登录
222   - AuthUser authUser = socialUserService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
223   - Assert.notNull(authUser, "授权用户不为空");
  222 + socialUserService.authSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
224 223  
225 224 // 使用账号密码,进行登录。
226 225 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.framework.common.exception.ServiceException;
5 4 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
6 5 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
7 6 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
8   -import me.zhyd.oauth.model.AuthUser;
9 7  
10 8 import javax.validation.Valid;
11 9 import javax.validation.constraints.NotNull;
... ... @@ -28,7 +26,7 @@ public interface SocialUserService {
28 26 String getAuthorizeUrl(Integer type, String redirectUri);
29 27  
30 28 /**
31   - * 获得授权的用户
  29 + * 授权获得对应的社交用户
32 30 * 如果授权失败,则会抛出 {@link ServiceException} 异常
33 31 *
34 32 * @param type 社交平台的类型 {@link SocialTypeEnum}
... ... @@ -37,17 +35,7 @@ public interface SocialUserService {
37 35 * @return 授权用户
38 36 */
39 37 @NotNull
40   - AuthUser getAuthUser(Integer type, String code, String state);
41   -
42   - /**
43   - * 获得社交用户的 unionId 编号
44   - *
45   - * @param authUser 社交用户
46   - * @return unionId 编号
47   - */
48   - default String getAuthUserUnionId(AuthUser authUser) {
49   - return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
50   - }
  38 + SocialUserDO authSocialUser(Integer type, String code, String state);
51 39  
52 40 /**
53 41 * 获得指定用户的社交用户列表
... ... @@ -71,25 +59,14 @@ public interface SocialUserService {
71 59 * @param userId 用户编号
72 60 * @param userType 全局用户类型
73 61 * @param type 社交平台的类型 {@link SocialTypeEnum}
74   - * @param unionId 社交平台的 unionId
75   - */
76   - void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId);
77   -
78   - /**
79   - * 校验社交用户的认证信息是否正确
80   - * 如果校验不通过,则抛出 {@link ServiceException} 业务异常
81   - *
82   - * @param type 社交平台的类型
83   - * @param code 授权码
84   - * @param state state
  62 + * @param openid 社交平台的 openid
85 63 */
86   - void checkSocialUser(Integer type, String code, String state);
  64 + void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
87 65  
88 66 /**
89 67 * 获得社交用户的绑定用户编号
90 68 * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号!
91   - * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。
92   - * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
  69 + * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常
93 70 *
94 71 * @param userType 用户类型
95 72 * @param type 社交平台的类型
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
6 6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
  7 +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
7 8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
  9 +import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
8 10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
9   -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
10 11 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
11   -import com.google.common.annotations.VisibleForTesting;
12 12 import com.xkcoding.justauth.AuthRequestFactory;
13 13 import lombok.extern.slf4j.Slf4j;
14 14 import me.zhyd.oauth.model.AuthCallback;
... ... @@ -22,7 +22,6 @@ import org.springframework.validation.annotation.Validated;
22 22  
23 23 import javax.annotation.Resource;
24 24 import java.util.List;
25   -import java.util.Objects;
26 25  
27 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 27 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
... ... @@ -42,8 +41,7 @@ public class SocialUserServiceImpl implements SocialUserService {
42 41 private AuthRequestFactory authRequestFactory;
43 42  
44 43 @Resource
45   - private SocialAuthUserRedisDAO authSocialUserRedisDAO;
46   -
  44 + private SocialUserBindMapper socialUserBindMapper;
47 45 @Resource
48 46 private SocialUserMapper socialUserMapper;
49 47  
... ... @@ -57,149 +55,85 @@ public class SocialUserServiceImpl implements SocialUserService {
57 55 }
58 56  
59 57 @Override
60   - public AuthUser getAuthUser(Integer type, String code, String state) {
61   - AuthCallback authCallback = buildAuthCallback(code, state);
62   - // 从缓存中获取
63   - AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback);
64   - if (authUser != null) {
65   - return authUser;
  58 + public SocialUserDO authSocialUser(Integer type, String code, String state) {
  59 + // 优先从 DB 中获取,因为 code 有且可以使用一次。
  60 + // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
  61 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state);
  62 + if (socialUser != null) {
  63 + return socialUser;
66 64 }
67 65  
68 66 // 请求获取
69   - authUser = this.getAuthUser0(type, authCallback);
70   - // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次
71   - authSocialUserRedisDAO.set(type, authCallback, authUser);
72   - return authUser;
73   - }
74   -
75   - /**
76   - * 获得 unionId 对应的某个社交平台的“所有”社交用户
77   - * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的
78   - *
79   - * @param type 社交平台的类型 {@link SocialTypeEnum}
80   - * @param unionId 社交平台的 unionId
81   - * @param userType 全局用户类型
82   - * @return 社交用户列表
83   - */
84   - private List<SocialUserDO> getAllSocialUserList(Integer type, String unionId, Integer userType) {
85   - List<Integer> types = SocialTypeEnum.getRelationTypes(type);
86   - return socialUserMapper.selectListByTypeAndUnionId(userType, types, unionId);
87   - }
88   -
89   - @Override
90   - public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
91   - return socialUserMapper.selectListByUserId(userType, userId);
92   - }
93   -
94   - @Override
95   - public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
96   - // 使用 code 授权
97   - AuthUser authUser = getAuthUser(reqDTO.getType(), reqDTO.getCode(),
98   - reqDTO.getState());
  67 + AuthUser authUser = getAuthUser(type, buildAuthCallback(code, state));
99 68 if (authUser == null) {
100 69 throw exception(SOCIAL_USER_NOT_FOUND);
101 70 }
102 71  
103   - // 绑定社交用户(新增)
104   - bindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(),
105   - reqDTO.getType(), authUser);
106   - }
107   -
108   - /**
109   - * 绑定社交用户
110   - * @param userId 用户编号
111   - * @param userType 用户类型
112   - * @param type 社交平台的类型 {@link SocialTypeEnum}
113   - * @param authUser 授权用户
114   - */
115   - @Transactional(rollbackFor = Exception.class)
116   - protected void bindSocialUser(Long userId, Integer userType, Integer type, AuthUser authUser) {
117   - // 获得 unionId 对应的 SocialUserDO 列表
118   - String unionId = getAuthUserUnionId(authUser);
119   - List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
120   -
121   - // 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑
122   - this.unbindOldSocialUser(userId, userType, type, unionId);
123   -
124   - // 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新
125   - // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改
126   - // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可
127   - socialUsers.forEach(socialUser -> {
128   - if (Objects.equals(socialUser.getUserId(), userId)) {
129   - return;
130   - }
131   - socialUserMapper.updateById(new SocialUserDO().setId(socialUser.getId()).setUserId(userId));
132   - });
133   -
134   - // 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新
135   - SocialUserDO socialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
136   - SocialUserDO saveSocialUser = SocialUserDO.builder() // 新增和更新的通用属性
137   - .token(authUser.getToken().getAccessToken()).rawTokenInfo(toJsonString(authUser.getToken()))
138   - .nickname(authUser.getNickname()).avatar(authUser.getAvatar()).rawUserInfo(toJsonString(authUser.getRawUserInfo()))
139   - .build();
  72 + // 保存到 DB 中
  73 + socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid());
140 74 if (socialUser == null) {
141   - saveSocialUser.setUserId(userId).setUserType(userType)
142   - .setType(type).setOpenid(authUser.getUuid()).setUnionId(unionId);
143   - socialUserMapper.insert(saveSocialUser);
  75 + socialUser = new SocialUserDO();
  76 + }
  77 + socialUser.setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))
  78 + .setUnionId(StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid())) // unionId 识别多个用户
  79 + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()))
  80 + .setCode(code).setState(state); // 需要保存 code + state 字段,保证后续可查询
  81 + if (socialUser.getId() == null) {
  82 + socialUserMapper.insert(socialUser);
144 83 } else {
145   - saveSocialUser.setId(socialUser.getId());
146   - socialUserMapper.updateById(saveSocialUser);
  84 + socialUserMapper.updateById(socialUser);
147 85 }
  86 + return socialUser;
148 87 }
149 88  
150 89 @Override
151   - public void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId) {
152   - // 获得 unionId 对应的所有 SocialUserDO 社交用户
153   - List<SocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId, userType);
154   - if (CollUtil.isEmpty(socialUsers)) {
155   - return;
156   - }
157   - // 校验,是否解绑的是非自己的
158   - socialUsers.forEach(socialUser -> {
159   - if (!Objects.equals(socialUser.getUserId(), userId)) {
160   - throw exception(SOCIAL_USER_UNBIND_NOT_SELF);
161   - }
162   - });
163   -
164   - // 解绑
165   - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SocialUserDO::getId));
  90 + public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {
  91 + return socialUserMapper.selectListByUserId(userType, userId);
166 92 }
167 93  
168 94 @Override
169   - public void checkSocialUser(Integer type, String code, String state) {
170   - AuthUser authUser = getAuthUser(type, code, state);
171   - if (authUser == null) {
172   - throw exception(SOCIAL_USER_NOT_FOUND);
173   - }
  95 + @Transactional
  96 + public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
  97 + // 获得社交用户
  98 + SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
  99 + Assert.notNull(socialUser, "社交用户不能为空");
  100 +
  101 + // 如果 userId 之前绑定过该 type 的其它账号,需要进行解绑
  102 + socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(reqDTO.getUserType(), reqDTO.getUserId(),
  103 + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId());
  104 +
  105 + // 绑定当前登录的社交用户
  106 + SocialUserBindDO socialUserBind = SocialUserBindDO.builder().userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
  107 + .unionId(socialUser.getUnionId()).build();
  108 + socialUserBindMapper.insert(socialUserBind);
174 109 }
175 110  
176 111 @Override
177   - public Long getBindUserId(Integer userType, Integer type, String code, String state) {
178   - AuthUser authUser = getAuthUser(type, code, state);
179   - if (authUser == null) {
  112 + public void unbindSocialUser(Long userId, Integer userType, Integer type, String openid) {
  113 + // 获得 openid 对应的 SocialUserDO 社交用户
  114 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(type, openid);
  115 + if (socialUser == null) {
180 116 throw exception(SOCIAL_USER_NOT_FOUND);
181 117 }
182 118  
183   - // 如果未绑定 SocialUserDO 用户,则无法自动登录,进行报错
184   - String unionId = getAuthUserUnionId(authUser);
185   - List<SocialUserDO> socialUsers = getAllSocialUserList(type, unionId, userType);
186   - if (CollUtil.isEmpty(socialUsers)) {
187   - throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
188   - }
189   - return socialUsers.get(0).getUserId();
  119 + // 获得对应的社交绑定关系
  120 + socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(userType, userId,
  121 + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId());
190 122 }
191 123  
192   - @VisibleForTesting
193   - public void unbindOldSocialUser(Long userId, Integer userType, Integer type, String newUnionId) {
194   - List<Integer> types = SocialTypeEnum.getRelationTypes(type);
195   - List<SocialUserDO> oldSocialUsers = socialUserMapper.selectListByTypeAndUserId(userType, types, userId);
196   - // 如果新老的 unionId 是一致的,说明无需解绑
197   - if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) {
198   - return;
  124 + @Override
  125 + public Long getBindUserId(Integer userType, Integer type, String code, String state) {
  126 + // 获得社交用户
  127 + SocialUserDO socialUser = authSocialUser(type, code, state);
  128 + Assert.notNull(socialUser, "社交用户不能为空");
  129 +
  130 + // 如果未绑定的社交用户,则无法自动登录,进行报错
  131 + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndPlatformAndUnionId(userType,
  132 + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId());
  133 + if (socialUserBind == null) {
  134 + throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
199 135 }
200   -
201   - // 解绑
202   - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SocialUserDO::getId));
  136 + return socialUserBind.getUserId();
203 137 }
204 138  
205 139 /**
... ... @@ -209,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService {
209 143 * @param authCallback 授权回调
210 144 * @return 授权的用户
211 145 */
212   - private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) {
  146 + private AuthUser getAuthUser(Integer type, AuthCallback authCallback) {
213 147 AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
214 148 AuthResponse<?> authResponse = authRequest.login(authCallback);
215 149 log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback),
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java
1 1 package cn.iocoder.yudao.module.system.service.social;
2 2  
3   -import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
4 3 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
5   -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO;
6   -import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
7   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
8 4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
  5 +import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
9 6 import com.xkcoding.justauth.AuthRequestFactory;
10   -import me.zhyd.oauth.model.AuthUser;
  7 +import me.zhyd.oauth.request.AuthRequest;
  8 +import me.zhyd.oauth.utils.AuthStateUtils;
11 9 import org.junit.jupiter.api.Test;
  10 +import org.mockito.MockedStatic;
12 11 import org.springframework.boot.test.mock.mockito.MockBean;
13 12 import org.springframework.context.annotation.Import;
14 13  
15 14 import javax.annotation.Resource;
16   -import java.util.List;
17 15  
18 16 import static cn.hutool.core.util.RandomUtil.randomEle;
19 17 import static cn.hutool.core.util.RandomUtil.randomString;
20   -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
21   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
22 18 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
23 19 import static org.junit.jupiter.api.Assertions.assertEquals;
  20 +import static org.mockito.Mockito.*;
24 21  
25   -@Import({SocialUserServiceImpl.class, SocialAuthUserRedisDAO.class})
  22 +@Import(SocialUserServiceImpl.class)
26 23 public class SocialUserServiceTest extends BaseDbAndRedisUnitTest {
27 24  
28 25 @Resource
29   - private SocialUserServiceImpl socialService;
  26 + private SocialUserServiceImpl socialUserService;
30 27  
31 28 @Resource
32 29 private SocialUserMapper socialUserMapper;
... ... @@ -34,132 +31,152 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest {
34 31 @MockBean
35 32 private AuthRequestFactory authRequestFactory;
36 33  
37   - /**
38   - * 情况一,创建 SocialUserDO 的情况
39   - */
40 34 @Test
41   - public void testBindSocialUser_create() {
42   - // mock 数据
43   - // 准备参数
44   - Long userId = randomLongId();
45   - Integer type = randomEle(SocialTypeEnum.values()).getType();
46   - AuthUser authUser = randomPojo(AuthUser.class);
47   - // mock 方法
48   -
49   - // 调用
50   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
51   - // 断言
52   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
53   - assertEquals(1, socialUsers.size());
54   - assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
55   - }
56   -
57   - /**
58   - * 情况二,更新 SocialUserDO 的情况
59   - */
60   - @Test
61   - public void testBindSocialUser_update() {
62   - // mock 数据
63   - SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
64   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
65   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
66   - });
67   - socialUserMapper.insert(dbSocialUser);
68   - // 准备参数
69   - Long userId = dbSocialUser.getUserId();
70   - Integer type = dbSocialUser.getType();
71   - AuthUser authUser = randomPojo(AuthUser.class);
72   - // mock 方法
73   -
74   - // 调用
75   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
76   - // 断言
77   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
78   - assertEquals(1, socialUsers.size());
79   - assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
  35 + public void testGetAuthorizeUrl() {
  36 + try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {
  37 + // 准备参数
  38 + Integer type = 31;
  39 + String redirectUri = "sss";
  40 + // mock 获得对应的 AuthRequest 实现
  41 + AuthRequest authRequest = mock(AuthRequest.class);
  42 + when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest);
  43 + // mock 方法
  44 + authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman");
  45 + when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy");
  46 +
  47 + // 调用
  48 + String url = socialUserService.getAuthorizeUrl(type, redirectUri);
  49 + // 断言
  50 + assertEquals("https://www.iocoder.cn/?redirect_uri=sss", url);
  51 + }
80 52 }
81 53  
82   - /**
83   - * 情况一和二都存在的,逻辑二的场景
84   - */
85   - @Test
86   - public void testBindSocialUser_userId() {
87   - // mock 数据
88   - SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
89   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
90   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
91   - });
92   - socialUserMapper.insert(dbSocialUser);
93   - // 准备参数
94   - Long userId = randomLongId();
95   - Integer type = dbSocialUser.getType();
96   - AuthUser authUser = randomPojo(AuthUser.class);
97   - // mock 方法
98   -
99   - // 调用
100   - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
101   - // 断言
102   - List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
103   - assertEquals(1, socialUsers.size());
104   - }
105   -
106   - private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId,
107   - Integer type) {
108   - assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
109   - assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
110   - assertEquals(authUser.getNickname(), socialUser.getNickname());
111   - assertEquals(authUser.getAvatar(), socialUser.getAvatar());
112   - assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
113   - assertEquals(userId, socialUser.getUserId());
114   - assertEquals(UserTypeEnum.ADMIN.getValue(), socialUser.getUserType());
115   - assertEquals(type, socialUser.getType());
116   - assertEquals(authUser.getUuid(), socialUser.getOpenid());
117   - assertEquals(socialService.getAuthUserUnionId(authUser), socialUser.getUnionId());
118   - }
119   -
120   - /**
121   - * 情况一,如果新老的 unionId 是一致的,无需解绑
122   - */
123   - @Test
124   - public void testUnbindOldSocialUser_no() {
125   - // mock 数据
126   - SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
127   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
128   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
129   - });
130   - socialUserMapper.insert(oldSocialUser);
131   - // 准备参数
132   - Long userId = oldSocialUser.getUserId();
133   - Integer type = oldSocialUser.getType();
134   - String newUnionId = oldSocialUser.getUnionId();
135   -
136   - // 调用
137   - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
138   - // 断言
139   - assertEquals(1L, socialUserMapper.selectCount(null).longValue());
140   - }
141   -
142   -
143   - /**
144   - * 情况二,如果新老的 unionId 不一致的,需解绑
145   - */
146   - @Test
147   - public void testUnbindOldSocialUser_yes() {
148   - // mock 数据
149   - SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
150   - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
151   - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
152   - });
153   - socialUserMapper.insert(oldSocialUser);
154   - // 准备参数
155   - Long userId = oldSocialUser.getUserId();
156   - Integer type = oldSocialUser.getType();
157   - String newUnionId = randomString(10);
158   -
159   - // 调用
160   - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
161   - // 断言
162   - assertEquals(0L, socialUserMapper.selectCount(null).longValue());
163   - }
  54 +// /**
  55 +// * 情况一,创建 SocialUserDO 的情况
  56 +// */
  57 +// @Test
  58 +// public void testBindSocialUser_create() {
  59 +// // mock 数据
  60 +// // 准备参数
  61 +// Long userId = randomLongId();
  62 +// Integer type = randomEle(SocialTypeEnum.values()).getType();
  63 +// AuthUser authUser = randomPojo(AuthUser.class);
  64 +// // mock 方法
  65 +//
  66 +// // 调用
  67 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  68 +// // 断言
  69 +// List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
  70 +// assertEquals(1, socialUsers.size());
  71 +// assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
  72 +// }
  73 +//
  74 +// /**
  75 +// * 情况二,更新 SocialUserDO 的情况
  76 +// */
  77 +// @Test
  78 +// public void testBindSocialUser_update() {
  79 +// // mock 数据
  80 +// SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
  81 +// socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
  82 +// socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
  83 +// });
  84 +// socialUserMapper.insert(dbSocialUser);
  85 +// // 准备参数
  86 +// Long userId = dbSocialUser.getUserId();
  87 +// Integer type = dbSocialUser.getType();
  88 +// AuthUser authUser = randomPojo(AuthUser.class);
  89 +// // mock 方法
  90 +//
  91 +// // 调用
  92 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  93 +// // 断言
  94 +// List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
  95 +// assertEquals(1, socialUsers.size());
  96 +// assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
  97 +// }
  98 +//
  99 +// /**
  100 +// * 情况一和二都存在的,逻辑二的场景
  101 +// */
  102 +// @Test
  103 +// public void testBindSocialUser_userId() {
  104 +// // mock 数据
  105 +// SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
  106 +// socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
  107 +// socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
  108 +// });
  109 +// socialUserMapper.insert(dbSocialUser);
  110 +// // 准备参数
  111 +// Long userId = randomLongId();
  112 +// Integer type = dbSocialUser.getType();
  113 +// AuthUser authUser = randomPojo(AuthUser.class);
  114 +// // mock 方法
  115 +//
  116 +// // 调用
  117 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser);
  118 +// // 断言
  119 +// List<SocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
  120 +// assertEquals(1, socialUsers.size());
  121 +// }
  122 +//
  123 +// private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId,
  124 +// Integer type) {
  125 +// assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
  126 +// assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
  127 +// assertEquals(authUser.getNickname(), socialUser.getNickname());
  128 +// assertEquals(authUser.getAvatar(), socialUser.getAvatar());
  129 +// assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
  130 +// assertEquals(userId, socialUser.getUserId());
  131 +// assertEquals(UserTypeEnum.ADMIN.getValue(), socialUser.getUserType());
  132 +// assertEquals(type, socialUser.getType());
  133 +// assertEquals(authUser.getUuid(), socialUser.getOpenid());
  134 +// assertEquals(socialService.getAuthUserUnionId(authUser), socialUser.getUnionId());
  135 +// }
  136 +//
  137 +// /**
  138 +// * 情况一,如果新老的 unionId 是一致的,无需解绑
  139 +// */
  140 +// @Test
  141 +// public void testUnbindOldSocialUser_no() {
  142 +// // mock 数据
  143 +// SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
  144 +// socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
  145 +// socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
  146 +// });
  147 +// socialUserMapper.insert(oldSocialUser);
  148 +// // 准备参数
  149 +// Long userId = oldSocialUser.getUserId();
  150 +// Integer type = oldSocialUser.getType();
  151 +// String newUnionId = oldSocialUser.getUnionId();
  152 +//
  153 +// // 调用
  154 +// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
  155 +// // 断言
  156 +// assertEquals(1L, socialUserMapper.selectCount(null).longValue());
  157 +// }
  158 +//
  159 +//
  160 +// /**
  161 +// * 情况二,如果新老的 unionId 不一致的,需解绑
  162 +// */
  163 +// @Test
  164 +// public void testUnbindOldSocialUser_yes() {
  165 +// // mock 数据
  166 +// SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> {
  167 +// socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
  168 +// socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType());
  169 +// });
  170 +// socialUserMapper.insert(oldSocialUser);
  171 +// // 准备参数
  172 +// Long userId = oldSocialUser.getUserId();
  173 +// Integer type = oldSocialUser.getType();
  174 +// String newUnionId = randomString(10);
  175 +//
  176 +// // 调用
  177 +// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId);
  178 +// // 断言
  179 +// assertEquals(0L, socialUserMapper.selectCount(null).longValue());
  180 +// }
164 181  
165 182 }
... ...