Commit e1c08c4661063c3b46c724a2dd8a0ca5103886df

Authored by YunaiV
1 parent 1b7093f5

新增 member/auth/weixin-mini-app-login 接口,实现小程序的手机登录

Showing 16 changed files with 223 additions and 156 deletions
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java
1 package cn.iocoder.yudao.framework.social.config; 1 package cn.iocoder.yudao.framework.social.config;
2 2
3 -import cn.hutool.core.util.ReflectUtil;  
4 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; 3 import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
5 import com.xkcoding.http.HttpUtil; 4 import com.xkcoding.http.HttpUtil;
6 import com.xkcoding.http.support.hutool.HutoolImpl; 5 import com.xkcoding.http.support.hutool.HutoolImpl;
@@ -11,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -11,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11 import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 import org.springframework.boot.context.properties.EnableConfigurationProperties;
12 import org.springframework.context.annotation.Bean; 11 import org.springframework.context.annotation.Bean;
13 import org.springframework.context.annotation.Configuration; 12 import org.springframework.context.annotation.Configuration;
  13 +import org.springframework.context.annotation.Primary;
14 14
15 /** 15 /**
16 * 社交自动装配类 16 * 社交自动装配类
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration; @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration;
24 public class YudaoSocialAutoConfiguration { 24 public class YudaoSocialAutoConfiguration {
25 25
26 @Bean 26 @Bean
  27 + @Primary
27 @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true) 28 @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
28 public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { 29 public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
29 // 需要修改 HttpUtil 使用的实现,避免类报错 30 // 需要修改 HttpUtil 使用的实现,避免类报错
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; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.social.core;
3 import cn.hutool.core.util.EnumUtil; 3 import cn.hutool.core.util.EnumUtil;
4 import cn.hutool.core.util.ReflectUtil; 4 import cn.hutool.core.util.ReflectUtil;
5 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; 5 import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
6 -import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniProgramRequest; 6 +import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest;
7 import com.xkcoding.justauth.AuthRequestFactory; 7 import com.xkcoding.justauth.AuthRequestFactory;
8 import com.xkcoding.justauth.autoconfigure.JustAuthProperties; 8 import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
9 import me.zhyd.oauth.cache.AuthStateCache; 9 import me.zhyd.oauth.cache.AuthStateCache;
@@ -20,7 +20,6 @@ import java.lang.reflect.Method; @@ -20,7 +20,6 @@ import java.lang.reflect.Method;
20 * @author timfruit 20 * @author timfruit
21 * @date 2021-10-31 21 * @date 2021-10-31
22 */ 22 */
23 -// TODO @timfruit:单测  
24 public class YudaoAuthRequestFactory extends AuthRequestFactory { 23 public class YudaoAuthRequestFactory extends AuthRequestFactory {
25 24
26 protected JustAuthProperties properties; 25 protected JustAuthProperties properties;
@@ -69,15 +68,14 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory { @@ -69,15 +68,14 @@ public class YudaoAuthRequestFactory extends AuthRequestFactory {
69 if (config == null) { 68 if (config == null) {
70 return null; 69 return null;
71 } 70 }
72 - // 配置 http config  
73 - ReflectUtil.invoke(this, configureHttpConfigMethod,  
74 - authExtendSource.name(), config, properties.getHttpConfig()); 71 + // 反射调用,配置 http config
  72 + ReflectUtil.invoke(this, configureHttpConfigMethod, authExtendSource.name(), config, properties.getHttpConfig());
75 73
76 // 获得拓展的 Request 74 // 获得拓展的 Request
77 // noinspection SwitchStatementWithTooFewBranches 75 // noinspection SwitchStatementWithTooFewBranches
78 switch (authExtendSource) { 76 switch (authExtendSource) {
79 - case WECHAT_MINI_PROGRAM:  
80 - return new AuthWeChatMiniProgramRequest(config, authStateCache); 77 + case WECHAT_MINI_APP:
  78 + return new AuthWeChatMiniAppRequest(config, authStateCache);
81 default: 79 default:
82 return null; 80 return null;
83 } 81 }
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 { @@ -14,25 +14,25 @@ public enum AuthExtendSource implements AuthSource {
14 /** 14 /**
15 * 微信小程序授权登录 15 * 微信小程序授权登录
16 */ 16 */
17 - WECHAT_MINI_PROGRAM { 17 + WECHAT_MINI_APP {
18 18
19 @Override 19 @Override
20 public String authorize() { 20 public String authorize() {
21 - // https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html  
22 - throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code"); 21 + // 参见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 文档
  22 + throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 wx.login() 登录获取 code");
23 } 23 }
24 24
25 @Override 25 @Override
26 public String accessToken() { 26 public String accessToken() {
27 - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html  
28 - // 获取openid, unionid , session_key 27 + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
  28 + // 获取 openid, unionId , session_key 等字段
29 return "https://api.weixin.qq.com/sns/jscode2session"; 29 return "https://api.weixin.qq.com/sns/jscode2session";
30 } 30 }
31 31
32 @Override 32 @Override
33 public String userInfo() { 33 public String userInfo() {
34 - //https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html  
35 - throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息"); 34 + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
  35 + throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 wx.getUserProfile() 获取用户信息");
36 } 36 }
37 } 37 }
38 38
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/model/AuthExtendToken.java deleted
1 -package cn.iocoder.yudao.framework.social.core.model;  
2 -  
3 -import lombok.*;  
4 -import me.zhyd.oauth.model.AuthToken;  
5 -  
6 -/**  
7 - * 授权所需的 token 拓展类  
8 - *  
9 - * @author timfruit  
10 - * @date 2021-10-29  
11 - */  
12 -@Getter  
13 -@Setter  
14 -@NoArgsConstructor  
15 -@AllArgsConstructor  
16 -public class AuthExtendToken extends AuthToken {  
17 -  
18 - /**  
19 - * 微信小程序 - 会话密钥  
20 - */  
21 - private String miniSessionKey;  
22 -  
23 -}  
yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniProgramRequest.java renamed to yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java
1 -package cn.iocoder.yudao.framework.social.core.request;  
2 -  
3 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
4 -import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;  
5 -import cn.iocoder.yudao.framework.social.core.model.AuthExtendToken;  
6 -import com.fasterxml.jackson.annotation.JsonProperty;  
7 -import lombok.Data;  
8 -import me.zhyd.oauth.cache.AuthStateCache;  
9 -import me.zhyd.oauth.config.AuthConfig;  
10 -import me.zhyd.oauth.exception.AuthException;  
11 -import me.zhyd.oauth.model.AuthCallback;  
12 -import me.zhyd.oauth.model.AuthToken;  
13 -import me.zhyd.oauth.model.AuthUser;  
14 -import me.zhyd.oauth.request.AuthDefaultRequest;  
15 -import me.zhyd.oauth.utils.HttpUtils;  
16 -import me.zhyd.oauth.utils.UrlBuilder;  
17 -  
18 -/**  
19 - * 微信小程序登陆  
20 - *  
21 - * @author timfruit  
22 - * @date 2021-10-29  
23 - */  
24 -public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest {  
25 -  
26 - public AuthWeChatMiniProgramRequest(AuthConfig config) {  
27 - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM);  
28 - }  
29 -  
30 - public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) {  
31 - super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache);  
32 - }  
33 -  
34 - @Override  
35 - protected AuthToken getAccessToken(AuthCallback authCallback) {  
36 - // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html  
37 - String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode()));  
38 - CodeSessionResponse accessTokenObject = JsonUtils.parseObject(response, CodeSessionResponse.class);  
39 -  
40 - this.checkResponse(accessTokenObject);  
41 -  
42 - AuthExtendToken token = new AuthExtendToken();  
43 - token.setMiniSessionKey(accessTokenObject.sessionKey);  
44 - token.setOpenId(accessTokenObject.openid);  
45 - token.setUnionId(accessTokenObject.unionid);  
46 - return token;  
47 - }  
48 -  
49 - @Override  
50 - protected AuthUser getUserInfo(AuthToken authToken) {  
51 - // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html  
52 - // 如果需要用户信息,需要在小程序调用函数后传给后端  
53 - return AuthUser.builder()  
54 - .uuid(authToken.getOpenId())  
55 - //TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来  
56 - .nickname("")  
57 - .avatar("")  
58 - .token(authToken)  
59 - .source(source.toString())  
60 - .build();  
61 - }  
62 -  
63 - /**  
64 - * 检查响应内容是否正确  
65 - *  
66 - * @param object 请求响应内容  
67 - */  
68 - private void checkResponse(CodeSessionResponse object) {  
69 - if (object.errcode != 0) {  
70 - throw new AuthException(object.errcode, object.errmsg);  
71 - }  
72 - }  
73 -  
74 - /**  
75 - * 返回获取 accessToken 的 url  
76 - *  
77 - * @param code 授权码  
78 - * @return 返回获取 accessToken 的 url  
79 - */  
80 - @Override  
81 - protected String accessTokenUrl(String code) {  
82 - return UrlBuilder.fromBaseUrl(source.accessToken())  
83 - .queryParam("appid", config.getClientId())  
84 - .queryParam("secret", config.getClientSecret())  
85 - .queryParam("js_code", code)  
86 - .queryParam("grant_type", "authorization_code")  
87 - .build();  
88 - }  
89 -  
90 - @Data  
91 - private static class CodeSessionResponse {  
92 - private int errcode;  
93 - private String errmsg;  
94 - @JsonProperty("session_key")  
95 - private String sessionKey;  
96 - private String openid;  
97 - private String unionid;  
98 - }  
99 -  
100 -} 1 +package cn.iocoder.yudao.framework.social.core.request;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource;
  5 +import com.fasterxml.jackson.annotation.JsonProperty;
  6 +import lombok.Data;
  7 +import me.zhyd.oauth.cache.AuthStateCache;
  8 +import me.zhyd.oauth.config.AuthConfig;
  9 +import me.zhyd.oauth.exception.AuthException;
  10 +import me.zhyd.oauth.model.AuthCallback;
  11 +import me.zhyd.oauth.model.AuthToken;
  12 +import me.zhyd.oauth.model.AuthUser;
  13 +import me.zhyd.oauth.request.AuthDefaultRequest;
  14 +import me.zhyd.oauth.utils.HttpUtils;
  15 +import me.zhyd.oauth.utils.UrlBuilder;
  16 +
  17 +/**
  18 + * 微信小程序登陆 Request 请求
  19 + *
  20 + * 由于 JustAuth 定位是面向 Web 为主的三方登录,所以微信小程序只能自己封装
  21 + *
  22 + * @author timfruit
  23 + * @date 2021-10-29
  24 + */
  25 +public class AuthWeChatMiniAppRequest extends AuthDefaultRequest {
  26 +
  27 + public AuthWeChatMiniAppRequest(AuthConfig config, AuthStateCache authStateCache) {
  28 + super(config, AuthExtendSource.WECHAT_MINI_APP, authStateCache);
  29 + }
  30 +
  31 + @Override
  32 + protected AuthToken getAccessToken(AuthCallback authCallback) {
  33 + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档
  34 + // 使用 code 获取对应的 openId、unionId 等字段
  35 + String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode()));
  36 + JSCode2SessionResponse accessTokenObject = JsonUtils.parseObject(response, JSCode2SessionResponse.class);
  37 + assert accessTokenObject != null;
  38 + checkResponse(accessTokenObject);
  39 + // 拼装结果
  40 + return AuthToken.builder()
  41 + .openId(accessTokenObject.getOpenid())
  42 + .unionId(accessTokenObject.getUnionId())
  43 + .build();
  44 + }
  45 +
  46 + @Override
  47 + protected AuthUser getUserInfo(AuthToken authToken) {
  48 + // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档
  49 + // 如果需要用户信息,需要在小程序调用函数后传给后端
  50 + return AuthUser.builder()
  51 + .username("")
  52 + .nickname("")
  53 + .avatar("")
  54 + .uuid(authToken.getOpenId())
  55 + .token(authToken)
  56 + .source(source.toString())
  57 + .build();
  58 + }
  59 +
  60 + /**
  61 + * 检查响应内容是否正确
  62 + *
  63 + * @param response 请求响应内容
  64 + */
  65 + private void checkResponse(JSCode2SessionResponse response) {
  66 + if (response.getErrorCode() != 0) {
  67 + throw new AuthException(response.getErrorCode(), response.getErrorMsg());
  68 + }
  69 + }
  70 +
  71 + @Override
  72 + protected String accessTokenUrl(String code) {
  73 + return UrlBuilder.fromBaseUrl(source.accessToken())
  74 + .queryParam("appid", config.getClientId())
  75 + .queryParam("secret", config.getClientSecret())
  76 + .queryParam("js_code", code) // 和父类不同,所以需要重写该方法
  77 + .queryParam("grant_type", "authorization_code")
  78 + .build();
  79 + }
  80 +
  81 + @Data
  82 + @SuppressWarnings("SpellCheckingInspection")
  83 + private static class JSCode2SessionResponse {
  84 +
  85 + @JsonProperty("errcode")
  86 + private int errorCode;
  87 + @JsonProperty("errmsg")
  88 + private String errorMsg;
  89 + @JsonProperty("session_key")
  90 + private String sessionKey;
  91 + private String openid;
  92 + @JsonProperty("unionid")
  93 + private String unionId;
  94 +
  95 + }
  96 +
  97 +}
yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml
@@ -33,9 +33,13 @@ @@ -33,9 +33,13 @@
33 <!-- 三方云服务相关 --> 33 <!-- 三方云服务相关 -->
34 <dependency> 34 <dependency>
35 <groupId>com.github.binarywang</groupId> 35 <groupId>com.github.binarywang</groupId>
36 -<!-- <artifactId>weixin-java-mp</artifactId>-->  
37 <artifactId>wx-java-mp-spring-boot-starter</artifactId> 36 <artifactId>wx-java-mp-spring-boot-starter</artifactId>
38 - <version>4.1.9.B</version> 37 + <version>4.3.4.B</version>
  38 + </dependency>
  39 + <dependency>
  40 + <groupId>com.github.binarywang</groupId>
  41 + <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
  42 + <version>4.3.4.B</version>
39 </dependency> 43 </dependency>
40 <!-- TODO 芋艿:清理 --> 44 <!-- TODO 芋艿:清理 -->
41 </dependencies> 45 </dependencies>
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 { @@ -13,12 +13,12 @@ public interface ErrorCodeConstants {
13 ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在"); 13 ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
14 ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败"); 14 ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败");
15 15
16 -  
17 // ========== AUTH 模块 1004003000 ========== 16 // ========== AUTH 模块 1004003000 ==========
18 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); 17 ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");
19 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用"); 18 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, "登录失败,账号被禁用");
20 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期"); 19 ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, "Token 已经过期");
21 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定"); 20 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, "未绑定账号,需要进行绑定");
  21 + ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1004003006, "获得手机号失败");
