From 8aa45406fdaaaf4b8c1604d31b65c020d8ff8a0d Mon Sep 17 00:00:00 2001 From: timfruit Date: Sun, 31 Oct 2021 13:09:55 +0800 Subject: [PATCH] 拓展授权登录抽取成单独的starter,拓展配置和默认配置齐平 --- yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java | 4 ++-- yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java | 4 ++-- yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java | 17 ++++++++--------- yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java | 4 ++-- yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java | 172 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-core-service/pom.xml | 10 ++++------ yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendSource.java | 33 --------------------------------- yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendToken.java | 22 ---------------------- yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthWeChatMiniProgramRequest.java | 90 ------------------------------------------------------------------------------------------ yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserCoreMapper.java | 28 ++++++++++++++++++++++++++++ yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserMapper.java | 29 ----------------------------- yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java | 3 +-- yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreService.java | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialService.java | 83 ----------------------------------------------------------------------------------- yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialCoreServiceImpl.java | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialServiceImpl.java | 178 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-core-service/src/test/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreServiceTest.java | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-dependencies/pom.xml | 6 ++++++ yudao-framework/pom.xml | 1 + yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java | 29 +++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java | 33 +++++++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java | 23 +++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring.factories | 2 ++ yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java | 4 ++-- yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java | 16 ++++++++-------- yudao-user-server/src/main/resources/application-dev.yaml | 22 +++++++--------------- yudao-user-server/src/main/resources/application-local.yaml | 18 +++++++----------- 30 files changed, 855 insertions(+), 666 deletions(-) delete mode 100644 yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendSource.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendToken.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthWeChatMiniProgramRequest.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserCoreMapper.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserMapper.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreService.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialService.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialCoreServiceImpl.java delete mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialServiceImpl.java create mode 100644 yudao-core-service/src/test/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreServiceTest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring.factories diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java index 16130e1..c557b78 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java @@ -9,7 +9,7 @@ import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysAuthService; import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService; import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService; import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -50,7 +50,7 @@ public class SysAuthController { @Resource private SysPermissionService permissionService; @Resource - private SysSocialService socialService; + private SysSocialCoreService socialService; @PostMapping("/login") @ApiOperation("使用账号密码登录") diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java index 48236a7..0b85016 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java @@ -15,7 +15,7 @@ import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleSer import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService; import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; @@ -56,7 +56,7 @@ public class SysUserProfileController { @Resource private SysRoleService roleService; @Resource - private SysSocialService socialService; + private SysSocialCoreService socialService; @GetMapping("/get") @ApiOperation("获得登录用户信息") diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java index 558967c..abb6a3d 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java @@ -17,7 +17,7 @@ import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.logger.dto.SysLoginLogCreateReqDTO; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -73,10 +73,9 @@ public class SysAuthServiceImpl implements SysAuthService { @Resource private SysUserSessionCoreService userSessionCoreService; @Resource - private SysSocialService socialService; + private SysSocialCoreService socialService; - // TODO @timfruit:静态枚举类,需要都大写,例如说 USER_TYPE_ENUM;静态变量,放在普通变量前面;这个实践不错哈。 - private static final UserTypeEnum userTypeEnum = UserTypeEnum.ADMIN; + private static final UserTypeEnum USER_TYPE_ENUM = UserTypeEnum.ADMIN; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -201,7 +200,7 @@ public class SysAuthServiceImpl implements SysAuthService { // 如果未绑定 SysSocialUserDO 用户,则无法自动登录,进行报错 String unionId = socialService.getAuthUserUnionId(authUser); - List socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, userTypeEnum); + List socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, USER_TYPE_ENUM); if (CollUtil.isEmpty(socialUsers)) { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } @@ -218,7 +217,7 @@ public class SysAuthServiceImpl implements SysAuthService { loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 // 绑定社交用户(更新) - socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM); // 缓存登录用户到 Redis 中,返回 sessionId 编号 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); @@ -235,7 +234,7 @@ public class SysAuthServiceImpl implements SysAuthService { loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 // 绑定社交用户(新增) - socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM); // 缓存登录用户到 Redis 中,返回 sessionId 编号 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); @@ -248,7 +247,7 @@ public class SysAuthServiceImpl implements SysAuthService { Assert.notNull(authUser, "授权用户不为空"); // 绑定社交用户(新增) - socialService.bindSocialUser(userId, reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(userId, reqVO.getType(), authUser, USER_TYPE_ENUM); } @Override @@ -269,7 +268,7 @@ public class SysAuthServiceImpl implements SysAuthService { reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setUserId(userId); - reqDTO.setUserType(userTypeEnum.getValue()); + reqDTO.setUserType(USER_TYPE_ENUM.getValue()); reqDTO.setUsername(username); reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserIp(ServletUtils.getClientIP()); diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java index c09a194..d8fbec5 100644 --- a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java +++ b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java @@ -11,7 +11,7 @@ import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService; import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO; import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.security.core.LoginUser; @@ -66,7 +66,7 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest { @MockBean private SysUserSessionCoreService userSessionCoreService; @MockBean - private SysSocialService socialService; + private SysSocialCoreService socialService; @Test public void testLoadUserByUsername_success() { diff --git a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java b/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java deleted file mode 100644 index cec84c1..0000000 --- a/yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.iocoder.yudao.adminserver.modules.system.service.social; - -import cn.iocoder.yudao.adminserver.BaseDbAndRedisUnitTest; -import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; -import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserMapper; -import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO; -import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; -import cn.iocoder.yudao.coreservice.modules.system.service.social.impl.SysSocialServiceImpl; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import com.xkcoding.justauth.AuthRequestFactory; -import me.zhyd.oauth.model.AuthUser; -import org.junit.jupiter.api.Test; -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; - -// TODO @timfruit:这个单元测试,挪到 yudao-core-service -/** - * {@link SysSocialServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import({SysSocialServiceImpl.class, SysSocialAuthUserRedisDAO.class}) -public class SysSocialServiceTest extends BaseDbAndRedisUnitTest { - - @Resource - private SysSocialServiceImpl socialService; - - @Resource - private SysSocialUserMapper socialUserMapper; - - @MockBean - private AuthRequestFactory authRequestFactory; - - /** - * 情况一,创建 SysSocialUserDO 的情况 - */ - @Test - public void testBindSocialUser_create() { - // mock 数据 - // 准备参数 - Long userId = randomLongId(); - Integer type = randomEle(SysSocialTypeEnum.values()).getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 - - // 调用 - socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - assertBindSocialUser(socialUsers.get(0), authUser, userId, type); - } - - /** - * 情况二,更新 SysSocialUserDO 的情况 - */ - @Test - public void testBindSocialUser_update() { - // mock 数据 - SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(dbSocialUser); - // 准备参数 - Long userId = dbSocialUser.getUserId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 - - // 调用 - socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - assertBindSocialUser(socialUsers.get(0), authUser, userId, type); - } - - /** - * 情况一和二都存在的,逻辑二的场景 - */ - @Test - public void testBindSocialUser_userId() { - // mock 数据 - SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(dbSocialUser); - // 准备参数 - Long userId = randomLongId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); - // mock 方法 - - // 调用 - socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); - // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - } - - private void assertBindSocialUser(SysSocialUserDO 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 数据 - SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(oldSocialUser); - // 准备参数 - Long userId = oldSocialUser.getUserId(); - Integer type = oldSocialUser.getType(); - String newUnionId = oldSocialUser.getUnionId(); - - // 调用 - socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN); - // 断言 - assertEquals(1L, socialUserMapper.selectCount(null).longValue()); - } - - - /** - * 情况二,如果新老的 unionId 不一致的,需解绑 - */ - @Test - public void testUnbindOldSocialUser_yes() { - // mock 数据 - SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(oldSocialUser); - // 准备参数 - Long userId = oldSocialUser.getUserId(); - Integer type = oldSocialUser.getType(); - String newUnionId = randomString(10); - - // 调用 - socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN); - // 断言 - assertEquals(0L, socialUserMapper.selectCount(null).longValue()); - } - -} diff --git a/yudao-core-service/pom.xml b/yudao-core-service/pom.xml index 4fd85c0..c4e79d7 100644 --- a/yudao-core-service/pom.xml +++ b/yudao-core-service/pom.xml @@ -36,6 +36,10 @@ cn.iocoder.boot yudao-spring-boot-starter-biz-pay + + cn.iocoder.boot + yudao-spring-boot-starter-biz-social + @@ -96,12 +100,6 @@ guava - - - com.xkcoding.justauth - justauth-spring-boot-starter - - diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendSource.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendSource.java deleted file mode 100644 index e005114..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendSource.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.compent.justauth; - -import me.zhyd.oauth.config.AuthSource; - -public enum AuthExtendSource implements AuthSource { - - /** - * 微信小程序授权登录 - */ - WECHAT_MINI_PROGRAM{ - @Override - public String authorize() { - // https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html - throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code"); - } - - @Override - public String accessToken() { - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html - // 获取openid, unionid , session_key - return "https://api.weixin.qq.com/sns/jscode2session"; - } - - @Override - public String userInfo() { - //https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html - throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息"); - } - } - - ; - -} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendToken.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendToken.java deleted file mode 100644 index 2ecb0d4..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthExtendToken.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.compent.justauth; - -import lombok.*; -import me.zhyd.oauth.model.AuthToken; - -/** - * TODO @timfruit:类注释 - * @author timfruit - * @date 2021-10-29 - */ -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class AuthExtendToken extends AuthToken { - - /** - * 微信小程序 会话密钥 - */ - private String miniSessionKey; - -} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthWeChatMiniProgramRequest.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthWeChatMiniProgramRequest.java deleted file mode 100644 index 664f915..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/compent/justauth/AuthWeChatMiniProgramRequest.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.compent.justauth; - -import com.alibaba.fastjson.JSONObject; -import me.zhyd.oauth.cache.AuthStateCache; -import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.AuthCallback; -import me.zhyd.oauth.model.AuthToken; -import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.request.AuthDefaultRequest; -import me.zhyd.oauth.utils.HttpUtils; -import me.zhyd.oauth.utils.UrlBuilder; - -// TODO @timfruit:新建一个 yudao-spring-boot-starter-biz-social 包,把这个拓展拿进去哈。另外,可以思考下。 -// 1. application-local.yaml 的配置里,justauth.extend.enum-class 能否不配置,而是自动配置好 -// 2. application-local.yaml 的配置里,justauth.extend.extend.config.WECHAT_MINI_PROGRAM 有办法和 justauth.type.WECHAT_MP 持平 -/** - * 微信小程序登陆 - * - * @author timfruit - * @date 2021-10-29 - */ -public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest { - - public AuthWeChatMiniProgramRequest(AuthConfig config) { - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM); - } - - public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) { - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache); - } - - @Override - protected AuthToken getAccessToken(AuthCallback authCallback) { - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html - String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())); - JSONObject accessTokenObject = JSONObject.parseObject(response); // TODO @timfruit:使用 JsonUtils,项目尽量避免直接使用某个 json 库 - - this.checkResponse(accessTokenObject); - - AuthExtendToken token = new AuthExtendToken(); - token.setMiniSessionKey(accessTokenObject.getString("session_key")); - token.setOpenId(accessTokenObject.getString("openid")); - token.setUnionId(accessTokenObject.getString("unionid")); - return token; - } - - @Override - protected AuthUser getUserInfo(AuthToken authToken) { - // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html - // 如果需要用户信息,需要在小程序调用函数后传给后端 - return AuthUser.builder() - .uuid(authToken.getOpenId()) - //TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来 - .nickname("") - .avatar("") - .token(authToken) - .source(source.toString()) - .build(); - } - - /** - * 检查响应内容是否正确 - * - * @param object 请求响应内容 - */ - private void checkResponse(JSONObject object) { - int code = object.getIntValue("errcode"); - if(code != 0){ // TODO @timfruit:if (code != 0) { ,注意空格 - throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg")); - } - } - - /** - * 返回获取 accessToken 的 url - * - * @param code 授权码 - * @return 返回获取 accessToken 的 url - */ - @Override - protected String accessTokenUrl(String code) { - return UrlBuilder.fromBaseUrl(source.accessToken()) - .queryParam("appid", config.getClientId()) - .queryParam("secret", config.getClientSecret()) - .queryParam("js_code", code) - .queryParam("grant_type", "authorization_code") - .build(); - } - -} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserCoreMapper.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserCoreMapper.java new file mode 100644 index 0000000..727c38e --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserCoreMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social; + +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface SysSocialUserCoreMapper 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 List selectListByTypeAndUserId(Integer userType, Collection types, Long userId) { + return selectList(new QueryWrapper().eq("user_type", userType) + .in("type", types).eq("user_id", userId)); + } + + default List selectListByUserId(Integer userType, Long userId) { + return selectList(new QueryWrapper().eq("user_type", userType).eq("user_id", userId)); + } + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserMapper.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserMapper.java deleted file mode 100644 index ee8db4e..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/mysql/social/SysSocialUserMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social; - -import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.List; - -// TODO @timfruit:SysSocialUserCoreMapper 改名,方便区分 -@Mapper -public interface SysSocialUserMapper 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 List selectListByTypeAndUserId(Integer userType, Collection types, Long userId) { - return selectList(new QueryWrapper().eq("user_type", userType) - .in("type", types).eq("user_id", userId)); - } - - default List selectListByUserId(Integer userType, Long userId) { - return selectList(new QueryWrapper().eq("user_type", userType).eq("user_id", userId)); - } - -} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java index 5142810..0c033f8 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java @@ -10,9 +10,8 @@ import javax.annotation.Resource; import static cn.iocoder.yudao.coreservice.modules.system.dal.redis.SysRedisKeyCoreConstants.SOCIAL_AUTH_USER; -// TODO @timfruit,这里的 AuthUser 还是保留全路径,主要想体现出来,不是自己定义的 /** - * 社交 {@link AuthUser} 的 RedisDAO + * 社交 {@link me.zhyd.oauth.model.AuthUser} 的 RedisDAO * * @author 芋道源码 */ diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreService.java new file mode 100644 index 0000000..17634b3 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreService.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.coreservice.modules.system.service.social; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; +import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import me.zhyd.oauth.model.AuthUser; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 社交 Service 接口,例如说社交平台的授权登录 + * + * @author 芋道源码 + */ +public interface SysSocialCoreService { + + /** + * 获得社交平台的授权 URL + * + * @param type 社交平台的类型 {@link SysSocialTypeEnum} + * @param redirectUri 重定向 URL + * @return 社交平台的授权 URL + */ + String getAuthorizeUrl(Integer type, String redirectUri); + + /** + * 获得授权的用户 + * 如果授权失败,则会抛出 {@link ServiceException} 异常 + * + * @param type 社交平台的类型 {@link SysSocialTypeEnum} + * @param code 授权码 + * @param state state + * @return 授权用户 + */ + @NotNull + AuthUser getAuthUser(Integer type, String code, String state); + + default String getAuthUserUnionId(AuthUser authUser) { + return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid()); + } + + /** + * 获得 unionId 对应的某个社交平台的“所有”社交用户 + * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的 + * + * @param type 社交平台的类型 {@link SysSocialTypeEnum} + * @param unionId 社交平台的 unionId + * @return 社交用户列表 + * @param userTypeEnum 全局用户类型 + */ + List getAllSocialUserList(Integer type, String unionId, UserTypeEnum userTypeEnum); + + /** + * 获得指定用户的社交用户列表 + * + * @param userId 用户编号 + * @return 社交用户列表 + * @param userTypeEnum 全局用户类型 + */ + List getSocialUserList(Long userId, UserTypeEnum userTypeEnum); + + /** + * 绑定社交用户 + * + * @param userId 用户编号 + * @param type 社交平台的类型 {@link SysSocialTypeEnum} + * @param authUser 授权用户 + * @param userTypeEnum 全局用户类型 + */ + void bindSocialUser(Long userId, Integer type, AuthUser authUser, UserTypeEnum userTypeEnum); + + /** + * 取消绑定社交用户 + * + * @param userId 用户编号 + * @param type 社交平台的类型 {@link SysSocialTypeEnum} + * @param unionId 社交平台的 unionId + * @param userTypeEnum 全局用户类型 + */ + void unbindSocialUser(Long userId, Integer type, String unionId, UserTypeEnum userTypeEnum); + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialService.java deleted file mode 100644 index 8a8df27..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialService.java +++ /dev/null @@ -1,83 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.service.social; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; -import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.exception.ServiceException; -import me.zhyd.oauth.model.AuthUser; - -import javax.validation.constraints.NotNull; -import java.util.List; - -/** - * 社交 Service 接口,例如说社交平台的授权登录 - * - * @author 芋道源码 - */ -// TODO @timfruit:SysSocialCoreService 改名,方便区分 -public interface SysSocialService { - - /** - * 获得社交平台的授权 URL - * - * @param type 社交平台的类型 {@link SysSocialTypeEnum} - * @param redirectUri 重定向 URL - * @return 社交平台的授权 URL - */ - String getAuthorizeUrl(Integer type, String redirectUri); - - /** - * 获得授权的用户 - * 如果授权失败,则会抛出 {@link ServiceException} 异常 - * - * @param type 社交平台的类型 {@link SysSocialTypeEnum} - * @param code 授权码 - * @param state state - * @return 授权用户 - */ - @NotNull - AuthUser getAuthUser(Integer type, String code, String state); - - default String getAuthUserUnionId(AuthUser authUser) { - return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid()); - } - - /** - * 获得 unionId 对应的某个社交平台的“所有”社交用户 - * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的 - * - * @param type 社交平台的类型 {@link SysSocialTypeEnum} - * @param unionId 社交平台的 unionId - * @return 社交用户列表 - */ - List getAllSocialUserList(Integer type, String unionId, UserTypeEnum userTypeEnum); - - /** - * 获得指定用户的社交用户列表 - * - * @param userId 用户编号 - * @return 社交用户列表 - */ - List getSocialUserList(Long userId, UserTypeEnum userTypeEnum); - - /** - * 绑定社交用户 - * - * @param userId 用户编号 - * @param type 社交平台的类型 {@link SysSocialTypeEnum} - * @param authUser 授权用户 - */ - void bindSocialUser(Long userId, Integer type, AuthUser authUser, UserTypeEnum userTypeEnum); - - /** - * 取消绑定社交用户 - * - * @param userId 用户编号 - * @param type 社交平台的类型 {@link SysSocialTypeEnum} - * @param unionId 社交平台的 unionId - */ - void unbindSocialUser(Long userId, Integer type, String unionId,UserTypeEnum userTypeEnum); - // TODO @timfruit:逗号后面要有空格;缺少了 @userTypeEnum 的注释,都补充下哈。 - -} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialCoreServiceImpl.java new file mode 100644 index 0000000..0c3bcca --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialCoreServiceImpl.java @@ -0,0 +1,178 @@ +package cn.iocoder.yudao.coreservice.modules.system.service.social.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; +import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserCoreMapper; +import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO; +import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; +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.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.SOCIAL_AUTH_FAILURE; +import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.SOCIAL_UNBIND_NOT_SELF; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + +/** + * 社交 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class SysSocialCoreServiceImpl implements SysSocialCoreService { + + @Resource + private YudaoAuthRequestFactory yudaoAuthRequestFactory; + + @Resource + private SysSocialAuthUserRedisDAO authSocialUserRedisDAO; + + @Resource + private SysSocialUserCoreMapper socialUserMapper; + + @Override + public String getAuthorizeUrl(Integer type, String redirectUri) { + // 获得对应的 AuthRequest 实现 + AuthRequest authRequest = yudaoAuthRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource()); + // 生成跳转地址 + String authorizeUri = authRequest.authorize(AuthStateUtils.createState()); + return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri); + } + + @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; + } + + // 请求获取 + authUser = this.getAuthUser0(type, authCallback); + // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 + authSocialUserRedisDAO.set(type, authCallback, authUser); + return authUser; + } + + @Override + public List getAllSocialUserList(Integer type, String unionId,UserTypeEnum userTypeEnum) { + List types = SysSocialTypeEnum.getRelationTypes(type); + return socialUserMapper.selectListByTypeAndUnionId(userTypeEnum.getValue(), types, unionId); + } + + @Override + public List getSocialUserList(Long userId,UserTypeEnum userTypeEnum) { + return socialUserMapper.selectListByUserId(userTypeEnum.getValue(), userId); + } + + @Override + @Transactional + public void bindSocialUser(Long userId, Integer type, AuthUser authUser, UserTypeEnum userTypeEnum) { + // 获得 unionId 对应的 SysSocialUserDO 列表 + String unionId = getAuthUserUnionId(authUser); + List socialUsers = this.getAllSocialUserList(type, unionId, userTypeEnum); + + // 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑 + this.unbindOldSocialUser(userId, type, unionId, userTypeEnum); + + // 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新 + // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改 + // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可 + socialUsers.forEach(socialUser -> { + if (Objects.equals(socialUser.getUserId(), userId)) { + return; + } + socialUserMapper.updateById(new SysSocialUserDO().setId(socialUser.getId()).setUserId(userId)); + }); + + // 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新 + SysSocialUserDO socialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid()); + SysSocialUserDO saveSocialUser = SysSocialUserDO.builder() // 新增和更新的通用属性 + .token(authUser.getToken().getAccessToken()).rawTokenInfo(toJsonString(authUser.getToken())) + .nickname(authUser.getNickname()).avatar(authUser.getAvatar()).rawUserInfo(toJsonString(authUser.getRawUserInfo())) + .build(); + if (socialUser == null) { + saveSocialUser.setUserId(userId).setUserType(userTypeEnum.getValue()) + .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 type, String unionId, UserTypeEnum userTypeEnum) { + // 获得 unionId 对应的所有 SysSocialUserDO 社交用户 + List socialUsers = this.getAllSocialUserList(type, unionId, userTypeEnum); + if (CollUtil.isEmpty(socialUsers)) { + return; + } + // 校验,是否解绑的是非自己的 + socialUsers.forEach(socialUser -> { + if (!Objects.equals(socialUser.getUserId(), userId)) { + throw exception(SOCIAL_UNBIND_NOT_SELF); + } + }); + + // 解绑 + socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SysSocialUserDO::getId)); + } + + @VisibleForTesting + public void unbindOldSocialUser(Long userId, Integer type, String newUnionId, UserTypeEnum userTypeEnum) { + List types = SysSocialTypeEnum.getRelationTypes(type); + List oldSocialUsers = socialUserMapper.selectListByTypeAndUserId( + userTypeEnum.getValue(), types, userId); + // 如果新老的 unionId 是一致的,说明无需解绑 + if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) { + return; + } + + // 解绑 + socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SysSocialUserDO::getId)); + } + + /** + * 请求社交平台,获得授权的用户 + * + * @param type 社交平台的类型 + * @param authCallback 授权回调 + * @return 授权的用户 + */ + private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) { + AuthRequest authRequest = yudaoAuthRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource()); + AuthResponse authResponse = authRequest.login(authCallback); + log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback), + toJsonString(authResponse)); + if (!authResponse.ok()) { + throw exception(SOCIAL_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-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialServiceImpl.java deleted file mode 100644 index bde40c3..0000000 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/service/social/impl/SysSocialServiceImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -package cn.iocoder.yudao.coreservice.modules.system.service.social.impl; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; -import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserMapper; -import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO; -import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.http.HttpUtils; -import com.google.common.annotations.VisibleForTesting; -import com.xkcoding.justauth.AuthRequestFactory; -import lombok.extern.slf4j.Slf4j; -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.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Objects; - -import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.SOCIAL_AUTH_FAILURE; -import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.SOCIAL_UNBIND_NOT_SELF; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; - -/** - * 社交 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class SysSocialServiceImpl implements SysSocialService { - - @Resource - private AuthRequestFactory authRequestFactory; - - @Resource - private SysSocialAuthUserRedisDAO authSocialUserRedisDAO; - - @Resource - private SysSocialUserMapper socialUserMapper; - - @Override - public String getAuthorizeUrl(Integer type, String redirectUri) { - // 获得对应的 AuthRequest 实现 - AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource()); - // 生成跳转地址 - String authorizeUri = authRequest.authorize(AuthStateUtils.createState()); - return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri); - } - - @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; - } - - // 请求获取 - authUser = this.getAuthUser0(type, authCallback); - // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 - authSocialUserRedisDAO.set(type, authCallback, authUser); - return authUser; - } - - @Override - public List getAllSocialUserList(Integer type, String unionId,UserTypeEnum userTypeEnum) { - List types = SysSocialTypeEnum.getRelationTypes(type); - return socialUserMapper.selectListByTypeAndUnionId(userTypeEnum.getValue(), types, unionId); - } - - @Override - public List getSocialUserList(Long userId,UserTypeEnum userTypeEnum) { - return socialUserMapper.selectListByUserId(userTypeEnum.getValue(), userId); - } - - @Override - @Transactional - public void bindSocialUser(Long userId, Integer type, AuthUser authUser, UserTypeEnum userTypeEnum) { - // 获得 unionId 对应的 SysSocialUserDO 列表 - String unionId = getAuthUserUnionId(authUser); - List socialUsers = this.getAllSocialUserList(type, unionId, userTypeEnum); - - // 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑 - this.unbindOldSocialUser(userId, type, unionId, userTypeEnum); - - // 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新 - // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改 - // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可 - socialUsers.forEach(socialUser -> { - if (Objects.equals(socialUser.getUserId(), userId)) { - return; - } - socialUserMapper.updateById(new SysSocialUserDO().setId(socialUser.getId()).setUserId(userId)); - }); - - // 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新 - SysSocialUserDO socialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid()); - SysSocialUserDO saveSocialUser = SysSocialUserDO.builder() // 新增和更新的通用属性 - .token(authUser.getToken().getAccessToken()).rawTokenInfo(toJsonString(authUser.getToken())) - .nickname(authUser.getNickname()).avatar(authUser.getAvatar()).rawUserInfo(toJsonString(authUser.getRawUserInfo())) - .build(); - if (socialUser == null) { - saveSocialUser.setUserId(userId).setUserType(userTypeEnum.getValue()) - .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 type, String unionId, UserTypeEnum userTypeEnum) { - // 获得 unionId 对应的所有 SysSocialUserDO 社交用户 - List socialUsers = this.getAllSocialUserList(type, unionId, userTypeEnum); - if (CollUtil.isEmpty(socialUsers)) { - return; - } - // 校验,是否解绑的是非自己的 - socialUsers.forEach(socialUser -> { - if (!Objects.equals(socialUser.getUserId(), userId)) { - throw exception(SOCIAL_UNBIND_NOT_SELF); - } - }); - - // 解绑 - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(socialUsers, SysSocialUserDO::getId)); - } - - @VisibleForTesting - public void unbindOldSocialUser(Long userId, Integer type, String newUnionId, UserTypeEnum userTypeEnum) { - List types = SysSocialTypeEnum.getRelationTypes(type); - List oldSocialUsers = socialUserMapper.selectListByTypeAndUserId( - userTypeEnum.getValue(), types, userId); - // 如果新老的 unionId 是一致的,说明无需解绑 - if (CollUtil.isEmpty(oldSocialUsers) || Objects.equals(newUnionId, oldSocialUsers.get(0).getUnionId())) { - return; - } - - // 解绑 - socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SysSocialUserDO::getId)); - } - - /** - * 请求社交平台,获得授权的用户 - * - * @param type 社交平台的类型 - * @param authCallback 授权回调 - * @return 授权的用户 - */ - private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) { - AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource()); - AuthResponse authResponse = authRequest.login(authCallback); - log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback), - toJsonString(authResponse)); - if (!authResponse.ok()) { - throw exception(SOCIAL_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-core-service/src/test/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreServiceTest.java b/yudao-core-service/src/test/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreServiceTest.java new file mode 100644 index 0000000..2a33dd8 --- /dev/null +++ b/yudao-core-service/src/test/java/cn/iocoder/yudao/coreservice/modules/system/service/social/SysSocialCoreServiceTest.java @@ -0,0 +1,171 @@ +package cn.iocoder.yudao.coreservice.modules.system.service.social; + +import cn.iocoder.yudao.coreservice.BaseDbAndRedisUnitTest; +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO; +import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserCoreMapper; +import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO; +import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum; +import cn.iocoder.yudao.coreservice.modules.system.service.social.impl.SysSocialCoreServiceImpl; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import com.xkcoding.justauth.AuthRequestFactory; +import me.zhyd.oauth.model.AuthUser; +import org.junit.jupiter.api.Test; +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; + +/** + * {@link SysSocialCoreServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import({SysSocialCoreServiceImpl.class, SysSocialAuthUserRedisDAO.class}) +public class SysSocialCoreServiceTest extends BaseDbAndRedisUnitTest { + + @Resource + private SysSocialCoreServiceImpl socialService; + + @Resource + private SysSocialUserCoreMapper socialUserMapper; + + @MockBean + private AuthRequestFactory authRequestFactory; + + /** + * 情况一,创建 SysSocialUserDO 的情况 + */ + @Test + public void testBindSocialUser_create() { + // mock 数据 + // 准备参数 + Long userId = randomLongId(); + Integer type = randomEle(SysSocialTypeEnum.values()).getType(); + AuthUser authUser = randomPojo(AuthUser.class); + // mock 方法 + + // 调用 + socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); + // 断言 + List socialUsers = socialUserMapper.selectList("user_id", userId); + assertEquals(1, socialUsers.size()); + assertBindSocialUser(socialUsers.get(0), authUser, userId, type); + } + + /** + * 情况二,更新 SysSocialUserDO 的情况 + */ + @Test + public void testBindSocialUser_update() { + // mock 数据 + SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { + socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); + socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); + }); + socialUserMapper.insert(dbSocialUser); + // 准备参数 + Long userId = dbSocialUser.getUserId(); + Integer type = dbSocialUser.getType(); + AuthUser authUser = randomPojo(AuthUser.class); + // mock 方法 + + // 调用 + socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); + // 断言 + List socialUsers = socialUserMapper.selectList("user_id", userId); + assertEquals(1, socialUsers.size()); + assertBindSocialUser(socialUsers.get(0), authUser, userId, type); + } + + /** + * 情况一和二都存在的,逻辑二的场景 + */ + @Test + public void testBindSocialUser_userId() { + // mock 数据 + SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { + socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); + socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); + }); + socialUserMapper.insert(dbSocialUser); + // 准备参数 + Long userId = randomLongId(); + Integer type = dbSocialUser.getType(); + AuthUser authUser = randomPojo(AuthUser.class); + // mock 方法 + + // 调用 + socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN); + // 断言 + List socialUsers = socialUserMapper.selectList("user_id", userId); + assertEquals(1, socialUsers.size()); + } + + private void assertBindSocialUser(SysSocialUserDO 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 数据 + SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { + socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); + socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); + }); + socialUserMapper.insert(oldSocialUser); + // 准备参数 + Long userId = oldSocialUser.getUserId(); + Integer type = oldSocialUser.getType(); + String newUnionId = oldSocialUser.getUnionId(); + + // 调用 + socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN); + // 断言 + assertEquals(1L, socialUserMapper.selectCount(null).longValue()); + } + + + /** + * 情况二,如果新老的 unionId 不一致的,需解绑 + */ + @Test + public void testUnbindOldSocialUser_yes() { + // mock 数据 + SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> { + socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); + socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType()); + }); + socialUserMapper.insert(oldSocialUser); + // 准备参数 + Long userId = oldSocialUser.getUserId(); + Integer type = oldSocialUser.getType(); + String newUnionId = randomString(10); + + // 调用 + socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN); + // 断言 + assertEquals(0L, socialUserMapper.selectCount(null).longValue()); + } + +} diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index fba3941..b5028e9 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -351,6 +351,12 @@ + cn.iocoder.boot + yudao-spring-boot-starter-biz-social + ${revision} + + + org.projectlombok lombok ${lombok.version} diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml index f3927b3..21eb1ea 100644 --- a/yudao-framework/pom.xml +++ b/yudao-framework/pom.xml @@ -31,6 +31,7 @@ yudao-spring-boot-starter-biz-pay yudao-spring-boot-starter-biz-weixin yudao-spring-boot-starter-extension + yudao-spring-boot-starter-biz-social yudao-framework diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml new file mode 100644 index 0000000..7588af0 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml @@ -0,0 +1,50 @@ + + + + cn.iocoder.boot + yudao-framework + ${revision} + + jar + 4.0.0 + + yudao-spring-boot-starter-biz-social + ${artifactId} + + + + cn.iocoder.boot + yudao-common + + + + org.springframework.boot + spring-boot-starter-aop + + + + cn.iocoder.boot + yudao-spring-boot-starter-web + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + com.xkcoding.justauth + justauth-spring-boot-starter + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..a6c468a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.framework.social.config; + +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; +import com.xkcoding.justauth.autoconfigure.JustAuthProperties; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.cache.AuthStateCache; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 社交自动装配类 + * + * @author timfruit + * @date 2021-10-30 + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(JustAuthProperties.class) +public class YudaoSocialAutoConfiguration { + + @Bean + @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true) + public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { + return new YudaoAuthRequestFactory(properties, authStateCache); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java new file mode 100644 index 0000000..998c70a --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.framework.social.core; + +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; +import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniProgramRequest; +import com.xkcoding.http.config.HttpConfig; +import com.xkcoding.justauth.AuthRequestFactory; +import com.xkcoding.justauth.autoconfigure.JustAuthProperties; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthRequest; +import org.springframework.util.CollectionUtils; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.Map; + +/** + * 第三方授权拓展request工厂类 + * + * @author timfruit + * @date 2021-10-31 + */ +public class YudaoAuthRequestFactory extends AuthRequestFactory { + protected JustAuthProperties properties; + protected AuthStateCache authStateCache; + + public YudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { + super(properties, authStateCache); + this.properties = properties; + this.authStateCache = authStateCache; + } + + /** + * 返回AuthRequest对象 + * + * @param source {@link AuthSource} + * @return {@link AuthRequest} + */ + public AuthRequest get(String source) { + //先尝试获取自定义扩展的 + AuthRequest authRequest = getExtendRequest(source); + + if (authRequest == null) { + authRequest = super.get(source); + } + + return authRequest; + } + + + protected AuthRequest getExtendRequest(String source) { + AuthExtendSource authExtendSource; + + try { + authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase()); + } catch (IllegalArgumentException e) { + // 无自定义匹配 + return null; + } + + // 拓展配置和默认配置齐平,properties放在一起 + AuthConfig config = properties.getType().get(authExtendSource.name()); + // 找不到对应关系,直接返回空 + if (config == null) { + return null; + } + + // 配置 http config + configureHttpConfig(authExtendSource.name(), config, properties.getHttpConfig()); + + switch (authExtendSource) { + case WECHAT_MINI_PROGRAM: + return new AuthWeChatMiniProgramRequest(config, authStateCache); + default: + return null; + } + } + + /** + * 配置 http 相关的配置 + * + * @param authSource {@link AuthSource} + * @param authConfig {@link AuthConfig} + */ + protected void configureHttpConfig(String authSource, AuthConfig authConfig, JustAuthProperties.JustAuthHttpConfig httpConfig) { + if (null == httpConfig) { + return; + } + Map proxyConfigMap = httpConfig.getProxy(); + if (CollectionUtils.isEmpty(proxyConfigMap)) { + return; + } + JustAuthProperties.JustAuthProxyConfig proxyConfig = proxyConfigMap.get(authSource); + + if (null == proxyConfig) { + return; + } + + authConfig.setHttpConfig(HttpConfig.builder() + .timeout(httpConfig.getTimeout()) + .proxy(new Proxy(Proxy.Type.valueOf(proxyConfig.getType()), new InetSocketAddress(proxyConfig.getHostname(), proxyConfig.getPort()))) + .build()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java new file mode 100644 index 0000000..bd19b8d --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.framework.social.core.enums; + +import me.zhyd.oauth.config.AuthSource; + +public enum AuthExtendSource implements AuthSource { + + /** + * 微信小程序授权登录 + */ + WECHAT_MINI_PROGRAM{ + @Override + public String authorize() { + // https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html + throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code"); + } + + @Override + public String accessToken() { + // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html + // 获取openid, unionid , session_key + return "https://api.weixin.qq.com/sns/jscode2session"; + } + + @Override + public String userInfo() { + //https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html + throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息"); + } + } + + ; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java new file mode 100644 index 0000000..3397a49 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.framework.social.core.model; + +import lombok.*; +import me.zhyd.oauth.model.AuthToken; + +/** + * 授权所需的token 拓展类 + * + * @author timfruit + * @date 2021-10-29 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AuthExtendToken extends AuthToken { + + /** + * 微信小程序 会话密钥 + */ + private String miniSessionKey; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java new file mode 100644 index 0000000..a875c3b --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.framework.social.core.request; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; +import cn.iocoder.yudao.framework.social.core.model.AuthExtendToken; +import lombok.Data; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; +import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 微信小程序登陆 + * + * @author timfruit + * @date 2021-10-29 + */ +public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest { + + public AuthWeChatMiniProgramRequest(AuthConfig config) { + super(config, AuthExtendSource.WECHAT_MINI_PROGRAM); + } + + public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html + String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())); + CodeSessionResponse accessTokenObject = JsonUtils.parseObject(response, CodeSessionResponse.class); + + this.checkResponse(accessTokenObject); + + AuthExtendToken token = new AuthExtendToken(); + token.setMiniSessionKey(accessTokenObject.session_key); + token.setOpenId(accessTokenObject.openid); + token.setUnionId(accessTokenObject.unionid); + return token; + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html + // 如果需要用户信息,需要在小程序调用函数后传给后端 + return AuthUser.builder() + .uuid(authToken.getOpenId()) + //TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来 + .nickname("") + .avatar("") + .token(authToken) + .source(source.toString()) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(CodeSessionResponse object) { + if (object.errcode != 0) { + throw new AuthException(object.errcode, object.errmsg); + } + } + + /** + * 返回获取 accessToken 的 url + * + * @param code 授权码 + * @return 返回获取 accessToken 的 url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("appid", config.getClientId()) + .queryParam("secret", config.getClientSecret()) + .queryParam("js_code", code) + .queryParam("grant_type", "authorization_code") + .build(); + } + + @Data + private static class CodeSessionResponse { + private int errcode; + private String errmsg; + private String session_key; + private String openid; + private String unionid; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..dcd4dcf --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.iocoder.yudao.framework.social.config.YudaoSocialAutoConfiguration diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java index 6d18d53..fbca500 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.userserver.modules.system.controller.auth; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*; @@ -36,7 +36,7 @@ public class SysAuthController { @Resource private SysSmsCodeService smsCodeService; @Resource - private SysSocialService socialService; + private SysSocialCoreService socialService; @PostMapping("/login") diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java index 2394cd0..e4b9388 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java @@ -9,7 +9,7 @@ import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginResultEn import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService; import cn.iocoder.yudao.coreservice.modules.system.service.logger.dto.SysLoginLogCreateReqDTO; -import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; @@ -64,8 +64,8 @@ public class SysAuthServiceImpl implements SysAuthService { @Resource private SysUserSessionCoreService userSessionCoreService; @Resource - private SysSocialService socialService; - private static final UserTypeEnum userTypeEnum = UserTypeEnum.MEMBER; + private SysSocialCoreService socialService; + private static final UserTypeEnum USER_TYPE_ENUM = UserTypeEnum.MEMBER; @Override public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException { @@ -114,7 +114,7 @@ public class SysAuthServiceImpl implements SysAuthService { // 如果未绑定 SysSocialUserDO 用户,则无法自动登录,进行报错 String unionId = socialService.getAuthUserUnionId(authUser); - List socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, userTypeEnum); + List socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, USER_TYPE_ENUM); if (CollUtil.isEmpty(socialUsers)) { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } @@ -130,7 +130,7 @@ public class SysAuthServiceImpl implements SysAuthService { LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user); // 绑定社交用户(更新) - socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM); // 缓存登录用户到 Redis 中,返回 sessionId 编号 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); @@ -147,7 +147,7 @@ public class SysAuthServiceImpl implements SysAuthService { // loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表 // 绑定社交用户(新增) - socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM); // 缓存登录用户到 Redis 中,返回 sessionId 编号 return userSessionCoreService.createUserSession(loginUser, userIp, userAgent); @@ -160,7 +160,7 @@ public class SysAuthServiceImpl implements SysAuthService { org.springframework.util.Assert.notNull(authUser, "授权用户不为空"); // 绑定社交用户(新增) - socialService.bindSocialUser(userId, reqVO.getType(), authUser, userTypeEnum); + socialService.bindSocialUser(userId, reqVO.getType(), authUser, USER_TYPE_ENUM); } private LoginUser login0(String username, String password) { @@ -271,7 +271,7 @@ public class SysAuthServiceImpl implements SysAuthService { reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType()); reqDTO.setTraceId(TracerUtils.getTraceId()); reqDTO.setUserId(userId); - reqDTO.setUserType(userTypeEnum.getValue()); + reqDTO.setUserType(USER_TYPE_ENUM.getValue()); reqDTO.setUsername(username); reqDTO.setUserAgent(ServletUtils.getUserAgent()); reqDTO.setUserIp(ServletUtils.getClientIP()); diff --git a/yudao-user-server/src/main/resources/application-dev.yaml b/yudao-user-server/src/main/resources/application-dev.yaml index 858e977..20e0056 100644 --- a/yudao-user-server/src/main/resources/application-dev.yaml +++ b/yudao-user-server/src/main/resources/application-dev.yaml @@ -145,24 +145,16 @@ yudao: justauth: enabled: true - type: # TODO @timfruit:GITEE、DINGTALK、WECHAT_ENTERPRISE 这个几个,对于用户端是不需要的哈,可以删除噢 - GITEE: # Gitee - client-id: ee61f0374a4c6c404a8717094caa7a410d76950e45ff60348015830c519ba5c1 - client-secret: 7c044a5671be3b051414db0cf2cec6ad702dd298d2416ba24ceaf608e6fa26f9 - ignore-check-redirect-uri: true - DINGTALK: # 钉钉 - client-id: dingvrnreaje3yqvzhxg - client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI - ignore-check-redirect-uri: true - WECHAT_ENTERPRISE: # 企业微信 - client-id: wwd411c69a39ad2e54 - client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw - agent-id: 1000004 - ignore-check-redirect-uri: true - WECHAT_MP: # 微信公众平台 - H5 https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index + type: + WECHAT_MP: # 微信公众平台 - 移动端 H5 https://www.yuque.com/docs/share/a795bef6-ee8a-494a-8dc4-2ef41927743b?#%20%E3%80%8A%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E6%B5%8B%E8%AF%95%E3%80%8B client-id: wxa5a05b85ac361f96 client-secret: 247073c7cebb67f27f0e769195c2a57e ignore-check-redirect-uri: true + WECHAT_MINI_PROGRAM: # 微信小程序 https://www.yuque.com/docs/share/88e3d30a-6830-45fc-8c25-dae485aef3aa?#%20%E3%80%8A%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%88%E6%9D%83%E7%99%BB%E5%BD%95%E3%80%8B + client-id: wx44d047d87e6284d8 + client-secret: 21c3b7a8a51ee1b8f5cf875848ed4466 + ignore-check-redirect-uri: true + ignore-check-state: true cache: type: REDIS prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: diff --git a/yudao-user-server/src/main/resources/application-local.yaml b/yudao-user-server/src/main/resources/application-local.yaml index c39a7cb..3fc9852 100644 --- a/yudao-user-server/src/main/resources/application-local.yaml +++ b/yudao-user-server/src/main/resources/application-local.yaml @@ -165,17 +165,13 @@ justauth: # client-id: wx5b23ba7a5589ecbb # TODO 芋艿:自己的测试,后续可以删除 # client-secret: 2a7b3b20c537e52e74afd395eb85f61f ignore-check-redirect-uri: true - extend: - enum-class: cn.iocoder.yudao.coreservice.modules.system.compent.justauth.AuthExtendSource - config: - WECHAT_MINI_PROGRAM: # 微信小程序 https://www.yuque.com/docs/share/88e3d30a-6830-45fc-8c25-dae485aef3aa?#%20%E3%80%8A%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%88%E6%9D%83%E7%99%BB%E5%BD%95%E3%80%8B - request-class: cn.iocoder.yudao.coreservice.modules.system.compent.justauth.AuthWeChatMiniProgramRequest - client-id: wx44d047d87e6284d8 - client-secret: 21c3b7a8a51ee1b8f5cf875848ed4466 -# client-id: wx63c280fe3248a3e7 # TODO 芋艿:自己的测试,后续可以删除 -# client-secret: 6f270509224a7ae1296bbf1c8cb97aed - ignore-check-redirect-uri: true - ignore-check-state: true + WECHAT_MINI_PROGRAM: # 微信小程序 https://www.yuque.com/docs/share/88e3d30a-6830-45fc-8c25-dae485aef3aa?#%20%E3%80%8A%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%8E%88%E6%9D%83%E7%99%BB%E5%BD%95%E3%80%8B + client-id: wx44d047d87e6284d8 + client-secret: 21c3b7a8a51ee1b8f5cf875848ed4466 + # client-id: wx63c280fe3248a3e7 # TODO 芋艿:自己的测试,后续可以删除 + # client-secret: 6f270509224a7ae1296bbf1c8cb97aed + ignore-check-redirect-uri: true + ignore-check-state: true cache: type: REDIS prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: -- libgit2 0.21.4