文章目錄
- 1.資質申請
- 2.combinations-wx-login-starter
- 1.目錄結構
- 2.pom.xml 引入okhttp依賴
- 3.WxLoginProperties.java 屬性配置
- 4.WxLoginUtil.java 后端通過 code 獲取 access_token的工具類
- 5.WxLoginAutoConfiguration.java 自動配置類
- 6.spring.factories 激活自動配置類
- 3.combinations-wx-starter-demo
- 1.目錄結構
- 2.pom.xml 引入依賴
- 3.application.yml 配置AppID和AppSecret
- 4.application-prod.yml 配置生產環境的日志和.env文件路徑
- 5.CodeAndState.java 接受code和state的bean
- 6.WxLoginController.java 微信登錄Controller
- 7.WxApplication.java 啟動類
- 4.微信登錄流程梳理
- 1.用戶點擊微信登錄按鈕
- 2.前端向開放平臺發送請求主要攜帶appId和redirectUri
- 3.此時開放平臺會彈出一個掃碼的頁面,用戶掃碼確認
- 4.用戶確認成功后,開放平臺會將code和state作為參數去請求redirectUri(前端頁面)
- 5.前端頁面獲取code和state,再向后端發送請求
- 6.后端使用code進行微信登錄,可以獲取到AccessTokenResponse
1.資質申請
- 主體為企業的域名和備案的服務器
- 主體為企業的微信開放平臺的開發者資質認證
- 微信開放平臺創建應用獲取AppID和AppSecret
2.combinations-wx-login-starter
1.目錄結構

2.pom.xml 引入okhttp依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.sunxiansheng</groupId><artifactId>sunrays-combinations</artifactId><version>1.0.0</version></parent><artifactId>combinations-wx-login-starter</artifactId><name>${project.groupId}:${project.artifactId}</name><description>微信登錄模塊封裝</description><dependencies><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId></dependency></dependencies>
</project>
3.WxLoginProperties.java 屬性配置
package cn.sunxiansheng.wx.login.config.properties;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "sun-rays.wx.login")
@Data
public class WxLoginProperties {private String appId;private String appSecret;private String accessTokenUrlPrefix = "https://api.weixin.qq.com/sns/oauth2/access_token";
}
4.WxLoginUtil.java 后端通過 code 獲取 access_token的工具類
package cn.sunxiansheng.wx.login.utils;import cn.sunxiansheng.wx.login.config.properties.WxLoginProperties;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;import javax.annotation.Resource;
@Slf4j
public class WxLoginUtil {@Resourceprivate WxLoginProperties wxLoginProperties;@Datapublic static class AccessTokenResponse {@SerializedName("access_token")private String accessToken;@SerializedName("expires_in")private Integer expiresIn;@SerializedName("refresh_token")private String refreshToken;@SerializedName("openid")private String openId;@SerializedName("scope")private String scope;@SerializedName("unionid")private String unionId;}public AccessTokenResponse wxLogin(String code) {return getAccessToken(wxLoginProperties.getAppId(), wxLoginProperties.getAppSecret(), code);}private AccessTokenResponse getAccessToken(String appid, String secret, String code) {String url = String.format("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",wxLoginProperties.getAccessTokenUrlPrefix(), appid, secret, code);OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url(url).build();try (Response response = client.newCall(request).execute()) {if (!response.isSuccessful()) {String responseBody = response.body() != null ? response.body().string() : "響應體為空";log.error("后端通過 code 獲取 access_token 的請求失敗,響應碼:{}, 響應體:{}", response.code(), responseBody);return null;}String jsonResponse = response.body() != null ? response.body().string() : "響應體為空";log.info("成功獲取 access_token,響應:{}", jsonResponse);Gson gson = new Gson();return gson.fromJson(jsonResponse, AccessTokenResponse.class);} catch (Exception e) {log.error(e.getMessage(), e);return null;}}
}
5.WxLoginAutoConfiguration.java 自動配置類
package cn.sunxiansheng.wx.login.config;import cn.sunxiansheng.wx.login.config.properties.WxLoginProperties;
import cn.sunxiansheng.wx.login.utils.WxLoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
@Configuration
@EnableConfigurationProperties({WxLoginProperties.class})
@Slf4j
public class WxLoginAutoConfiguration {@PostConstructpublic void logConfigSuccess() {log.info("WxLoginAutoConfiguration has been loaded successfully!");}@Bean@ConditionalOnMissingBeanWxLoginUtil wxLoginUtil() {return new WxLoginUtil();}
}
6.spring.factories 激活自動配置類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.sunxiansheng.wx.login.config.WxLoginAutoConfiguration
3.combinations-wx-starter-demo
1.目錄結構