22 22
23 // ========== 用户收件地址 1004004000 ========== 23 // ========== 用户收件地址 1004004000 ==========
24 ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, "用户收件地址不存在"); 24 ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, "用户收件地址不存在");
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}} @@ -28,6 +28,17 @@ tenant-id: {{appTenentId}}
28 "code": 9999 28 "code": 9999
29 } 29 }
30 30
  31 +### 请求 /weixin-mini-app-login 接口 => 成功
  32 +POST {{appApi}}/member/auth/weixin-mini-app-login
  33 +Content-Type: application/json
  34 +tenant-id: {{appTenentId}}
  35 +
  36 +{
  37 + "phoneCode": "618e6412e0c728f5b8fc7164497463d0158a923c9e7fd86af8bba393b9decbc5",
  38 + "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR"
  39 +}
  40 +
  41 +
31 ### 请求 /logout 接口 => 成功 42 ### 请求 /logout 接口 => 成功
32 POST {{appApi}}/member/auth/logout 43 POST {{appApi}}/member/auth/logout
33 Content-Type: application/json 44 Content-Type: application/json
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 { @@ -110,4 +110,10 @@ public class AppAuthController {
110 return success(authService.socialLogin(reqVO)); 110 return success(authService.socialLogin(reqVO));
111 } 111 }
112 112
  113 + @PostMapping("/weixin-mini-app-login")
  114 + @ApiOperation("微信小程序的一键登录")
  115 + public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) {
  116 + return success(authService.weixinMiniAppLogin(reqVO));
  117 + }
  118 +
