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 fcae86c5e..078159724 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,5 @@ 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; @@ -11,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; /** * 社交自动装配类 @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration; public class YudaoSocialAutoConfiguration { @Bean + @Primary @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true) public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { // 需要修改 HttpUtil 使用的实现,避免类报错 diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java index 8f3cf5d51..b2cd28ec6 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.social.core; import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; -import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniProgramRequest; +import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest; import com.xkcoding.justauth.AuthRequestFactory; import com.xkcoding.justauth.autoconfigure.JustAuthProperties; import me.zhyd.oauth.cache.AuthStateCache; @@ -20,7 +20,6 @@ import java.lang.reflect.Method; * @author timfruit * @date 2021-10-31 */ -// TODO @timfruit:单测 public class YudaoAuthRequestFactory extends AuthRequestFactory { protected JustAuthProperties properties; @@ -69,15 +68,14 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory { if (config == null) { return null; } - // 配置 http config - ReflectUtil.invoke(this, configureHttpConfigMethod, - authExtendSource.name(), config, properties.getHttpConfig()); + // 反射调用,配置 http config + ReflectUtil.invoke(this, configureHttpConfigMethod, authExtendSource.name(), config, properties.getHttpConfig()); // 获得拓展的 Request // noinspection SwitchStatementWithTooFewBranches switch (authExtendSource) { - case WECHAT_MINI_PROGRAM: - return new AuthWeChatMiniProgramRequest(config, authStateCache); + case WECHAT_MINI_APP: + return new AuthWeChatMiniAppRequest(config, authStateCache); default: return null; } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java index 3cd62b540..f51c81e02 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java @@ -14,25 +14,25 @@ public enum AuthExtendSource implements AuthSource { /** * 微信小程序授权登录 */ - WECHAT_MINI_PROGRAM { + WECHAT_MINI_APP { @Override public String authorize() { - // https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html - throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code"); + // 参见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 文档 + throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 wx.login() 登录获取 code"); } @Override public String accessToken() { - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html - // 获取openid, unionid , session_key + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档 + // 获取 openid, unionId , session_key 等字段 return "https://api.weixin.qq.com/sns/jscode2session"; } @Override public String userInfo() { - //https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html - throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息"); + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档 + throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 wx.getUserProfile() 获取用户信息"); } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java deleted file mode 100644 index 2c0f4f403..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.framework.social.core.model; - -import lombok.*; -import me.zhyd.oauth.model.AuthToken; - -/** - * 授权所需的 token 拓展类 - * - * @author timfruit - * @date 2021-10-29 - */ -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class AuthExtendToken extends AuthToken { - - /** - * 微信小程序 - 会话密钥 - */ - private String miniSessionKey; - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java similarity index 50% rename from yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java rename to yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java index e5bbfcaad..5ff5b8578 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java @@ -1,100 +1,97 @@ -package cn.iocoder.yudao.framework.social.core.request; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; -import cn.iocoder.yudao.framework.social.core.model.AuthExtendToken; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import me.zhyd.oauth.cache.AuthStateCache; -import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.AuthCallback; -import me.zhyd.oauth.model.AuthToken; -import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.request.AuthDefaultRequest; -import me.zhyd.oauth.utils.HttpUtils; -import me.zhyd.oauth.utils.UrlBuilder; - -/** - * 微信小程序登陆 - * - * @author timfruit - * @date 2021-10-29 - */ -public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest { - - public AuthWeChatMiniProgramRequest(AuthConfig config) { - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM); - } - - public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) { - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache); - } - - @Override - protected AuthToken getAccessToken(AuthCallback authCallback) { - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html - String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())); - CodeSessionResponse accessTokenObject = JsonUtils.parseObject(response, CodeSessionResponse.class); - - this.checkResponse(accessTokenObject); - - AuthExtendToken token = new AuthExtendToken(); - token.setMiniSessionKey(accessTokenObject.sessionKey); - token.setOpenId(accessTokenObject.openid); - token.setUnionId(accessTokenObject.unionid); - return token; - } - - @Override - protected AuthUser getUserInfo(AuthToken authToken) { - // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html - // 如果需要用户信息,需要在小程序调用函数后传给后端 - return AuthUser.builder() - .uuid(authToken.getOpenId()) - //TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来 - .nickname("") - .avatar("") - .token(authToken) - .source(source.toString()) - .build(); - } - - /** - * 检查响应内容是否正确 - * - * @param object 请求响应内容 - */ - private void checkResponse(CodeSessionResponse object) { - if (object.errcode != 0) { - throw new AuthException(object.errcode, object.errmsg); - } - } - - /** - * 返回获取 accessToken 的 url - * - * @param code 授权码 - * @return 返回获取 accessToken 的 url - */ - @Override - protected String accessTokenUrl(String code) { - return UrlBuilder.fromBaseUrl(source.accessToken()) - .queryParam("appid", config.getClientId()) - .queryParam("secret", config.getClientSecret()) - .queryParam("js_code", code) - .queryParam("grant_type", "authorization_code") - .build(); - } - - @Data - private static class CodeSessionResponse { - private int errcode; - private String errmsg; - @JsonProperty("session_key") - private String sessionKey; - private String openid; - private String unionid; - } - -} +package cn.iocoder.yudao.framework.social.core.request; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthDefaultRequest; +import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 微信小程序登陆 Request 请求 + * + * 由于 JustAuth 定位是面向 Web 为主的三方登录,所以微信小程序只能自己封装 + * + * @author timfruit + * @date 2021-10-29 + */ +public class AuthWeChatMiniAppRequest extends AuthDefaultRequest { + + public AuthWeChatMiniAppRequest(AuthConfig config, AuthStateCache authStateCache) { + super(config, AuthExtendSource.WECHAT_MINI_APP, authStateCache); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档 + // 使用 code 获取对应的 openId、unionId 等字段 + String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())); + JSCode2SessionResponse accessTokenObject = JsonUtils.parseObject(response, JSCode2SessionResponse.class); + assert accessTokenObject != null; + checkResponse(accessTokenObject); + // 拼装结果 + return AuthToken.builder() + .openId(accessTokenObject.getOpenid()) + .unionId(accessTokenObject.getUnionId()) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档 + // 如果需要用户信息,需要在小程序调用函数后传给后端 + return AuthUser.builder() + .username("") + .nickname("") + .avatar("") + .uuid(authToken.getOpenId()) + .token(authToken) + .source(source.toString()) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param response 请求响应内容 + */ + private void checkResponse(JSCode2SessionResponse response) { + if (response.getErrorCode() != 0) { + throw new AuthException(response.getErrorCode(), response.getErrorMsg()); + } + } + + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("appid", config.getClientId()) + .queryParam("secret", config.getClientSecret()) + .queryParam("js_code", code) // 和父类不同,所以需要重写该方法 + .queryParam("grant_type", "authorization_code") + .build(); + } + + @Data + @SuppressWarnings("SpellCheckingInspection") + private static class JSCode2SessionResponse { + + @JsonProperty("errcode") + private int errorCode; + @JsonProperty("errmsg") + private String errorMsg; + @JsonProperty("session_key") + private String sessionKey; + private String openid; + @JsonProperty("unionid") + private String unionId; + + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml index 763c722c4..4c53e2900 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml @@ -33,9 +33,13 @@ com.github.binarywang - wx-java-mp-spring-boot-starter - 4.1.9.B + 4.3.4.B + + + com.github.binarywang + wx-java-miniapp-spring-boot-starter + 4.3.4.B diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java index 2529ae4fd..2fbb61c8b 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java @@ -13,12 +13,12 @@ public interface ErrorCodeConstants { ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在"); ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败"); - // ========== AUTH 模块 1004003000 ========== ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用"); ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定"); + ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1004003006, "获得手机号失败"); // ========== 用户收件地址 1004004000 ========== ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, "用户收件地址不存在"); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http index b1704f55d..51252530b 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http @@ -28,6 +28,17 @@ tenant-id: {{appTenentId}} "code": 9999 } +### 请求 /weixin-mini-app-login 接口 => 成功 +POST {{appApi}}/member/auth/weixin-mini-app-login +Content-Type: application/json +tenant-id: {{appTenentId}} + +{ + "phoneCode": "618e6412e0c728f5b8fc7164497463d0158a923c9e7fd86af8bba393b9decbc5", + "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR" +} + + ### 请求 /logout 接口 => 成功 POST {{appApi}}/member/auth/logout Content-Type: application/json 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 8a7091c02..e42554aa8 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 @@ -110,4 +110,10 @@ public class AppAuthController { return success(authService.socialLogin(reqVO)); } + @PostMapping("/weixin-mini-app-login") + @ApiOperation("微信小程序的一键登录") + public CommonResult weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) { + return success(authService.weixinMiniAppLogin(reqVO)); + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java new file mode 100644 index 000000000..bd34085b2 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.member.controller.app.auth.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@ApiModel("用户 APP - 微信小程序手机登录 Request VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthWeixinMiniAppLoginReqVO { + + @ApiModelProperty(value = "手机 code", required = true, example = "hello", notes = "小程序通过 wx.getPhoneNumber 方法获得") + @NotEmpty(message = "手机 code 不能为空") + private String phoneCode; + + @ApiModelProperty(value = "登录 code", required = true, example = "word", notes = "小程序通过 wx.login 方法获得") + @NotEmpty(message = "登录 code 不能为空") + private String loginCode; + +} 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 de8132de0..6990fba2e 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 @@ -44,6 +44,14 @@ public interface MemberAuthService { */ AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO); + /** + * 微信小程序的一键登录 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO); + /** * 获得社交认证 URL * @@ -81,4 +89,5 @@ public interface MemberAuthService { * @return 登录结果 */ AppAuthLoginRespVO refreshToken(String refreshToken); + } 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 dce13f28f..44b33fa05 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 @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.member.service.auth; +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -23,6 +25,7 @@ import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -56,6 +59,9 @@ public class MemberAuthServiceImpl implements MemberAuthService { @Resource private OAuth2TokenApi oauth2TokenApi; + @Resource + private WxMaService wxMaService; + @Resource private PasswordEncoder passwordEncoder; @Resource @@ -116,12 +122,34 @@ public class MemberAuthServiceImpl implements MemberAuthService { return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL); } + @Override + public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) { + // 获得对应的手机号信息 + WxMaPhoneNumberInfo phoneNumberInfo; + try { + phoneNumberInfo = wxMaService.getUserService().getNewPhoneNoInfo(reqVO.getPhoneCode()); + } catch (Exception exception) { + throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR); + } + // 获得获得注册用户 + MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP()); + Assert.notNull(user, "获取用户失败,结果为空"); + + // 绑定社交用户 + socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), "")); + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL); + } + private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) { // 插入登陆日志 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS); // 创建 Token 令牌 OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO() - .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)); + .setUserId(user.getId()).setUserType(getUserType().getValue()) + .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)); // 构建返回结果 return AuthConvert.INSTANCE.convert(accessTokenRespDTO); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java index a3bb6b315..c591df2c3 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialUserBindReqDTO.java @@ -46,7 +46,7 @@ public class SocialUserBindReqDTO { /** * state */ - @NotEmpty(message = "state 不能为空") + @NotNull(message = "state 不能为空") private String state; } 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 77833b2e6..197bb2943 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 @@ -6,9 +6,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; /** * 社交平台的类型枚举 @@ -49,7 +46,7 @@ public enum SocialTypeEnum implements IntArrayValuable { * 微信小程序 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html */ - WECHAT_MINI_PROGRAM(33, "WECHAT_MINI_PROGRAM"), + WECHAT_MINI_APP(34, "WECHAT_MINI_APP"), ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); 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 723e507c4..411d749b0 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 @@ -3,13 +3,13 @@ package cn.iocoder.yudao.module.system.service.social; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; 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 lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; @@ -39,8 +39,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @Slf4j public class SocialUserServiceImpl implements SocialUserService { - @Resource - private AuthRequestFactory authRequestFactory; + @Resource// 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它 + private YudaoAuthRequestFactory yudaoAuthRequestFactory; @Resource private SocialUserBindMapper socialUserBindMapper; @@ -50,7 +50,7 @@ public class SocialUserServiceImpl implements SocialUserService { @Override public String getAuthorizeUrl(Integer type, String redirectUri) { // 获得对应的 AuthRequest 实现 - AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); + AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); // 生成跳转地址 String authorizeUri = authRequest.authorize(AuthStateUtils.createState()); return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri); @@ -153,7 +153,7 @@ public class SocialUserServiceImpl implements SocialUserService { * @return 授权的用户 */ private AuthUser getAuthUser(Integer type, String code, String state) { - AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); + AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build(); AuthResponse authResponse = authRequest.login(authCallback); log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type, diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index beed068e5..388b5676f 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -173,10 +173,9 @@ logging: cn.iocoder.yudao.module.tool.dal.mysql: debug cn.iocoder.yudao.module.member.dal.mysql: debug ---- #################### 微信公众号相关配置 #################### -wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 - mp: - # 公众号配置(必填) +--- #################### 微信公众号、小程序相关配置 #################### +wx: + mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 app-id: wx041349c6f39b268b secret: 5abee519483bc9f8cb37ce280e814bd0 # 存储配置,解决 AccessToken 的跨节点的共享 @@ -184,6 +183,13 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 + miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 + appid: wx63c280fe3248a3e7 + secret: 6f270509224a7ae1296bbf1c8cb97aed + config-storage: + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 + key-prefix: wa # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 --- #################### 芋道相关配置 #################### @@ -218,6 +224,12 @@ justauth: client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw agent-id: 1000004 ignore-check-redirect-uri: true + WECHAT_MINI_APP: # 微信小程序 + client-id: ${wx.miniapp.appid} + client-secret: ${wx.miniapp.secret} + ignore-check-redirect-uri: true + ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验 + cache: type: REDIS prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::