文章目录
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 {
@Resource
private WxLoginProperties wxLoginProperties;
@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;
}
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 {
@PostConstruct
public void logConfigSuccess ( ) {
log. info ( "WxLoginAutoConfiguration has been loaded successfully!" ) ;
}
@Bean
@ConditionalOnMissingBean
WxLoginUtil 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 {
@Resource
private 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; // 从环境变量中读取 appId
const redirectUri = encodeURIComponent(import.meta.env.VITE_REDIRECT_URI); // 从环境变量中读取 redirectUri
const 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;
}