113 } 119 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.auth.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Builder;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import javax.validation.constraints.NotEmpty;
  11 +
  12 +@ApiModel("用户 APP - 微信小程序手机登录 Request VO")
  13 +@Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +@Builder
  17 +public class AppAuthWeixinMiniAppLoginReqVO {
  18 +
  19 + @ApiModelProperty(value = "手机 code", required = true, example = "hello", notes = "小程序通过 wx.getPhoneNumber 方法获得")
  20 + @NotEmpty(message = "手机 code 不能为空")
  21 + private String phoneCode;
  22 +
  23 + @ApiModelProperty(value = "登录 code", required = true, example = "word", notes = "小程序通过 wx.login 方法获得")
  24 + @NotEmpty(message = "登录 code 不能为空")
  25 + private String loginCode;
  26 +
  27 +}
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
@@ -45,6 +45,14 @@ public interface MemberAuthService { @@ -45,6 +45,14 @@ public interface MemberAuthService {
45 AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO); 45 AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO);
46 46
47 /** 47 /**
  48 + * 微信小程序的一键登录
  49 + *
  50 + * @param reqVO 登录信息
  51 + * @return 登录结果
  52 + */
  53 + AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO);
  54 +
  55 + /**
48 * 获得社交认证 URL 56 * 获得社交认证 URL
49 * 57 *
50 * @param type 社交平台类型 58 * @param type 社交平台类型
@@ -81,4 +89,5 @@ public interface MemberAuthService { @@ -81,4 +89,5 @@ public interface MemberAuthService {
81 * @return 登录结果 89 * @return 登录结果
82 */ 90 */
83 AppAuthLoginRespVO refreshToken(String refreshToken); 91 AppAuthLoginRespVO refreshToken(String refreshToken);
  92 +