2.pom.xml 引入依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>cn.sunxiansheng</groupId><artifactId>sunrays-combinations-demo</artifactId><version>1.0.0</version></parent><artifactId>combinations-wx-starter-demo</artifactId><dependencies><dependency><groupId>cn.sunxiansheng</groupId><artifactId>combinations-wx-login-starter</artifactId><version>1.0.0</version></dependency><dependency><groupId>cn.sunxiansheng</groupId><artifactId>common-web-starter</artifactId><version>1.0.0</version></dependency></dependencies><build><finalName>${project.artifactId}-${project.version}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>
3.application.yml 配置AppID和AppSecret
sun-rays:log4j2:home: /Users/sunxiansheng/IdeaProjects/sunrays-framework/sunrays-combinations-demo/combinations-wx-starter-demo/logs env:path: /Users/sunxiansheng/IdeaProjects/sunrays-framework/sunrays-combinations-demo/combinations-wx-starter-demo wx:login:app-id: ${WX_LOGIN_APP_ID} app-secret: ${WX_LOGIN_APP_SECRET}
spring:profiles:active: prod
4.application-prod.yml 配置生產環境的日志和.env文件路徑
sun-rays:log4j2:home: /www/wwwroot/sunrays-framework/logs env:path: /www/wwwroot/sunrays-framework
5.CodeAndState.java 接受code和state的bean
package cn.sunxiansheng.wx.entity;import lombok.Data;@Data
public class CodeAndState {private String code;private String state;
}
6.WxLoginController.java 微信登錄Controller
package cn.sunxiansheng.wx.controller;import cn.sunxiansheng.wx.entity.CodeAndState;
import cn.sunxiansheng.wx.login.utils.WxLoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/wx")
public class WxLoginController {@Resourceprivate WxLoginUtil wxLoginUtil;@RequestMapping("/test")public String test() {return "test";}@RequestMapping("/login")public String login(@RequestBody CodeAndState codeAndState) {WxLoginUtil.AccessTokenResponse accessTokenResponse = wxLoginUtil.wxLogin(codeAndState.getCode());if (accessTokenResponse == null) {log.error("accessToken is null");return "null";}String unionId = accessTokenResponse.getUnionId();if (unionId == null) {log.error("unionId is null");return "null";}return accessTokenResponse.getUnionId();}
}
7.WxApplication.java 啟動類
package cn.sunxiansheng.wx;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WxApplication {public static void main(String[] args) {SpringApplication.run(WxApplication.class, args);}
}
4.微信登錄流程梳理
1.用戶點擊微信登錄按鈕

2.前端向開放平臺發送請求主要攜帶appId和redirectUri
<template><button @click="handleLogin" class="wechat-login-button">微信登錄</button>
</template><script>
export default {methods: {handleLogin() {// 從環境變量中獲取參數const appId = import.meta.env.VITE_APP_ID; // 從環境變量中讀取 appIdconst redirectUri = encodeURIComponent(import.meta.env.VITE_REDIRECT_URI); // 從環境變量中讀取 redirectUriconst responseType = 'code';const scope = 'snsapi_login'; // 網頁應用固定填寫 snsapi_login// 生成一個隨機的 state 參數,用于防止 CSRF 攻擊const state = Math.random().toString(36).substring(2); // 或者使用更安全的方式生成一個隨機字符串// 拼接請求URL,并加入 state 參數const wechatLoginUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${redirectUri}&response_type=${responseType}&scope=${scope}&state=${state}#wechat_redirect`;// 跳轉到微信登錄頁面window.location.href = wechatLoginUrl;},},
};
</script><style scoped>
.wechat-login-button {background-color: #1aad19;color: white;border: none;border-radius: 5px;padding: 10px 20px;cursor: pointer;transition: background-color 0.3s ease;
}.wechat-login-button:hover {background-color: #128c13;
}
</style>
3.此時開放平臺會彈出一個掃碼的頁面,用戶掃碼確認

4.用戶確認成功后,開放平臺會將code和state作為參數去請求redirectUri(前端頁面)

5.前端頁面獲取code和state,再向后端發送請求
<template><div class="login-container"><div class="loading-spinner"></div><p class="loading-text">微信登錄中,請稍候...</p></div>
</template><script>
export default {async mounted() {const urlParams = new URLSearchParams(window.location.search);const code = urlParams.get("code");const state = urlParams.get("state");if (!code) {console.error("未獲取到微信返回的 code");alert("登錄失敗,請重試");return;}try {const response = await fetch("/wx/login", {method: "POST",headers: {"Content-Type": "application/json",},body: JSON.stringify({ code, state }),});const result = await response.json();if (result.success) {const unionid = result.data;alert(`登錄成功,您的unionid是:${unionid}`);this.$router.push({ path: "/products" });} else {alert("登錄失敗,請重試");}} catch (error) {console.error("請求失敗", error);alert("網絡錯誤,請稍后重試");}},
};
</script><style scoped>
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap");:root {--primary-color: #4facfe;--secondary-color: #00f2fe;--text-color: #333;
}.login-container {display: flex;flex-direction: column;align-items: center;justify-content: center;height: 100vh;background: linear-gradient(120deg, #ffffff, #f0f0f0);font-family: "Poppins", sans-serif;
}.loading-spinner {width: 60px;height: 60px;border: 6px solid #e0e0e0;border-top: 6px solid var(--primary-color);border-radius: 50%;animation: spin 1s linear infinite;
}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}
}.loading-text {margin-top: 20px;font-size: 18px;font-weight: 500;color: var(--text-color);animation: fadeIn 2s ease-in-out infinite alternate;
}@keyframes fadeIn {0% {opacity: 0.6;}100% {opacity: 1;}
}
</style>
6.后端使用code進行微信登錄,可以獲取到AccessTokenResponse
@Data
public static class AccessTokenResponse {@SerializedName("access_token")private String accessToken;@SerializedName("expires_in")private Integer expiresIn;@SerializedName("refresh_token")private String refreshToken;@SerializedName("openid")private String openId;@SerializedName("scope")private String scope;@SerializedName("unionid")private String unionId;
}