From 705a5ff6459ec811264c66e501eae7c11fbcd93b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 25 Apr 2022 01:36:29 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=A4=BE=E4=BA=A4?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=8B=AC=E7=AB=8B=E7=9A=84=E7=A4=BE=E4=BA=A4=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/social/SocialTypeEnum.java | 28 +- .../system/api/social/SocialUserApiImpl.java | 2 +- .../dataobject/social/SocialUserBindDO.java | 45 +++ .../dal/dataobject/social/SocialUserDO.java | 23 +- .../mysql/social/SocialUserBindMapper.java | 28 ++ .../dal/mysql/social/SocialUserMapper.java | 16 +- .../system/dal/redis/RedisKeyConstants.java | 4 - .../redis/social/SocialAuthUserRedisDAO.java | 39 --- .../service/auth/AdminAuthServiceImpl.java | 3 +- .../service/social/SocialUserService.java | 33 +- .../service/social/SocialUserServiceImpl.java | 184 ++++-------- .../service/social/SocialUserServiceTest.java | 283 ++++++++++-------- 12 files changed, 322 insertions(+), 366 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java index 8744c3509b..67fca5ff3b 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java @@ -22,44 +22,47 @@ public enum SocialTypeEnum implements IntArrayValuable { * Gitee * 文档链接:https://gitee.com/api/v5/oauth_doc#/ */ - GITEE(10, "GITEE"), + GITEE(10, 1, "GITEE"), /** * 钉钉 * 文档链接:https://developers.dingtalk.com/document/app/obtain-identity-credentials */ - DINGTALK(20, "DINGTALK"), + DINGTALK(20, 2, "DINGTALK"), /** * 企业微信 * 文档链接:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html */ - WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"), + WECHAT_ENTERPRISE(30, 3, "WECHAT_ENTERPRISE"), /** * 微信公众平台 - 移动端 H5 * 文档链接:https://www.cnblogs.com/juewuzhe/p/11905461.html */ - WECHAT_MP(31, "WECHAT_MP"), + WECHAT_MP(31, 3, "WECHAT_MP"), /** * 微信开放平台 - 网站应用 PC 端扫码授权登录 * 文档链接:https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证 */ - WECHAT_OPEN(32, "WECHAT_OPEN"), + WECHAT_OPEN(32, 3, "WECHAT_OPEN"), /** * 微信小程序 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html */ - WECHAT_MINI_PROGRAM(33, "WECHAT_MINI_PROGRAM"), + WECHAT_MINI_PROGRAM(33, 3, "WECHAT_MINI_PROGRAM"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); - public static final List WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type, WECHAT_MP.type, WECHAT_OPEN.type, - WECHAT_MINI_PROGRAM.type); - /** * 类型 */ private final Integer type; + /** + * 平台 + * + * 例如说,微信平台下,有企业微信、公众平台、开放平台、小程序等 + */ + private final Integer platform; /** * 类型的标识 */ @@ -74,11 +77,4 @@ public enum SocialTypeEnum implements IntArrayValuable { return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); } - public static List getRelationTypes(Integer type) { - if (WECHAT_ALL.contains(type)) { - return WECHAT_ALL; - } - return ListUtil.toList(type); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java index 02a7942bbc..a2cf44d176 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java +++ b/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 { @Override public void checkSocialUser(Integer type, String code, String state) { - socialUserService.checkSocialUser(type, code, state); + socialUserService.authSocialUser(type, code, state); } @Override diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java new file mode 100644 index 0000000000..d75f895385 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java @@ -0,0 +1,45 @@ +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 cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; +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 { + + /** + * 关联的用户编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 社交平台 + * + * 枚举 {@link SocialTypeEnum#getPlatform()} + */ + private Integer platform; + /** + * 社交的全局编号 + */ + private String unionId; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java index 9572f1e939..069536dcaf 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java +++ b/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; 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.*; @@ -26,21 +27,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; @@ -77,6 +67,15 @@ public class SocialUserDO extends BaseDO { */ private String rawUserInfo; + /** + * 最后一次的认证 code + */ + private String code; + /** + * 最后一次的认证 state + */ + private String state; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java new file mode 100644 index 0000000000..0b245e0bd3 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java @@ -0,0 +1,28 @@ +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; + +@Mapper +public interface SocialUserBindMapper extends BaseMapperX { + + default void deleteByUserTypeAndUserIdAndPlatformAndUnionId(Integer userType, Long userId, + Integer platform, String unionId) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getPlatform, platform) + .eq(SocialUserBindDO::getUnionId, unionId)); + } + + default SocialUserBindDO selectByUserTypeAndPlatformAndUnionId(Integer userType, + Integer platform, String unionId) { + return selectOne(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getPlatform, platform) + .eq(SocialUserBindDO::getUnionId, unionId)); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java index 3322ab8403..aa5a623de5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java +++ b/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,14 +12,17 @@ import java.util.List; @Mapper public interface SocialUserMapper extends BaseMapperX { - default List selectListByTypeAndUnionId(Integer userType, Collection types, String unionId) { - return selectList(new QueryWrapper().eq("user_type", userType) - .in("type", types).eq("union_id", unionId)); + default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) { + return selectOne(new LambdaQueryWrapper() + .eq(SocialUserDO::getType, type) + .eq(SocialUserDO::getCode, code) + .eq(SocialUserDO::getState, state)); } - default List selectListByTypeAndUserId(Integer userType, Collection types, Long userId) { - return selectList(new QueryWrapper().eq("user_type", userType) - .in("type", types).eq("user_id", userId)); + default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) { + return selectOne(new LambdaQueryWrapper() + .eq(SocialUserDO::getType, type) + .eq(SocialUserDO::getCode, openid)); } default List selectListByUserId(Integer userType, Long userId) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index a98edc17f9..170cb53d1d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/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 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java deleted file mode 100644 index ac71f1b5d7..0000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/social/SocialAuthUserRedisDAO.java +++ /dev/null @@ -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); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 9d021a2364..efe3d578d7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/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 { @Override public String socialLogin2(AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) { // 使用 code 授权码,进行登录 - AuthUser authUser = socialUserService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState()); - Assert.notNull(authUser, "授权用户不为空"); + socialUserService.authSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState()); // 使用账号密码,进行登录。 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java index f57baff0d3..6d89897bb7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java +++ b/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 + * @param openid 社交平台的 openid */ - void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId); - - /** - * 校验社交用户的认证信息是否正确 - * 如果校验不通过,则抛出 {@link ServiceException} 业务异常 - * - * @param type 社交平台的类型 - * @param code 授权码 - * @param state state - */ - 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 社交平台的类型 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index b408a1fd49..711e218710 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/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.hutool.core.util.StrUtil; 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; @@ -22,7 +22,6 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; 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.json.JsonUtils.toJsonString; @@ -42,8 +41,7 @@ public class SocialUserServiceImpl implements SocialUserService { private AuthRequestFactory authRequestFactory; @Resource - private SocialAuthUserRedisDAO authSocialUserRedisDAO; - + private SocialUserBindMapper socialUserBindMapper; @Resource private SocialUserMapper socialUserMapper; @@ -57,33 +55,35 @@ 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; - } + AuthUser authUser = getAuthUser(type, buildAuthCallback(code, state)); + if (authUser == null) { + throw exception(SOCIAL_USER_NOT_FOUND); + } - /** - * 获得 unionId 对应的某个社交平台的“所有”社交用户 - * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的 - * - * @param type 社交平台的类型 {@link SocialTypeEnum} - * @param unionId 社交平台的 unionId - * @param userType 全局用户类型 - * @return 社交用户列表 - */ - private List getAllSocialUserList(Integer type, String unionId, Integer userType) { - List types = SocialTypeEnum.getRelationTypes(type); - return socialUserMapper.selectListByTypeAndUnionId(userType, types, unionId); + // 保存到 DB 中 + socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid()); + if (socialUser == null) { + socialUser = new SocialUserDO(); + } + socialUser.setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) + .setUnionId(StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid())) // unionId 识别多个用户 + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())) + .setCode(code).setState(state); // 需要保存 code + state 字段,保证后续可查询 + if (socialUser.getId() == null) { + socialUserMapper.insert(socialUser); + } else { + socialUserMapper.updateById(socialUser); + } + return socialUser; } @Override @@ -92,114 +92,48 @@ public class SocialUserServiceImpl implements SocialUserService { } @Override + @Transactional public void bindSocialUser(SocialUserBindReqDTO reqDTO) { - // 使用 code 授权 - AuthUser authUser = getAuthUser(reqDTO.getType(), reqDTO.getCode(), - reqDTO.getState()); - if (authUser == null) { - throw exception(SOCIAL_USER_NOT_FOUND); - } + // 获得社交用户 + SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState()); + Assert.notNull(socialUser, "社交用户不能为空"); - // 绑定社交用户(新增) - bindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(), - reqDTO.getType(), authUser); + // 如果 userId 之前绑定过该 type 的其它账号,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(reqDTO.getUserType(), reqDTO.getUserId(), + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); + + // 绑定当前登录的社交用户 + SocialUserBindDO socialUserBind = SocialUserBindDO.builder().userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) + .unionId(socialUser.getUnionId()).build(); + socialUserBindMapper.insert(socialUserBind); } - /** - * 绑定社交用户 - * @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 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(); + @Override + public void unbindSocialUser(Long userId, Integer userType, Integer type, String openid) { + // 获得 openid 对应的 SocialUserDO 社交用户 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(type, openid); if (socialUser == null) { - saveSocialUser.setUserId(userId).setUserType(userType) - .setType(type).setOpenid(authUser.getUuid()).setUnionId(unionId); - socialUserMapper.insert(saveSocialUser); - } else { - saveSocialUser.setId(socialUser.getId()); - socialUserMapper.updateById(saveSocialUser); - } - } - - @Override - public void unbindSocialUser(Long userId, Integer userType, Integer type, String unionId) { - // 获得 unionId 对应的所有 SocialUserDO 社交用户 - List socialUsers = this.getAllSocialUserList(type, unionId, userType); - if (CollUtil.isEmpty(socialUsers)) { - return; - } - // 校验,是否解绑的是非自己的 - socialUsers.forEach(socialUser -> { - if (!Objects.equals(socialUser.getUserId(), userId)) { - throw exception(SOCIAL_USER_UNBIND_NOT_SELF); - } - }); - - // 解绑 - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SocialUserDO::getId)); - } - - @Override - public void checkSocialUser(Integer type, String code, String state) { - AuthUser authUser = getAuthUser(type, code, state); - if (authUser == null) { throw exception(SOCIAL_USER_NOT_FOUND); } + + // 获得对应的社交绑定关系 + socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(userType, userId, + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); } @Override public Long getBindUserId(Integer userType, Integer type, String code, String state) { - AuthUser authUser = getAuthUser(type, code, state); - if (authUser == null) { - throw exception(SOCIAL_USER_NOT_FOUND); - } + // 获得社交用户 + SocialUserDO socialUser = authSocialUser(type, code, state); + Assert.notNull(socialUser, "社交用户不能为空"); - // 如果未绑定 SocialUserDO 用户,则无法自动登录,进行报错 - String unionId = getAuthUserUnionId(authUser); - List socialUsers = getAllSocialUserList(type, unionId, userType); - if (CollUtil.isEmpty(socialUsers)) { + // 如果未绑定的社交用户,则无法自动登录,进行报错 + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndPlatformAndUnionId(userType, + SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); + if (socialUserBind == null) { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } - return socialUsers.get(0).getUserId(); - } - - @VisibleForTesting - public void unbindOldSocialUser(Long userId, Integer userType, Integer type, String newUnionId) { - List types = SocialTypeEnum.getRelationTypes(type); - List oldSocialUsers = socialUserMapper.selectListByTypeAndUserId(userType, types, userId); - // 如果新老的 unionId 是一致的,说明无需解绑 - if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) { - return; - } - - // 解绑 - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SocialUserDO::getId)); + return socialUserBind.getUserId(); } /** @@ -209,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService { * @param authCallback 授权回调 * @return 授权的用户 */ - private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) { + private AuthUser getAuthUser(Integer type, AuthCallback authCallback) { AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); AuthResponse authResponse = authRequest.login(authCallback); log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback), diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java index 7be8feae34..909424074c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java @@ -1,32 +1,29 @@ package cn.iocoder.yudao.module.system.service.social; -import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper; -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO; -import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.xkcoding.justauth.AuthRequestFactory; -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.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.RandomUtils.randomPojo; 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; @@ -34,132 +31,152 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { @MockBean private AuthRequestFactory authRequestFactory; - /** - * 情况一,创建 SocialUserDO 的情况 - */ @Test - public void testBindSocialUser_create() { - // mock 数据 - // 准备参数 - Long userId = randomLongId(); - Integer type = randomEle(SocialTypeEnum.values()).getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 + public void testGetAuthorizeUrl() { + try (MockedStatic authStateUtilsMock = mockStatic(AuthStateUtils.class)) { + // 准备参数 + Integer type = 31; + 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"); - // 调用 - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - assertBindSocialUser(socialUsers.get(0), authUser, userId, type); + // 调用 + String url = socialUserService.getAuthorizeUrl(type, redirectUri); + // 断言 + assertEquals("https://www.iocoder.cn/?redirect_uri=sss", url); + } } - /** - * 情况二,更新 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); - // 准备参数 - Long userId = dbSocialUser.getUserId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 - - // 调用 - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - assertBindSocialUser(socialUsers.get(0), authUser, userId, type); - } - - /** - * 情况一和二都存在的,逻辑二的场景 - */ - @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); - // 准备参数 - Long userId = randomLongId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 - - // 调用 - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - } - - private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId, - Integer type) { - 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); - // 准备参数 - Long userId = oldSocialUser.getUserId(); - Integer type = oldSocialUser.getType(); - String newUnionId = oldSocialUser.getUnionId(); - - // 调用 - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); - // 断言 - assertEquals(1L, socialUserMapper.selectCount(null).longValue()); - } - - - /** - * 情况二,如果新老的 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); - // 准备参数 - Long userId = oldSocialUser.getUserId(); - Integer type = oldSocialUser.getType(); - String newUnionId = randomString(10); - - // 调用 - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); - // 断言 - assertEquals(0L, socialUserMapper.selectCount(null).longValue()); - } +// /** +// * 情况一,创建 SocialUserDO 的情况 +// */ +// @Test +// public void testBindSocialUser_create() { +// // mock 数据 +// // 准备参数 +// Long userId = randomLongId(); +// Integer type = randomEle(SocialTypeEnum.values()).getType(); +// AuthUser authUser = randomPojo(AuthUser.class); +// // mock 方法 +// +// // 调用 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); +// // 断言 +// List socialUsers = socialUserMapper.selectList("user_id", userId); +// assertEquals(1, socialUsers.size()); +// assertBindSocialUser(socialUsers.get(0), authUser, userId, type); +// } +// +// /** +// * 情况二,更新 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); +// // 准备参数 +// Long userId = dbSocialUser.getUserId(); +// Integer type = dbSocialUser.getType(); +// AuthUser authUser = randomPojo(AuthUser.class); +// // mock 方法 +// +// // 调用 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); +// // 断言 +// List socialUsers = socialUserMapper.selectList("user_id", userId); +// assertEquals(1, socialUsers.size()); +// assertBindSocialUser(socialUsers.get(0), authUser, userId, type); +// } +// +// /** +// * 情况一和二都存在的,逻辑二的场景 +// */ +// @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); +// // 准备参数 +// Long userId = randomLongId(); +// Integer type = dbSocialUser.getType(); +// AuthUser authUser = randomPojo(AuthUser.class); +// // mock 方法 +// +// // 调用 +// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); +// // 断言 +// List socialUsers = socialUserMapper.selectList("user_id", userId); +// assertEquals(1, socialUsers.size()); +// } +// +// private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId, +// Integer type) { +// 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); +// // 准备参数 +// Long userId = oldSocialUser.getUserId(); +// Integer type = oldSocialUser.getType(); +// String newUnionId = oldSocialUser.getUnionId(); +// +// // 调用 +// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); +// // 断言 +// assertEquals(1L, socialUserMapper.selectCount(null).longValue()); +// } +// +// +// /** +// * 情况二,如果新老的 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); +// // 准备参数 +// Long userId = oldSocialUser.getUserId(); +// Integer type = oldSocialUser.getType(); +// String newUnionId = randomString(10); +// +// // 调用 +// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); +// // 断言 +// assertEquals(0L, socialUserMapper.selectCount(null).longValue()); +// } } From 89df5b3cf619e98cf4a4fb98d5bd140f2b644eb3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 26 Apr 2022 01:15:24 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=EF=BC=8C=E6=9A=82=E6=97=B6=E4=B8=8D=E8=80=83=E8=99=91=20unionI?= =?UTF-8?q?d=EF=BC=8C=E7=AE=80=E5=8C=96=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/social/SocialTypeEnum.java | 12 +- .../dal/dataobject/social/SocialUserDO.java | 7 - .../mysql/social/SocialUserBindMapper.java | 17 +- .../dal/mysql/social/SocialUserMapper.java | 6 +- .../service/social/SocialUserServiceImpl.java | 54 +++-- .../service/social/SocialUserServiceTest.java | 193 ++++++++++++++---- .../src/test/resources/sql/clean.sql | 1 + .../src/test/resources/sql/create_tables.sql | 22 +- 8 files changed, 239 insertions(+), 73 deletions(-) diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java index 67fca5ff3b..81b4e47e96 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java +++ b/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; /** * 社交平台的类型枚举 @@ -77,4 +78,11 @@ public enum SocialTypeEnum implements IntArrayValuable { return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); } + public static Set getTypes(Collection platforms) { + return Arrays.stream(values()) + .filter(socialTypeEnum -> platforms.contains(socialTypeEnum.getPlatform())) + .map(SocialTypeEnum::getType) + .collect(Collectors.toSet()); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java index 069536dcaf..7d9babf57e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java @@ -42,13 +42,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 格式 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java index 0b245e0bd3..016a7fcf82 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java @@ -5,14 +5,21 @@ 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 { - default void deleteByUserTypeAndUserIdAndPlatformAndUnionId(Integer userType, Long userId, - Integer platform, String unionId) { + default void deleteByUserTypeAndUserIdAndUnionId(Integer userType, Long userId, Integer platform) { delete(new LambdaQueryWrapperX() .eq(SocialUserBindDO::getUserType, userType) .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getPlatform, platform)); + } + + default void deleteByUserTypeAndPlatformAndUnionId(Integer userType, Integer platform, String unionId) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) .eq(SocialUserBindDO::getPlatform, platform) .eq(SocialUserBindDO::getUnionId, unionId)); } @@ -25,4 +32,10 @@ public interface SocialUserBindMapper extends BaseMapperX { .eq(SocialUserBindDO::getUnionId, unionId)); } + default List selectListByUserIdAndUserType(Long userId, Integer userType) { + return selectList(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getUserType, userType)); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java index aa5a623de5..4c1d854a73 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java @@ -25,8 +25,10 @@ public interface SocialUserMapper extends BaseMapperX { .eq(SocialUserDO::getCode, openid)); } - default List selectListByUserId(Integer userType, Long userId) { - return selectList(new QueryWrapper().eq("user_type", userType).eq("user_id", userId)); + default List selectListByUnionIdAndType(Collection unionIds, Collection types) { + return selectList(new LambdaQueryWrapper() + .in(SocialUserDO::getUnionId, unionIds) + .in(SocialUserDO::getType, types)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index 711e218710..dd4dc0f5a8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.system.service.social; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 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; @@ -21,7 +23,9 @@ 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.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; @@ -64,20 +68,18 @@ public class SocialUserServiceImpl implements SocialUserService { } // 请求获取 - AuthUser authUser = getAuthUser(type, buildAuthCallback(code, state)); - if (authUser == null) { - throw exception(SOCIAL_USER_NOT_FOUND); - } + AuthUser authUser = getAuthUser(type, code, state); + Assert.notNull(authUser, "三方用户不能为空"); // 保存到 DB 中 socialUser = socialUserMapper.selectByTypeAndOpenid(type, authUser.getUuid()); if (socialUser == null) { socialUser = new SocialUserDO(); } - socialUser.setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) + socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询 + .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) .setUnionId(StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid())) // unionId 识别多个用户 - .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())) - .setCode(code).setState(state); // 需要保存 code + state 字段,保证后续可查询 + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())); if (socialUser.getId() == null) { socialUserMapper.insert(socialUser); } else { @@ -88,7 +90,15 @@ public class SocialUserServiceImpl implements SocialUserService { @Override public List getSocialUserList(Long userId, Integer userType) { - return socialUserMapper.selectListByUserId(userType, userId); + // 获得绑定 + List socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType); + if (CollUtil.isEmpty(socialUserBinds)) { + return Collections.emptyList(); + } + // 获得社交用户 + Set platforms = CollectionUtils.convertSet(socialUserBinds, SocialUserBindDO::getPlatform); + return socialUserMapper.selectListByUnionIdAndType(CollectionUtils.convertSet(socialUserBinds, SocialUserBindDO::getUnionId), + SocialTypeEnum.getTypes(platforms)); } @Override @@ -98,13 +108,19 @@ public class SocialUserServiceImpl implements SocialUserService { SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState()); Assert.notNull(socialUser, "社交用户不能为空"); + // 如果 unionId 之前被绑定过,需要进行解绑 + Integer platform = SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(); + socialUserBindMapper.deleteByUserTypeAndPlatformAndUnionId(reqDTO.getUserType(), platform, + socialUser.getUnionId()); + // 如果 userId 之前绑定过该 type 的其它账号,需要进行解绑 - socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(reqDTO.getUserType(), reqDTO.getUserId(), - SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); + socialUserBindMapper.deleteByUserTypeAndUserIdAndUnionId(reqDTO.getUserType(), reqDTO.getUserId(), + socialUser.getUnionId()); // 绑定当前登录的社交用户 - SocialUserBindDO socialUserBind = SocialUserBindDO.builder().userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) - .unionId(socialUser.getUnionId()).build(); + SocialUserBindDO socialUserBind = SocialUserBindDO.builder() + .userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) + .platform(platform).unionId(socialUser.getUnionId()).build(); socialUserBindMapper.insert(socialUserBind); } @@ -140,22 +156,20 @@ public class SocialUserServiceImpl implements SocialUserService { * 请求社交平台,获得授权的用户 * * @param type 社交平台的类型 - * @param authCallback 授权回调 + * @param code 授权码 + * @param state 授权 state * @return 授权的用户 */ - private AuthUser getAuthUser(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(); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java index 909424074c..b7302173db 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java @@ -1,21 +1,40 @@ package cn.iocoder.yudao.module.system.service.social; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.test.core.util.AssertUtils; +import cn.iocoder.yudao.framework.test.core.util.RandomUtils; +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.framework.test.core.ut.BaseDbAndRedisUnitTest; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; 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.junit.jupiter.api.function.Executable; import org.mockito.MockedStatic; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import javax.annotation.Resource; -import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.hutool.core.util.RandomUtil.randomString; +import java.util.List; + +import static cn.hutool.core.util.RandomUtil.*; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +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.*; @@ -27,6 +46,8 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { @Resource private SocialUserMapper socialUserMapper; + @Resource + private SocialUserBindMapper socialUserBindMapper; @MockBean private AuthRequestFactory authRequestFactory; @@ -35,7 +56,7 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { public void testGetAuthorizeUrl() { try (MockedStatic authStateUtilsMock = mockStatic(AuthStateUtils.class)) { // 准备参数 - Integer type = 31; + Integer type = SocialTypeEnum.WECHAT_MP.getType(); String redirectUri = "sss"; // mock 获得对应的 AuthRequest 实现 AuthRequest authRequest = mock(AuthRequest.class); @@ -51,25 +72,138 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { } } -// /** -// * 情况一,创建 SocialUserDO 的情况 -// */ -// @Test -// public void testBindSocialUser_create() { -// // mock 数据 -// // 准备参数 -// Long userId = randomLongId(); -// Integer type = randomEle(SocialTypeEnum.values()).getType(); -// AuthUser authUser = randomPojo(AuthUser.class); -// // mock 方法 -// -// // 调用 -// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); -// // 断言 -// List socialUsers = socialUserMapper.selectList("user_id", userId); -// assertEquals(1, socialUsers.size()); -// assertBindSocialUser(socialUsers.get(0), authUser, userId, type); -// } + @Test + public void testAuthSocialUser_exists() { + // 准备参数 + 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); + + // 调用 + SocialUserDO result = socialUserService.authSocialUser(type, code, state); + // 断言 + assertPojoEquals(socialUser, result); + } + + @Test + public void testAuthSocialUser_authFailure() { + // 准备参数 + 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 authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser); + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse); + + // 调用 + SocialUserDO result = socialUserService.authSocialUser(type, code, state); + // 断言 + assertBindSocialUser(type, result, authResponse.getData()); + assertEquals(code, result.getCode()); + assertEquals(state, result.getState()); + } + + @Test + public void testAuthSocialUser_update() { + // 准备参数 + Integer type = SocialTypeEnum.GITEE.getType(); + String code = "tudou"; + String state = "yuanma"; + // mock 数据 + socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(type).setOpenid("test_openid")); + // mock 方法 + AuthRequest authRequest = mock(AuthRequest.class); + when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest); + AuthUser authUser = randomPojo(AuthUser.class); + authUser.getToken().setOpenId("test_openid"); + AuthResponse authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser); + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse); + + // 调用 + SocialUserDO result = socialUserService.authSocialUser(type, code, state); + // 断言 + assertBindSocialUser(type, result, authResponse.getData()); + assertEquals(code, result.getCode()); + assertEquals(state, result.getState()); + } + + private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) { + assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken()); + assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo()); + assertEquals(authUser.getNickname(), socialUser.getNickname()); + assertEquals(authUser.getAvatar(), socialUser.getAvatar()); + assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo()); + assertEquals(type, socialUser.getType()); + assertEquals(authUser.getUuid(), socialUser.getOpenid()); + assertEquals(authUser.getToken().getUnionId(), socialUser.getUnionId()); + } + + @Test + public void testGetSocialUserList() { + Long userId = 1L; + Integer userType = UserTypeEnum.ADMIN.getValue(); + // mock 获得绑定 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到 + .setUserId(userId).setUserType(userType).setPlatform(SocialTypeEnum.GITEE.getPlatform()) + .setUnionId("test_unionId")); + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到 + .setUserId(2L).setUserType(userType).setPlatform(SocialTypeEnum.DINGTALK.getPlatform())); + // mock 获得社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType()) + .setUnionId("test_unionId"); + socialUserMapper.insert(socialUser); // 可被查到 + socialUserMapper.insert(randomPojo(SocialUserDO.class)); // 不可被查到 + + // 调用 + List result = socialUserService.getSocialUserList(userId, userType); + // 断言 + assertEquals(1, result.size()); + assertPojoEquals(socialUser, result.get(0)); + } + + @Test + public void testBindSocialUser_create() { + // 准备参数 + 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()).setUnionId("test_unionId"); + socialUserMapper.insert(socialUser); + // mock 数据:解绑其它账号 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue()) + .setPlatform(SocialTypeEnum.GITEE.getPlatform()).setUnionId("test_delete_unionId")); + + // 调用 + socialUserService.bindSocialUser(reqDTO); + // 断言 + List socialUserBinds = socialUserBindMapper.selectList(); + assertEquals(1, socialUserBinds.size()); + + } // // /** // * 情况二,更新 SocialUserDO 的情况 @@ -119,20 +253,7 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { // List socialUsers = socialUserMapper.selectList("user_id", userId); // assertEquals(1, socialUsers.size()); // } -// -// private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId, -// Integer type) { -// 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 是一致的,无需解绑 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql index 6f0e9d3847..1a3cdce8fd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql +++ b/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"; diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql index ae59b7ab0f..9f88f8b5b3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql @@ -378,16 +378,16 @@ 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, + "union_id" varchar(64) 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 +396,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 DEFAULT '0', + "platform" tinyint NOT NULL, + "union_id" varchar(64) 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, From 878445a238cd2f47c6cdfbe6735a16bbdd9c653f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 26 Apr 2022 02:17:38 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=A4=BE=E4=BA=A4?= =?UTF-8?q?=E7=99=BB=E5=BD=95=EF=BC=8C=E5=AE=8C=E5=96=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/social/SocialTypeEnum.java | 25 +-- .../dataobject/social/SocialUserBindDO.java | 14 +- .../dal/dataobject/social/SocialUserDO.java | 3 - .../mysql/social/SocialUserBindMapper.java | 15 +- .../dal/mysql/social/SocialUserMapper.java | 8 +- .../service/social/SocialUserServiceImpl.java | 30 ++- .../service/social/SocialUserServiceTest.java | 183 +++++++----------- .../src/test/resources/sql/create_tables.sql | 19 +- 8 files changed, 110 insertions(+), 187 deletions(-) diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java index 81b4e47e96..77833b2e6c 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java @@ -23,33 +23,33 @@ public enum SocialTypeEnum implements IntArrayValuable { * Gitee * 文档链接:https://gitee.com/api/v5/oauth_doc#/ */ - GITEE(10, 1, "GITEE"), + GITEE(10, "GITEE"), /** * 钉钉 * 文档链接:https://developers.dingtalk.com/document/app/obtain-identity-credentials */ - DINGTALK(20, 2, "DINGTALK"), + DINGTALK(20, "DINGTALK"), /** * 企业微信 * 文档链接:https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html */ - WECHAT_ENTERPRISE(30, 3, "WECHAT_ENTERPRISE"), + WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"), /** * 微信公众平台 - 移动端 H5 * 文档链接:https://www.cnblogs.com/juewuzhe/p/11905461.html */ - WECHAT_MP(31, 3, "WECHAT_MP"), + WECHAT_MP(31, "WECHAT_MP"), /** * 微信开放平台 - 网站应用 PC 端扫码授权登录 * 文档链接:https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证 */ - WECHAT_OPEN(32, 3, "WECHAT_OPEN"), + WECHAT_OPEN(32, "WECHAT_OPEN"), /** * 微信小程序 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html */ - WECHAT_MINI_PROGRAM(33, 3, "WECHAT_MINI_PROGRAM"), + WECHAT_MINI_PROGRAM(33, "WECHAT_MINI_PROGRAM"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); @@ -58,12 +58,6 @@ public enum SocialTypeEnum implements IntArrayValuable { * 类型 */ private final Integer type; - /** - * 平台 - * - * 例如说,微信平台下,有企业微信、公众平台、开放平台、小程序等 - */ - private final Integer platform; /** * 类型的标识 */ @@ -78,11 +72,4 @@ public enum SocialTypeEnum implements IntArrayValuable { return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); } - public static Set getTypes(Collection platforms) { - return Arrays.stream(values()) - .filter(socialTypeEnum -> platforms.contains(socialTypeEnum.getPlatform())) - .map(SocialTypeEnum::getType) - .collect(Collectors.toSet()); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java index d75f895385..09626621e1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java @@ -22,6 +22,8 @@ public class SocialUserBindDO extends BaseDO { /** * 关联的用户编号 + * + * 关联 UserDO 的编号 */ private Long userId; /** @@ -32,14 +34,16 @@ public class SocialUserBindDO extends BaseDO { private Integer userType; /** - * 社交平台 + * 社交平台的用户编号 * - * 枚举 {@link SocialTypeEnum#getPlatform()} + * 关联 {@link SocialUserDO#getId()} */ - private Integer platform; + private Long socialUserId; /** - * 社交的全局编号 + * 社交平台的类型 + * + * 冗余 {@link SocialUserDO#getType()} */ - private String unionId; + private Integer socialType; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java index 7d9babf57e..b59553ca15 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserDO.java @@ -1,7 +1,5 @@ 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; @@ -10,7 +8,6 @@ import lombok.*; /** * 社交用户 - * 通过 {@link SocialUserDO#getUserId()} 关联到对应的 {@link AdminUserDO} * * @author weir */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java index 016a7fcf82..88d0c9227f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserBindMapper.java @@ -10,26 +10,23 @@ import java.util.List; @Mapper public interface SocialUserBindMapper extends BaseMapperX { - default void deleteByUserTypeAndUserIdAndUnionId(Integer userType, Long userId, Integer platform) { + default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) { delete(new LambdaQueryWrapperX() .eq(SocialUserBindDO::getUserType, userType) .eq(SocialUserBindDO::getUserId, userId) - .eq(SocialUserBindDO::getPlatform, platform)); + .eq(SocialUserBindDO::getSocialType, socialType)); } - default void deleteByUserTypeAndPlatformAndUnionId(Integer userType, Integer platform, String unionId) { + default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { delete(new LambdaQueryWrapperX() .eq(SocialUserBindDO::getUserType, userType) - .eq(SocialUserBindDO::getPlatform, platform) - .eq(SocialUserBindDO::getUnionId, unionId)); + .eq(SocialUserBindDO::getSocialUserId, socialUserId)); } - default SocialUserBindDO selectByUserTypeAndPlatformAndUnionId(Integer userType, - Integer platform, String unionId) { + default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { return selectOne(new LambdaQueryWrapperX() .eq(SocialUserBindDO::getUserType, userType) - .eq(SocialUserBindDO::getPlatform, platform) - .eq(SocialUserBindDO::getUnionId, unionId)); + .eq(SocialUserBindDO::getSocialUserId, socialUserId)); } default List selectListByUserIdAndUserType(Long userId, Integer userType) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java index 4c1d854a73..442cc45760 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/social/SocialUserMapper.java @@ -22,13 +22,7 @@ public interface SocialUserMapper extends BaseMapperX { default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) { return selectOne(new LambdaQueryWrapper() .eq(SocialUserDO::getType, type) - .eq(SocialUserDO::getCode, openid)); - } - - default List selectListByUnionIdAndType(Collection unionIds, Collection types) { - return selectList(new LambdaQueryWrapper() - .in(SocialUserDO::getUnionId, unionIds) - .in(SocialUserDO::getType, types)); + .eq(SocialUserDO::getOpenid, openid)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index dd4dc0f5a8..723e507c41 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.system.service.social; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 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; @@ -25,9 +23,9 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Collections; import java.util.List; -import java.util.Set; 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.*; @@ -78,7 +76,6 @@ public class SocialUserServiceImpl implements SocialUserService { } socialUser.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询 .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) - .setUnionId(StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid())) // unionId 识别多个用户 .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())); if (socialUser.getId() == null) { socialUserMapper.insert(socialUser); @@ -96,9 +93,7 @@ public class SocialUserServiceImpl implements SocialUserService { return Collections.emptyList(); } // 获得社交用户 - Set platforms = CollectionUtils.convertSet(socialUserBinds, SocialUserBindDO::getPlatform); - return socialUserMapper.selectListByUnionIdAndType(CollectionUtils.convertSet(socialUserBinds, SocialUserBindDO::getUnionId), - SocialTypeEnum.getTypes(platforms)); + return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId)); } @Override @@ -108,19 +103,17 @@ public class SocialUserServiceImpl implements SocialUserService { SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState()); Assert.notNull(socialUser, "社交用户不能为空"); - // 如果 unionId 之前被绑定过,需要进行解绑 - Integer platform = SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(); - socialUserBindMapper.deleteByUserTypeAndPlatformAndUnionId(reqDTO.getUserType(), platform, - socialUser.getUnionId()); + // 社交用户可能之前绑定过别的用户,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId()); - // 如果 userId 之前绑定过该 type 的其它账号,需要进行解绑 - socialUserBindMapper.deleteByUserTypeAndUserIdAndUnionId(reqDTO.getUserType(), reqDTO.getUserId(), - socialUser.getUnionId()); + // 用户可能之前已经绑定过该社交类型,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(), + socialUser.getType()); // 绑定当前登录的社交用户 SocialUserBindDO socialUserBind = SocialUserBindDO.builder() .userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) - .platform(platform).unionId(socialUser.getUnionId()).build(); + .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build(); socialUserBindMapper.insert(socialUserBind); } @@ -133,8 +126,7 @@ public class SocialUserServiceImpl implements SocialUserService { } // 获得对应的社交绑定关系 - socialUserBindMapper.deleteByUserTypeAndUserIdAndPlatformAndUnionId(userType, userId, - SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType()); } @Override @@ -144,8 +136,8 @@ public class SocialUserServiceImpl implements SocialUserService { Assert.notNull(socialUser, "社交用户不能为空"); // 如果未绑定的社交用户,则无法自动登录,进行报错 - SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndPlatformAndUnionId(userType, - SocialTypeEnum.valueOfType(socialUser.getType()).getPlatform(), socialUser.getUnionId()); + SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType, + socialUser.getId()); if (socialUserBind == null) { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java index b7302173db..36d6b3b974 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceTest.java @@ -1,16 +1,13 @@ package cn.iocoder.yudao.module.system.service.social; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.test.core.util.AssertUtils; -import cn.iocoder.yudao.framework.test.core.util.RandomUtils; +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.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; -import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.xkcoding.justauth.AuthRequestFactory; import me.zhyd.oauth.enums.AuthResponseStatus; import me.zhyd.oauth.model.AuthCallback; @@ -19,16 +16,15 @@ import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; 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.*; +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.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; @@ -157,24 +153,22 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo()); assertEquals(type, socialUser.getType()); assertEquals(authUser.getUuid(), socialUser.getOpenid()); - assertEquals(authUser.getToken().getUnionId(), socialUser.getUnionId()); } @Test public void testGetSocialUserList() { Long userId = 1L; Integer userType = UserTypeEnum.ADMIN.getValue(); - // mock 获得绑定 - socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到 - .setUserId(userId).setUserType(userType).setPlatform(SocialTypeEnum.GITEE.getPlatform()) - .setUnionId("test_unionId")); - socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到 - .setUserId(2L).setUserType(userType).setPlatform(SocialTypeEnum.DINGTALK.getPlatform())); // mock 获得社交用户 - SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType()) - .setUnionId("test_unionId"); + 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 result = socialUserService.getSocialUserList(userId, userType); @@ -184,120 +178,79 @@ public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { } @Test - public void testBindSocialUser_create() { + public void testBindSocialUser() { // 准备参数 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()).setUnionId("test_unionId"); + .setCode(reqDTO.getCode()).setState(reqDTO.getState()); socialUserMapper.insert(socialUser); - // mock 数据:解绑其它账号 + // mock 数据:用户可能之前已经绑定过该社交类型 socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue()) - .setPlatform(SocialTypeEnum.GITEE.getPlatform()).setUnionId("test_delete_unionId")); + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(-1L)); + // mock 数据:社交用户可能之前绑定过别的用户 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserType(UserTypeEnum.ADMIN.getValue()) + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId())); // 调用 socialUserService.bindSocialUser(reqDTO); // 断言 List socialUserBinds = socialUserBindMapper.selectList(); assertEquals(1, socialUserBinds.size()); - } -// -// /** -// * 情况二,更新 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); -// // 准备参数 -// Long userId = dbSocialUser.getUserId(); -// Integer type = dbSocialUser.getType(); -// AuthUser authUser = randomPojo(AuthUser.class); -// // mock 方法 -// -// // 调用 -// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); -// // 断言 -// List socialUsers = socialUserMapper.selectList("user_id", userId); -// assertEquals(1, socialUsers.size()); -// assertBindSocialUser(socialUsers.get(0), authUser, userId, type); -// } -// -// /** -// * 情况一和二都存在的,逻辑二的场景 -// */ -// @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); -// // 准备参数 -// Long userId = randomLongId(); -// Integer type = dbSocialUser.getType(); -// AuthUser authUser = randomPojo(AuthUser.class); -// // mock 方法 -// -// // 调用 -// socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); -// // 断言 -// List socialUsers = socialUserMapper.selectList("user_id", userId); -// assertEquals(1, socialUsers.size()); -// } -// -// /** -// * 情况一,如果新老的 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); -// // 准备参数 -// Long userId = oldSocialUser.getUserId(); -// Integer type = oldSocialUser.getType(); -// String newUnionId = oldSocialUser.getUnionId(); -// -// // 调用 -// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); -// // 断言 -// assertEquals(1L, socialUserMapper.selectCount(null).longValue()); -// } -// -// -// /** -// * 情况二,如果新老的 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); -// // 准备参数 -// Long userId = oldSocialUser.getUserId(); -// Integer type = oldSocialUser.getType(); -// String newUnionId = randomString(10); -// -// // 调用 -// socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); -// // 断言 -// assertEquals(0L, socialUserMapper.selectCount(null).longValue()); -// } + @Test + public void testUnbindSocialUser_success() { + // 准备参数 + Long userId = 1L; + Integer userType = UserTypeEnum.ADMIN.getValue(); + Integer type = SocialTypeEnum.GITEE.getType(); + String openid = "test_openid"; + // mock 数据:社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setOpenid(openid); + socialUserMapper.insert(socialUser); + // mock 数据:社交绑定关系 + SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType) + .setUserId(userId).setSocialType(type); + socialUserBindMapper.insert(socialUserBind); + + // 调用 + socialUserService.unbindSocialUser(userId, userType, type, openid); + // 断言 + assertEquals(0, socialUserBindMapper.selectCount(null).intValue()); + } + + @Test + public void testUnbindSocialUser_notFound() { + // 调用,并断言 + assertServiceException( + () -> socialUserService.unbindSocialUser(randomLong(), UserTypeEnum.ADMIN.getValue(), + SocialTypeEnum.GITEE.getType(), "test_openid"), + SOCIAL_USER_NOT_FOUND); + } + + @Test + public void testGetBindUserId() { + // 准备参数 + Integer userType = UserTypeEnum.ADMIN.getValue(); + Integer type = SocialTypeEnum.GITEE.getType(); + String code = "tudou"; + String state = "yuanma"; + // mock 社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state); + socialUserMapper.insert(socialUser); + // mock 社交用户的绑定 + Long userId = randomLong(); + SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId) + .setSocialType(type).setSocialUserId(socialUser.getId()); + socialUserBindMapper.insert(socialUserBind); + + // 调用 + Long result = socialUserService.getBindUserId(userType, type, code, state); + // 断言 + assertEquals(userId, result); + } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql index 9f88f8b5b3..709de08fa3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql @@ -381,7 +381,6 @@ CREATE TABLE IF NOT EXISTS "system_social_user" ( "type" tinyint NOT NULL, "openid" varchar(64) NOT NULL, "token" varchar(256) DEFAULT NULL, - "union_id" varchar(64) NOT NULL, "raw_token_info" varchar(1024) NOT NULL, "nickname" varchar(32) NOT NULL, "avatar" varchar(255) DEFAULT NULL, @@ -399,15 +398,15 @@ CREATE TABLE IF NOT EXISTS "system_social_user" ( 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 DEFAULT '0', - "platform" tinyint NOT NULL, - "union_id" varchar(64) 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") + "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" ( From 7227664f7796843589906354501a530fff07a20d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 26 Apr 2022 23:36:26 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E8=BF=9B=E4=B8=80=E6=AD=A5=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=A4=BE=E4=BA=A4=E7=99=BB=E9=99=86=E7=9A=84=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/YudaoSocialAutoConfiguration.java | 6 +++ .../controller/admin/auth/AuthController.java | 32 ++++---------- ...qVO.java => AuthSocialBindLoginReqVO.java} | 4 +- ...VO.java => AuthSocialQuickLoginReqVO.java} | 4 +- .../admin/socail/SocialUserController.java | 42 ++++++++++++++++++ .../user/vo/profile/UserProfileRespVO.java | 4 +- .../system/convert/auth/AuthConvert.java | 7 ++- .../convert/social/SocialUserConvert.java | 19 ++++++++ .../dataobject/social/SocialUserBindDO.java | 1 - .../config/SecurityConfiguration.java | 14 +++--- .../system/service/auth/AdminAuthService.java | 16 ++----- .../service/auth/AdminAuthServiceImpl.java | 37 ++++++---------- .../src/main/resources/application-dev.yaml | 4 -- .../src/main/resources/application-local.yaml | 4 -- yudao-ui-admin/src/api/login.js | 43 ++++--------------- yudao-ui-admin/src/api/menu.js | 2 +- yudao-ui-admin/src/api/system/socialUser.js | 26 +++++++++++ yudao-ui-admin/src/store/modules/user.js | 6 +-- yudao-ui-admin/src/utils/constants.js | 6 --- yudao-ui-admin/src/views/login.vue | 1 - .../views/system/user/profile/userSocial.vue | 11 ++--- 21 files changed, 155 insertions(+), 134 deletions(-) rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/{AuthSocialLogin2ReqVO.java => AuthSocialBindLoginReqVO.java} (92%) rename yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/{AuthSocialLoginReqVO.java => AuthSocialQuickLoginReqVO.java} (90%) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java create mode 100644 yudao-ui-admin/src/api/system/socialUser.js diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java index a6c468af50..fcae86c5e7 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java +++ b/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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index fe31c20b4c..8cb7762817 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/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> 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 socialLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) { + public CommonResult 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 socialLogin2(@RequestBody @Valid AuthSocialLogin2ReqVO reqVO) { - String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent()); + public CommonResult 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 socialBind(@RequestBody @Valid AuthSocialBindReqVO reqVO) { - authService.socialBind(getLoginUserId(), reqVO); - return CommonResult.success(true); - } - - @DeleteMapping("/social-unbind") - @ApiOperation("取消社交绑定") - public CommonResult socialUnbind(@RequestBody AuthSocialUnbindReqVO reqVO) { - socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getUnionId()); - return CommonResult.success(true); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLogin2ReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java similarity index 92% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLogin2ReqVO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindLoginReqVO.java index 725a52d8cb..71f5cfe9fd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLogin2ReqVO.java +++ b/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) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLoginReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java similarity index 90% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLoginReqVO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialQuickLoginReqVO.java index 4f51bcb989..2bec43691a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialLoginReqVO.java +++ b/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) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialUserController.java new file mode 100644 index 0000000000..c29fe8aa7e --- /dev/null +++ b/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.auth.vo.auth.AuthSocialBindReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialUnbindReqVO; +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 socialBind(@RequestBody @Valid AuthSocialBindReqVO reqVO) { + socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO)); + return CommonResult.success(true); + } + + @DeleteMapping("/unbind") + @ApiOperation("取消社交绑定") + public CommonResult socialUnbind(@RequestBody AuthSocialUnbindReqVO reqVO) { + socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getUnionId()); + return CommonResult.success(true); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java index c0b77f0f2d..c14a2903dd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java +++ b/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; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index c52328cc0d..2f19e7d840 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/social/SocialUserConvert.java new file mode 100644 index 0000000000..f27186e17a --- /dev/null +++ b/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.auth.vo.auth.AuthSocialBindReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialUnbindReqVO; +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, AuthSocialBindReqVO reqVO); + + SocialUserUnbindReqDTO convert(Long userId, Integer userType, AuthSocialUnbindReqVO reqVO); + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java index 09626621e1..dcebb069b8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java @@ -2,7 +2,6 @@ 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 cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java index 53b99ce8cf..9a5d36ab88 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/security/config/SecurityConfiguration.java +++ b/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.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(); } }; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 36ef97972d..c78f59cb18 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/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); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index efe3d578d7..9159fd0f86 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -9,9 +9,9 @@ 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.AuthSocialBindLoginReqVO; 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.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 +22,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 +81,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw new UsernameNotFoundException(username); } // 创建 LoginUser 对象 - return this.buildLoginUser(user); + return buildLoginUser(user); } @Override @@ -92,19 +91,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 +191,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,24 +206,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 授权码,进行登录 - socialUserService.authSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState()); - + 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 编号 @@ -238,12 +231,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) { // 查询用户信息 diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index a2f191b7ba..256c5beb88 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/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 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 1b4b038df4..0d5f777d01 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/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 diff --git a/yudao-ui-admin/src/api/login.js b/yudao-ui-admin/src/api/login.js index 740f497955..ffe83fb5f3 100644 --- a/yudao-ui-admin/src/api/login.js +++ b/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 - } - }) -} diff --git a/yudao-ui-admin/src/api/menu.js b/yudao-ui-admin/src/api/menu.js index 48df3dd554..0939be7575 100644 --- a/yudao-ui-admin/src/api/menu.js +++ b/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' }) } diff --git a/yudao-ui-admin/src/api/system/socialUser.js b/yudao-ui-admin/src/api/system/socialUser.js new file mode 100644 index 0000000000..39149cb609 --- /dev/null +++ b/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, unionId) { + return request({ + url: '/system/social-user/unbind', + method: 'delete', + data: { + type, + unionId + } + }) +} diff --git a/yudao-ui-admin/src/store/modules/user.js b/yudao-ui-admin/src/store/modules/user.js index 78da4ece41..d3cbf4886e 100644 --- a/yudao-ui-admin/src/store/modules/user.js +++ b/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) diff --git a/yudao-ui-admin/src/utils/constants.js b/yudao-ui-admin/src/utils/constants.js index f1e69a2461..91950e03dd 100644 --- a/yudao-ui-admin/src/utils/constants.js +++ b/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, diff --git a/yudao-ui-admin/src/views/login.vue b/yudao-ui-admin/src/views/login.vue index 95ddb98620..3e05bbb840 100644 --- a/yudao-ui-admin/src/views/login.vue +++ b/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 diff --git a/yudao-ui-admin/src/views/system/user/profile/userSocial.vue b/yudao-ui-admin/src/views/system/user/profile/userSocial.vue index 670ee423d9..2e41a4cd48 100644 --- a/yudao-ui-admin/src/views/system/user/profile/userSocial.vue +++ b/yudao-ui-admin/src/views/system/user/profile/userSocial.vue @@ -7,7 +7,7 @@