84 } 93 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
1 package cn.iocoder.yudao.module.member.service.auth; 1 package cn.iocoder.yudao.module.member.service.auth;
2 2
  3 +import cn.binarywang.wx.miniapp.api.WxMaService;
  4 +import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
3 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -23,6 +25,7 @@ import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants; @@ -23,6 +25,7 @@ import cn.iocoder.yudao.module.system.enums.auth.OAuth2ClientConstants;
23 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 25 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
24 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 26 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
25 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 27 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
  28 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
26 import com.google.common.annotations.VisibleForTesting; 29 import com.google.common.annotations.VisibleForTesting;
27 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
28 import org.springframework.security.crypto.password.PasswordEncoder; 31 import org.springframework.security.crypto.password.PasswordEncoder;
@@ -57,6 +60,9 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -57,6 +60,9 @@ public class MemberAuthServiceImpl implements MemberAuthService {
57 private OAuth2TokenApi oauth2TokenApi; 60 private OAuth2TokenApi oauth2TokenApi;
58 61
59 @Resource 62 @Resource
  63 + private WxMaService wxMaService;
  64 +
  65 + @Resource
60 private PasswordEncoder passwordEncoder; 66 private PasswordEncoder passwordEncoder;
61 @Resource 67 @Resource
62 private MemberUserMapper userMapper; 68 private MemberUserMapper userMapper;
@@ -116,12 +122,34 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -116,12 +122,34 @@ public class MemberAuthServiceImpl implements MemberAuthService {
116 return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL); 122 return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
117 } 123 }
118 124
  125 + @Override
  126 + public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) {
  127 + // 获得对应的手机号信息
  128 + WxMaPhoneNumberInfo phoneNumberInfo;
  129 + try {
  130 + phoneNumberInfo = wxMaService.getUserService().getNewPhoneNoInfo(reqVO.getPhoneCode());
  131 + } catch (Exception exception) {
  132 + throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR);
  133 + }
  134 + // 获得获得注册用户
  135 + MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP());
  136 + Assert.notNull(user, "获取用户失败,结果为空");
  137 +
  138 + // 绑定社交用户
  139 + socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
  140 + SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), ""));
  141 +
  142 + // 创建 Token 令牌,记录登录日志
  143 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
  144 + }
  145 +
