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 a6c468af5..fcae86c5e 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-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java index d55d44dd7..8daf0bf55 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java @@ -23,7 +23,7 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti @Api(tags = "用户 APP - 认证") @RestController -@RequestMapping("/member/") +@RequestMapping("/member/auth") @Validated @Slf4j public class AppAuthController { @@ -33,7 +33,6 @@ public class AppAuthController { @PostMapping("/login") @ApiOperation("使用手机 + 密码登录") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid AppAuthLoginReqVO reqVO) { String token = authService.login(reqVO, getClientIP(), getUserAgent()); // 返回结果 @@ -42,7 +41,6 @@ public class AppAuthController { @PostMapping("/sms-login") @ApiOperation("使用手机 + 验证码登录") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) { String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent()); // 返回结果 @@ -51,7 +49,6 @@ public class AppAuthController { @PostMapping("/send-sms-code") @ApiOperation(value = "发送手机验证码") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) { authService.sendSmsCode(getLoginUserId(), reqVO); return success(true); @@ -60,7 +57,6 @@ public class AppAuthController { @PostMapping("/reset-password") @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用") @PreAuthenticated - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) { authService.resetPassword(reqVO); return success(true); @@ -87,35 +83,18 @@ public class AppAuthController { return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri)); } - @PostMapping("/social-login") - @ApiOperation(value = "社交登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户") - public CommonResult socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) { - String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent()); + @PostMapping("/social-quick-login") + @ApiOperation(value = "社交快捷登录,使用 code 授权码", notes = "适合未登录的用户,但是社交账号已绑定用户") + public CommonResult socialLogin(@RequestBody @Valid AppAuthSocialQuickLoginReqVO reqVO) { + String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent()); return success(AppAuthLoginRespVO.builder().token(token).build()); } - @PostMapping("/social-login2") - @ApiOperation(value = "社交登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定") - @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 - public CommonResult socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) { - String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent()); + @PostMapping("/social-bind-login") + @ApiOperation(value = "社交绑定登录,使用 手机号 + 手机验证码", notes = "适合未登录的用户,进行登录 + 绑定") + public CommonResult socialLogin2(@RequestBody @Valid AppAuthSocialBindLoginReqVO reqVO) { + String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent()); return success(AppAuthLoginRespVO.builder().token(token).build()); } - @PostMapping("/social-bind") - @ApiOperation(value = "社交绑定,使用 code 授权码", notes = "使用在用户已经登录的情况下") - @PreAuthenticated - public CommonResult socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) { - authService.socialBind(getLoginUserId(), reqVO); - return CommonResult.success(true); - } - - @DeleteMapping("/social-unbind") - @ApiOperation("取消社交绑定") - @PreAuthenticated - public CommonResult socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) { - authService.unbindSocialUser(getLoginUserId(), reqVO); - return CommonResult.success(true); - } - } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLogin2ReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindLoginReqVO.java similarity index 92% rename from yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLogin2ReqVO.java rename to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindLoginReqVO.java index b9a854ec1..e5c173768 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLogin2ReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindLoginReqVO.java @@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; -@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码 + 账号密码") +@ApiModel("用户 APP - 社交绑定登录 Request VO,使用 code 授权码 + 账号密码") @Data @NoArgsConstructor @AllArgsConstructor @Builder -public class AppAuthSocialLogin2ReqVO { +public class AppAuthSocialBindLoginReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) @@ -45,5 +45,4 @@ public class AppAuthSocialLogin2ReqVO { @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") private String smsCode; - } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialQuickLoginReqVO.java similarity index 90% rename from yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java rename to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialQuickLoginReqVO.java index e262765a9..02c26bcb3 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialQuickLoginReqVO.java @@ -12,12 +12,12 @@ import lombok.NoArgsConstructor; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -@ApiModel("用户 APP - 社交登录 Request VO,使用 code 授权码") +@ApiModel("用户 APP - 社交快捷登录 Request VO,使用 code 授权码") @Data @NoArgsConstructor @AllArgsConstructor @Builder -public class AppAuthSocialLoginReqVO { +public class AppAuthSocialQuickLoginReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java new file mode 100644 index 000000000..d5dac93e6 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.member.controller.app.social; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; +import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert; +import cn.iocoder.yudao.module.system.api.social.SocialUserApi; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Api(tags = "用户 App - 社交用户") +@RestController +@RequestMapping("/system/social-user") +@Validated +public class AppSocialUserController { + + @Resource + private SocialUserApi socialUserApi; + + @PostMapping("/bind") + @ApiOperation("社交绑定,使用 code 授权码") + public CommonResult socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) { + socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO)); + return CommonResult.success(true); + } + + @DeleteMapping("/unbind") + @ApiOperation("取消社交绑定") + public CommonResult socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) { + socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO)); + return CommonResult.success(true); + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java similarity index 91% rename from yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindReqVO.java rename to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java index 3ca408318..f3fcd0bb0 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialBindReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.member.controller.app.auth.vo; +package cn.iocoder.yudao.module.member.controller.app.social.vo; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; @@ -17,7 +17,7 @@ import javax.validation.constraints.NotNull; @NoArgsConstructor @AllArgsConstructor @Builder -public class AppAuthSocialBindReqVO { +public class AppSocialUserBindReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialUnbindReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java similarity index 65% rename from yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialUnbindReqVO.java rename to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java index 245417820..195238adc 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialUnbindReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.member.controller.app.auth.vo; +package cn.iocoder.yudao.module.member.controller.app.social.vo; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; @@ -12,20 +12,20 @@ import lombok.NoArgsConstructor; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -@ApiModel("用户 APP - 取消社交绑定 Request VO,使用 code 授权码") +@ApiModel("用户 APP - 取消社交绑定 Request VO") @Data @NoArgsConstructor @AllArgsConstructor @Builder -public class AppAuthSocialUnbindReqVO { +public class AppSocialUserUnbindReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) @NotNull(message = "社交平台的类型不能为空") private Integer type; - @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") - @NotEmpty(message = "社交的全局编号不能为空") - private String unionId; + @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") + @NotEmpty(message = "社交用户的 openid 不能为空") + private String openid; } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java index 133c997d3..95e2f9fd5 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.convert.auth; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; @@ -26,10 +27,9 @@ public interface AuthConvert { return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue()); } - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindReqVO reqVO); - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLogin2ReqVO reqVO); - SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO); - SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppAuthSocialUnbindReqVO reqVO); + SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialBindLoginReqVO reqVO); + SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialQuickLoginReqVO reqVO); + SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO); SmsCodeSendReqDTO convert(AppAuthSendSmsReqVO reqVO); SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java new file mode 100644 index 000000000..3c9288ba8 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.member.convert.social; + +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SocialUserConvert { + + SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class); + + SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO); + + SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO); + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java index b72ac5ee7..c8718221f 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.member.service.auth; import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import javax.validation.Valid; @@ -43,7 +45,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService { * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent); + String socialQuickLogin(@Valid AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent); /** * 社交登录,使用 手机号 + 手机验证码 @@ -53,23 +55,7 @@ public interface MemberAuthService extends SecurityAuthFrameworkService { * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent); - - /** - * 社交绑定,使用 code 授权码 - * - * @param userId 用户编号 - * @param reqVO 绑定信息 - */ - void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO); - - /** - * 取消社交绑定 - * - * @param userId 用户编号 - * @param reqVO 解绑信息 - */ - void unbindSocialUser(Long userId, @Valid AppAuthSocialUnbindReqVO reqVO); + String socialBindLogin(@Valid AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent); /** * 获得社交认证 URL diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java index f28af5081..ac0fce407 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; import cn.iocoder.yudao.module.member.controller.app.auth.vo.*; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import cn.iocoder.yudao.module.member.convert.auth.AuthConvert; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; @@ -108,7 +110,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { } @Override - public String socialLogin(AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent) { + public String socialQuickLogin(AppAuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(), reqVO.getCode(), reqVO.getState()); @@ -125,25 +127,19 @@ public class MemberAuthServiceImpl implements MemberAuthService { // 创建 LoginUser 对象 LoginUser loginUser = AuthConvert.INSTANCE.convert(user); - // 绑定社交用户(更新) - socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); - // 缓存登录用户到 Redis 中,返回 sessionId 编号 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); } @Override - public String socialLogin2(AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) { - // 校验社交平台的认证信息是否正确 - socialUserApi.checkSocialUser(reqVO.getType(), reqVO.getCode(), reqVO.getState()); - + public String socialBindLogin(AppAuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) { // 使用手机号、手机验证码登录 AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder() .mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build(); String sessionId = this.smsLogin(loginReqVO, userIp, userAgent); LoginUser loginUser = userSessionApi.getLoginUser(sessionId); - // 绑定社交用户(新增) + // 绑定社交用户 socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); return sessionId; } @@ -155,17 +151,6 @@ public class MemberAuthServiceImpl implements MemberAuthService { return userSessionApi.createUserSession(loginUser, userIp, userAgent); } - @Override - public void socialBind(Long userId, AppAuthSocialBindReqVO reqVO) { - // 绑定社交用户(新增) - socialUserApi.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO)); - } - - @Override - public void unbindSocialUser(Long userId, AppAuthSocialUnbindReqVO reqVO) { - socialUserApi.unbindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO)); - } - @Override public String getSocialAuthorizeUrl(Integer type, String redirectUri) { return socialUserApi.getAuthorizeUrl(type, redirectUri); diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java index 720712108..5d42731c2 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java @@ -37,21 +37,10 @@ public interface SocialUserApi { */ void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO); - /** - * 校验社交用户的认证信息是否正确 - * 如果校验不通过,则抛出 {@link ServiceException} 业务异常 - * - * @param type 社交平台的类型 - * @param code 授权码 - * @param state state - */ - void checkSocialUser(Integer type, String code, String state); - /** * 获得社交用户的绑定用户编号 * 注意,返回的是 MemberUser 或者 AdminUser 的 id 编号! - * 该方法会执行和 {@link #checkSocialUser(Integer, String, String)} 一样的逻辑。 - * 所以在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 + * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 * * @param userType 用户类型 * @param type 社交平台的类型 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 8744c3509..77833b2e6 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; /** * 社交平台的类型枚举 @@ -53,9 +54,6 @@ public enum SocialTypeEnum implements IntArrayValuable { public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); - public static final List WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type, WECHAT_MP.type, WECHAT_OPEN.type, - WECHAT_MINI_PROGRAM.type); - /** * 类型 */ @@ -74,11 +72,4 @@ public enum SocialTypeEnum implements IntArrayValuable { return ArrayUtil.firstMatch(o -> o.getType().equals(type), values()); } - public static List 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 02a7942bb..ae8903135 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 @@ -36,11 +36,6 @@ public class SocialUserApiImpl implements SocialUserApi { reqDTO.getType(), reqDTO.getUnionId()); } - @Override - public void checkSocialUser(Integer type, String code, String state) { - socialUserService.checkSocialUser(type, code, state); - } - @Override public Long getBindUserId(Integer userType, Integer type, String code, String state) { return socialUserService.getBindUserId(userType, type, code, state); 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 fe31c20b4..8cb776281 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 725a52d8c..71f5cfe9f 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 4f51bcb98..2bec43691 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 000000000..85585f537 --- /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.socail.vo.SocialUserBindReqVO; +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO; +import cn.iocoder.yudao.module.system.convert.social.SocialUserConvert; +import cn.iocoder.yudao.module.system.service.social.SocialUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Api(tags = "管理后台 - 社交用户") +@RestController +@RequestMapping("/system/social-user") +@Validated +public class SocialUserController { + + @Resource + private SocialUserService socialUserService; + + @PostMapping("/bind") + @ApiOperation("社交绑定,使用 code 授权码") + public CommonResult socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) { + socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO)); + return CommonResult.success(true); + } + + @DeleteMapping("/unbind") + @ApiOperation("取消社交绑定") + public CommonResult socialUnbind(@RequestBody SocialUserUnbindReqVO reqVO) { + socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getOpenid()); + 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/AuthSocialBindReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java similarity index 91% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindReqVO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java index 2a83329d8..27dd6b79a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialBindReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserBindReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; +package cn.iocoder.yudao.module.system.controller.admin.socail.vo; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.framework.common.validation.InEnum; @@ -17,7 +17,7 @@ import javax.validation.constraints.NotNull; @NoArgsConstructor @AllArgsConstructor @Builder -public class AuthSocialBindReqVO { +public class SocialUserBindReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialUnbindReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java similarity index 65% rename from yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialUnbindReqVO.java rename to yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java index 774602681..68904ce58 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/auth/AuthSocialUnbindReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/vo/SocialUserUnbindReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth; +package cn.iocoder.yudao.module.system.controller.admin.socail.vo; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; @@ -12,20 +12,20 @@ import lombok.NoArgsConstructor; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -@ApiModel("管理后台 - 取消社交绑定 Request VO,使用 code 授权码") +@ApiModel("管理后台 - 取消社交绑定 Request VO") @Data @NoArgsConstructor @AllArgsConstructor @Builder -public class AuthSocialUnbindReqVO { +public class SocialUserUnbindReqVO { @ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 UserSocialTypeEnum 枚举值") @InEnum(SocialTypeEnum.class) @NotNull(message = "社交平台的类型不能为空") private Integer type; - @ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") - @NotEmpty(message = "社交的全局编号不能为空") - private String unionId; + @ApiModelProperty(value = "社交用户的 openid", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE") + @NotEmpty(message = "社交用户的 openid 不能为空") + private String openid; } 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 c0b77f0f2..c14a2903d 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 c52328cc0..2f19e7d84 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 000000000..7cc8066d7 --- /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.socail.vo.SocialUserBindReqVO; +import cn.iocoder.yudao.module.system.controller.admin.socail.vo.SocialUserUnbindReqVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SocialUserConvert { + + SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class); + + SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO); + + SocialUserUnbindReqDTO convert(Long userId, Integer userType, SocialUserUnbindReqVO reqVO); + +} 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 000000000..dcebb069b --- /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,48 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.social; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 社交用户的绑定 + * 即 {@link SocialUserDO} 与 UserDO 的关联表 + * + * @author 芋道源码 + */ +@TableName(value = "system_social_user_bind", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SocialUserBindDO extends BaseDO { + + /** + * 关联的用户编号 + * + * 关联 UserDO 的编号 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 社交平台的用户编号 + * + * 关联 {@link SocialUserDO#getId()} + */ + private Long socialUserId; + /** + * 社交平台的类型 + * + * 冗余 {@link SocialUserDO#getType()} + */ + private Integer socialType; + +} 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 9572f1e93..b59553ca1 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,15 +1,13 @@ package cn.iocoder.yudao.module.system.dal.dataobject.social; -import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; /** * 社交用户 - * 通过 {@link SocialUserDO#getUserId()} 关联到对应的 {@link AdminUserDO} * * @author weir */ @@ -26,21 +24,10 @@ public class SocialUserDO extends BaseDO { */ @TableId private Long id; - /** - * 关联的用户编号 - */ - private Long userId; - /** - * 用户类型 - * - * 枚举 {@link UserTypeEnum} - */ - private Integer userType; - /** * 社交平台的类型 * - * 枚举 {@link UserTypeEnum} + * 枚举 {@link SocialTypeEnum} */ private Integer type; @@ -52,13 +39,6 @@ public class SocialUserDO extends BaseDO { * 社交 token */ private String token; - /** - * 社交的全局编号 - * - * 例如说,微信平台的 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html - * 如果没有 unionId 的平台,直接使用 openid 作为该字段的值 - */ - private String unionId; /** * 原始 Token 数据,一般是 JSON 格式 */ @@ -77,6 +57,15 @@ public class SocialUserDO extends BaseDO { */ private String rawUserInfo; + /** + * 最后一次的认证 code + */ + private String code; + /** + * 最后一次的认证 state + */ + private String state; + } 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 000000000..88d0c9227 --- /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,38 @@ +package cn.iocoder.yudao.module.system.dal.mysql.social; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface SocialUserBindMapper extends BaseMapperX { + + default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getUserId, userId) + .eq(SocialUserBindDO::getSocialType, socialType)); + } + + default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { + delete(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getSocialUserId, socialUserId)); + } + + default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) { + return selectOne(new LambdaQueryWrapperX() + .eq(SocialUserBindDO::getUserType, userType) + .eq(SocialUserBindDO::getSocialUserId, socialUserId)); + } + + 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 3322ab840..442cc4576 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,18 +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 List selectListByUserId(Integer userType, Long userId) { - return selectList(new QueryWrapper().eq("user_type", userType).eq("user_id", userId)); + default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) { + return selectOne(new LambdaQueryWrapper() + .eq(SocialUserDO::getType, type) + .eq(SocialUserDO::getOpenid, openid)); } } 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 a98edc17f..170cb53d1 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 ac71f1b5d..000000000 --- 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/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 53b99ce8c..9a5d36ab8 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 36ef97972..c78f59cb1 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 9d021a236..2c054b9dc 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,8 @@ import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthLoginReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLogin2ReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialLoginReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialBindLoginReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.AuthSocialQuickLoginReqVO; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; @@ -22,7 +21,6 @@ import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import lombok.extern.slf4j.Slf4j; -import me.zhyd.oauth.model.AuthUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; @@ -82,7 +80,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw new UsernameNotFoundException(username); } // 创建 LoginUser 对象 - return this.buildLoginUser(user); + return buildLoginUser(user); } @Override @@ -92,19 +90,19 @@ public class AdminAuthServiceImpl implements AdminAuthService { if (user == null) { throw new UsernameNotFoundException(String.valueOf(userId)); } - this.createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); + createLoginLog(user.getUsername(), LoginLogTypeEnum.LOGIN_MOCK, LoginResultEnum.SUCCESS); // 创建 LoginUser 对象 - return this.buildLoginUser(user); + return buildLoginUser(user); } @Override public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { // 判断验证码是否正确 - this.verifyCaptcha(reqVO); + verifyCaptcha(reqVO); // 使用账号密码,进行登录 - LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); + LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); // 缓存登陆用户到 Redis 中,返回 sessionId 编号 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); @@ -192,7 +190,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String socialLogin(AuthSocialLoginReqVO reqVO, String userIp, String userAgent) { + public String socialLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getCode(), reqVO.getState()); @@ -207,25 +205,18 @@ public class AdminAuthServiceImpl implements AdminAuthService { } // 创建 LoginUser 对象 - LoginUser loginUser = this.buildLoginUser(user); - - // 绑定社交用户(更新) - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); + LoginUser loginUser = buildLoginUser(user); // 缓存登录用户到 Redis 中,返回 sessionId 编号 return createUserSessionAfterLoginSuccess(loginUser, LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); } @Override - public String socialLogin2(AuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) { - // 使用 code 授权码,进行登录 - AuthUser authUser = socialUserService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState()); - Assert.notNull(authUser, "授权用户不为空"); - + public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) { // 使用账号密码,进行登录。 - LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); + LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); - // 绑定社交用户(新增) + // 绑定社交用户 socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); // 缓存登录用户到 Redis 中,返回 sessionId 编号 @@ -239,12 +230,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { return userSessionService.createUserSession(loginUser, userIp, userAgent); } - @Override - public void socialBind(Long userId, AuthSocialBindReqVO reqVO) { - // 绑定社交用户(新增) - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(userId, getUserType().getValue(), reqVO)); - } - @Override public void logout(String token) { // 查询用户信息 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 f57baff0d..6d89897bb 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 b408a1fd4..723e507c4 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.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; +import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; +import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper; import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper; -import cn.iocoder.yudao.module.system.dal.redis.social.SocialAuthUserRedisDAO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; -import com.google.common.annotations.VisibleForTesting; import com.xkcoding.justauth.AuthRequestFactory; import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.model.AuthCallback; @@ -21,10 +21,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.util.Collections; import java.util.List; -import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -42,8 +43,7 @@ public class SocialUserServiceImpl implements SocialUserService { private AuthRequestFactory authRequestFactory; @Resource - private SocialAuthUserRedisDAO authSocialUserRedisDAO; - + private SocialUserBindMapper socialUserBindMapper; @Resource private SocialUserMapper socialUserMapper; @@ -57,171 +57,111 @@ public class SocialUserServiceImpl implements SocialUserService { } @Override - public AuthUser getAuthUser(Integer type, String code, String state) { - AuthCallback authCallback = buildAuthCallback(code, state); - // 从缓存中获取 - AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback); - if (authUser != null) { - return authUser; + public SocialUserDO authSocialUser(Integer type, String code, String state) { + // 优先从 DB 中获取,因为 code 有且可以使用一次。 + // 在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 + SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(type, code, state); + if (socialUser != null) { + return socialUser; } // 请求获取 - authUser = this.getAuthUser0(type, authCallback); - // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登录,此时需要 code 使用两次 - authSocialUserRedisDAO.set(type, authCallback, authUser); - return authUser; - } + AuthUser authUser = getAuthUser(type, code, state); + Assert.notNull(authUser, "三方用户不能为空"); - /** - * 获得 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.setType(type).setCode(code).setState(state) // 需要保存 code + state 字段,保证后续可查询 + .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken()))) + .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo())); + if (socialUser.getId() == null) { + socialUserMapper.insert(socialUser); + } else { + socialUserMapper.updateById(socialUser); + } + return socialUser; } @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(); + } + // 获得社交用户 + return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId)); } @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); + // 社交用户可能之前绑定过别的用户,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId()); + + // 用户可能之前已经绑定过该社交类型,需要进行解绑 + socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(), + socialUser.getType()); + + // 绑定当前登录的社交用户 + SocialUserBindDO socialUserBind = SocialUserBindDO.builder() + .userId(reqDTO.getUserId()).userType(reqDTO.getUserType()) + .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build(); + socialUserBindMapper.insert(socialUserBind); } - /** - * 绑定社交用户 - * @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.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType()); } @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.selectByUserTypeAndSocialUserId(userType, + socialUser.getId()); + 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(); } /** * 请求社交平台,获得授权的用户 * * @param type 社交平台的类型 - * @param authCallback 授权回调 + * @param code 授权码 + * @param state 授权 state * @return 授权的用户 */ - private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) { + private AuthUser getAuthUser(Integer type, String code, String state) { AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); + AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build(); AuthResponse authResponse = authRequest.login(authCallback); - log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback), - toJsonString(authResponse)); + log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type, + toJsonString(authCallback), toJsonString(authResponse)); if (!authResponse.ok()) { throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg()); } return (AuthUser) authResponse.getData(); } - private static AuthCallback buildAuthCallback(String code, String state) { - return AuthCallback.builder().code(code).state(state).build(); - } - } 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 7be8feae3..36d6b3b97 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,165 +1,256 @@ 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 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.enums.social.SocialTypeEnum; import com.xkcoding.justauth.AuthRequestFactory; +import me.zhyd.oauth.enums.AuthResponseStatus; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import javax.annotation.Resource; import java.util.List; -import static cn.hutool.core.util.RandomUtil.randomEle; +import static cn.hutool.core.util.RandomUtil.randomLong; import static cn.hutool.core.util.RandomUtil.randomString; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_AUTH_FAILURE; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; -@Import({SocialUserServiceImpl.class, SocialAuthUserRedisDAO.class}) +@Import(SocialUserServiceImpl.class) public class SocialUserServiceTest extends BaseDbAndRedisUnitTest { @Resource - private SocialUserServiceImpl socialService; + private SocialUserServiceImpl socialUserService; @Resource private SocialUserMapper socialUserMapper; + @Resource + private SocialUserBindMapper socialUserBindMapper; @MockBean private AuthRequestFactory authRequestFactory; - /** - * 情况一,创建 SocialUserDO 的情况 - */ @Test - public void testBindSocialUser_create() { - // mock 数据 - // 准备参数 - 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 = SocialTypeEnum.WECHAT_MP.getType(); + String redirectUri = "sss"; + // mock 获得对应的 AuthRequest 实现 + AuthRequest authRequest = mock(AuthRequest.class); + when(authRequestFactory.get(eq("WECHAT_MP"))).thenReturn(authRequest); + // mock 方法 + authStateUtilsMock.when(AuthStateUtils::createState).thenReturn("aoteman"); + when(authRequest.authorize(eq("aoteman"))).thenReturn("https://www.iocoder.cn?redirect_uri=yyy"); - // 调用 - 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); + public void testAuthSocialUser_exists() { // 准备参数 - Long userId = dbSocialUser.getUserId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); + Integer type = SocialTypeEnum.GITEE.getType(); + String code = "tudou"; + String state = "yuanma"; // mock 方法 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state); + socialUserMapper.insert(socialUser); // 调用 - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); + SocialUserDO result = socialUserService.authSocialUser(type, code, state); // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); - assertBindSocialUser(socialUsers.get(0), authUser, userId, type); + assertPojoEquals(socialUser, result); } - /** - * 情况一和二都存在的,逻辑二的场景 - */ @Test - public void testBindSocialUser_userId() { - // mock 数据 - SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(dbSocialUser); + public void testAuthSocialUser_authFailure() { // 准备参数 - Long userId = randomLongId(); - Integer type = dbSocialUser.getType(); - AuthUser authUser = randomPojo(AuthUser.class); + Integer type = SocialTypeEnum.GITEE.getType(); // mock 方法 + AuthRequest authRequest = mock(AuthRequest.class); + when(authRequestFactory.get(anyString())).thenReturn(authRequest); + AuthResponse authResponse = new AuthResponse<>(0, "模拟失败", null); + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse); + + // 调用并断言 + assertServiceException( + () -> socialUserService.authSocialUser(type, randomString(10), randomString(10)), + SOCIAL_USER_AUTH_FAILURE, "模拟失败"); + } + + @Test + public void testAuthSocialUser_insert() { + // 准备参数 + Integer type = SocialTypeEnum.GITEE.getType(); + String code = "tudou"; + String state = "yuanma"; + // mock 方法 + AuthRequest authRequest = mock(AuthRequest.class); + when(authRequestFactory.get(eq(SocialTypeEnum.GITEE.getSource()))).thenReturn(authRequest); + AuthUser authUser = randomPojo(AuthUser.class); + AuthResponse authResponse = new AuthResponse<>(AuthResponseStatus.SUCCESS.getCode(), null, authUser); + when(authRequest.login(any(AuthCallback.class))).thenReturn(authResponse); // 调用 - socialService.bindSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, authUser); + SocialUserDO result = socialUserService.authSocialUser(type, code, state); // 断言 - List socialUsers = socialUserMapper.selectList("user_id", userId); - assertEquals(1, socialUsers.size()); + assertBindSocialUser(type, result, authResponse.getData()); + assertEquals(code, result.getCode()); + assertEquals(state, result.getState()); } - private void assertBindSocialUser(SocialUserDO socialUser, AuthUser authUser, Long userId, - Integer type) { + @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(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(); + public void testGetSocialUserList() { + Long userId = 1L; + Integer userType = UserTypeEnum.ADMIN.getValue(); + // mock 获得社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType()); + socialUserMapper.insert(socialUser); // 可被查到 + socialUserMapper.insert(randomPojo(SocialUserDO.class)); // 不可被查到 + // mock 获得绑定 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到 + .setUserId(userId).setUserType(userType).setSocialType(SocialTypeEnum.GITEE.getType()) + .setSocialUserId(socialUser.getId())); + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到 + .setUserId(2L).setUserType(userType).setSocialType(SocialTypeEnum.DINGTALK.getType())); // 调用 - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); + List result = socialUserService.getSocialUserList(userId, userType); // 断言 - assertEquals(1L, socialUserMapper.selectCount(null).longValue()); + assertEquals(1, result.size()); + assertPojoEquals(socialUser, result.get(0)); } - - /** - * 情况二,如果新老的 unionId 不一致的,需解绑 - */ @Test - public void testUnbindOldSocialUser_yes() { - // mock 数据 - SocialUserDO oldSocialUser = randomPojo(SocialUserDO.class, socialUserDO -> { - socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue()); - socialUserDO.setType(randomEle(SocialTypeEnum.values()).getType()); - }); - socialUserMapper.insert(oldSocialUser); + public void testBindSocialUser() { // 准备参数 - Long userId = oldSocialUser.getUserId(); - Integer type = oldSocialUser.getType(); - String newUnionId = randomString(10); + SocialUserBindReqDTO reqDTO = new SocialUserBindReqDTO() + .setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue()) + .setType(SocialTypeEnum.GITEE.getType()).setCode("test_code").setState("test_state"); + // mock 数据:获得社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(reqDTO.getType()) + .setCode(reqDTO.getCode()).setState(reqDTO.getState()); + socialUserMapper.insert(socialUser); + // mock 数据:用户可能之前已经绑定过该社交类型 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue()) + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(-1L)); + // mock 数据:社交用户可能之前绑定过别的用户 + socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserType(UserTypeEnum.ADMIN.getValue()) + .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId())); // 调用 - socialService.unbindOldSocialUser(userId, UserTypeEnum.ADMIN.getValue(), type, newUnionId); + socialUserService.bindSocialUser(reqDTO); // 断言 - assertEquals(0L, socialUserMapper.selectCount(null).longValue()); + List socialUserBinds = socialUserBindMapper.selectList(); + assertEquals(1, socialUserBinds.size()); + } + + @Test + public void testUnbindSocialUser_success() { + // 准备参数 + Long userId = 1L; + Integer userType = UserTypeEnum.ADMIN.getValue(); + Integer type = SocialTypeEnum.GITEE.getType(); + String openid = "test_openid"; + // mock 数据:社交用户 + SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setOpenid(openid); + socialUserMapper.insert(socialUser); + // mock 数据:社交绑定关系 + SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType) + .setUserId(userId).setSocialType(type); + socialUserBindMapper.insert(socialUserBind); + + // 调用 + socialUserService.unbindSocialUser(userId, userType, type, openid); + // 断言 + assertEquals(0, socialUserBindMapper.selectCount(null).intValue()); + } + + @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/clean.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql index 6f0e9d384..1a3cdce8f 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 ae59b7ab0..709de08fa 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,15 @@ CREATE TABLE IF NOT EXISTS "system_error_code" ( CREATE TABLE IF NOT EXISTS "system_social_user" ( "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "user_id" bigint NOT NULL, - "user_type" tinyint NOT NULL DEFAULT '0', "type" tinyint NOT NULL, - "openid" varchar(32) NOT NULL, + "openid" varchar(64) NOT NULL, "token" varchar(256) DEFAULT NULL, - "union_id" varchar(32) NOT NULL, "raw_token_info" varchar(1024) NOT NULL, "nickname" varchar(32) NOT NULL, "avatar" varchar(255) DEFAULT NULL, "raw_user_info" varchar(1024) NOT NULL, + "code" varchar(64) NOT NULL, + "state" varchar(64), "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "updater" varchar(64) DEFAULT '', @@ -396,6 +395,20 @@ CREATE TABLE IF NOT EXISTS "system_social_user" ( PRIMARY KEY ("id") ) COMMENT '社交用户'; +CREATE TABLE IF NOT EXISTS "system_social_user_bind" ( + "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "user_type" tinyint NOT NULL, + "social_type" tinyint NOT NULL, + "social_user_id" number NOT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '社交用户的绑定'; + CREATE TABLE IF NOT EXISTS "system_tenant" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" varchar(63) NOT NULL, diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index a2f191b7b..256c5beb8 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 1b4b038df..0d5f777d0 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 740f49795..ffe83fb5f 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 48df3dd55..0939be757 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 000000000..d0877095d --- /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, openid) { + return request({ + url: '/system/social-user/unbind', + method: 'delete', + data: { + type, + openid + } + }) +} diff --git a/yudao-ui-admin/src/store/modules/user.js b/yudao-ui-admin/src/store/modules/user.js index 78da4ece4..d3cbf4886 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 f1e69a246..91950e03d 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 95ddb9862..3e05bbb84 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 670ee423d..2e41a4cd4 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 @@