119 private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) { 146 private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
120 // 插入登陆日志 147 // 插入登陆日志
121 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS); 148 createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
122 // 创建 Token 令牌 149 // 创建 Token 令牌
123 OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO() 150 OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
124 - .setUserId(user.getId()).setUserType(getUserType().getValue()).setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)); 151 + .setUserId(user.getId()).setUserType(getUserType().getValue())
  152 + .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
125 // 构建返回结果 153 // 构建返回结果
126 return AuthConvert.INSTANCE.convert(accessTokenRespDTO); 154 return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
127 } 155 }
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 { @@ -46,7 +46,7 @@ public class SocialUserBindReqDTO {
46 /** 46 /**
47 * state 47 * state
48 */ 48 */
49 - @NotEmpty(message = "state 不能为空") 49 + @NotNull(message = "state 不能为空")
50 private String state; 50 private String state;
51 51
52 } 52 }
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; @@ -6,9 +6,6 @@ import lombok.AllArgsConstructor;
6 import lombok.Getter; 6 import lombok.Getter;
7 7
8 import java.util.Arrays; 8 import java.util.Arrays;
9 -import java.util.Collection;  
10 -import java.util.Set;  
11 -import java.util.stream.Collectors;  
12 9
13 /** 10 /**
14 * 社交平台的类型枚举 11 * 社交平台的类型枚举
@@ -49,7 +46,7 @@ public enum SocialTypeEnum implements IntArrayValuable { @@ -49,7 +46,7 @@ public enum SocialTypeEnum implements IntArrayValuable {
49 * 微信小程序 46 * 微信小程序
50 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 47 * 文档链接:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
51 */ 48 */
52 - WECHAT_MINI_PROGRAM(33, "WECHAT_MINI_PROGRAM"), 49 + WECHAT_MINI_APP(34, "WECHAT_MINI_APP"),
53 ; 50 ;
54 51
55 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray(); 52 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray();
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; @@ -3,13 +3,13 @@ package cn.iocoder.yudao.module.system.service.social;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils; 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  6 +import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
6 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 7 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
7 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; 8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; 9 import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
9 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper; 10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
10 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper; 11 import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserMapper;
11 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; 12 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
12 -import com.xkcoding.justauth.AuthRequestFactory;  
13 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
14 import me.zhyd.oauth.model.AuthCallback; 14 import me.zhyd.oauth.model.AuthCallback;
15 import me.zhyd.oauth.model.AuthResponse; 15 import me.zhyd.oauth.model.AuthResponse;
@@ -39,8 +39,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -39,8 +39,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
39 @Slf4j 39 @Slf4j
40 public class SocialUserServiceImpl implements SocialUserService { 40 public class SocialUserServiceImpl implements SocialUserService {
41 41
42 - @Resource  
43 - private AuthRequestFactory authRequestFactory; 42 + @Resource// 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
  43 + private YudaoAuthRequestFactory yudaoAuthRequestFactory;
44 44
45 @Resource 45 @Resource
46 private SocialUserBindMapper socialUserBindMapper; 46 private SocialUserBindMapper socialUserBindMapper;
@@ -50,7 +50,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -50,7 +50,7 @@ public class SocialUserServiceImpl implements SocialUserService {
50 @Override 50 @Override
51 public String getAuthorizeUrl(Integer type, String redirectUri) { 51 public String getAuthorizeUrl(Integer type, String redirectUri) {
52 // 获得对应的 AuthRequest 实现 52 // 获得对应的 AuthRequest 实现
53 - AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); 53 + AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
54 // 生成跳转地址 54 // 生成跳转地址
55 String authorizeUri = authRequest.authorize(AuthStateUtils.createState()); 55 String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
56 return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri); 56 return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
@@ -153,7 +153,7 @@ public class SocialUserServiceImpl implements SocialUserService { @@ -153,7 +153,7 @@ public class SocialUserServiceImpl implements SocialUserService {
153 * @return 授权的用户 153 * @return 授权的用户
154 */ 154 */
155 private AuthUser getAuthUser(Integer type, String code, String state) { 155 private AuthUser getAuthUser(Integer type, String code, String state) {
156 - AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource()); 156 + AuthRequest authRequest = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(type).getSource());
157 AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build(); 157 AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();
158 AuthResponse<?> authResponse = authRequest.login(authCallback); 158 AuthResponse<?> authResponse = authRequest.login(authCallback);
159 log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type, 159 log.info("[getAuthUser][请求社交平台 type({}) request({}) response({})]", type,
yudao-server/src/main/resources/application-local.yaml
@@ -173,10 +173,9 @@ logging: @@ -173,10 +173,9 @@ logging:
173 cn.iocoder.yudao.module.tool.dal.mysql: debug 173 cn.iocoder.yudao.module.tool.dal.mysql: debug
174 cn.iocoder.yudao.module.member.dal.mysql: debug 174 cn.iocoder.yudao.module.member.dal.mysql: debug
175 175
176 ---- #################### 微信公众号相关配置 ####################  
177 -wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档  
178 - mp:  
179 - # 公众号配置(必填) 176 +--- #################### 微信公众号、小程序相关配置 ####################
  177 +wx:
  178 + mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
180 app-id: wx041349c6f39b268b 179 app-id: wx041349c6f39b268b
181 secret: 5abee519483bc9f8cb37ce280e814bd0 180 secret: 5abee519483bc9f8cb37ce280e814bd0
182 # 存储配置,解决 AccessToken 的跨节点的共享 181 # 存储配置,解决 AccessToken 的跨节点的共享
@@ -184,6 +183,13 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta @@ -184,6 +183,13 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
184 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 183 type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
185 key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置 184 key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
186 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 185 http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
  186 + miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
  187 + appid: wx63c280fe3248a3e7
  188 + secret: 6f270509224a7ae1296bbf1c8cb97aed
  189 + config-storage:
  190 + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取
  191 + key-prefix: wa # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
  192 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
187 193
188 --- #################### 芋道相关配置 #################### 194 --- #################### 芋道相关配置 ####################
189 195
@@ -218,6 +224,12 @@ justauth: @@ -218,6 +224,12 @@ justauth:
218 client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw 224 client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
219 agent-id: 1000004 225 agent-id: 1000004
220 ignore-check-redirect-uri: true 226 ignore-check-redirect-uri: true
  227 + WECHAT_MINI_APP: # 微信小程序
  228 + client-id: ${wx.miniapp.appid}
  229 + client-secret: ${wx.miniapp.secret}
  230 + ignore-check-redirect-uri: true
  231 + ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验
  232 +
221 cache: 233 cache:
222 type: REDIS 234 type: REDIS
223 prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: 235 prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::