当前位置: 首页 > article >正文

微信登录模块封装

文章目录

    • 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.资质申请

  1. 主体为企业的域名和备案的服务器
  2. 主体为企业的微信开放平台的开发者资质认证
  3. 微信开放平台创建应用获取AppID和AppSecret

2.combinations-wx-login-starter

1.目录结构

CleanShot 2025-01-23 at 21.17.45@2x

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>
        <!-- okhttp -->
        <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;

/**
 * Description: 微信登录的属性配置
 *
 * @Author sun
 * @Create 2025/1/23 18:49
 * @Version 1.0
 */
@ConfigurationProperties(prefix = "sun-rays.wx.login")
@Data
public class WxLoginProperties {

    /**
     * 微信开放平台应用的AppID
     */
    private String appId;

    /**
     * 微信开放平台应用的AppSecret
     */
    private String appSecret;

    /**
     * 微信开放平台的access_token_url前缀,有默认值,可以不填,为了防止变化!
     */
    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;

/**
 * Description: 微信登录工具类
 *
 * @Author sun
 * @Create 2025/1/23 19:01
 * @Version 1.0
 */
@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;
    }

    /**
     * 根据code来完成微信登录
     *
     * @param code 微信开放平台返回的code
     * @return 返回AccessTokenResponse
     */
    public AccessTokenResponse wxLogin(String code) {
        return getAccessToken(wxLoginProperties.getAppId(), wxLoginProperties.getAppSecret(), code);
    }

    /**
     * 后端通过 code 获取 access_token
     *
     * @param appid  微信应用的 appid
     * @param secret 微信应用的 secret
     * @param code   后端已经获得的 code 参数
     * @return 返回封装的 AccessTokenResponse 对象
     */
    private AccessTokenResponse getAccessToken(String appid, String secret, String code) {
        // 构造请求 URL
        String url = String.format("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                wxLoginProperties.getAccessTokenUrlPrefix(), appid, secret, code);

        // 创建 OkHttpClient 实例
        OkHttpClient client = new OkHttpClient();

        // 创建 Request 对象
        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 解析 JSON 数据并封装成 AccessTokenResponse 对象
            Gson gson = new Gson();

            // 返回封装的对象
            return gson.fromJson(jsonResponse, AccessTokenResponse.class);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            // 返回 null 或者其他错误处理
            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;

/**
 * Description: 微信登录自动配置类
 *
 * @Author sun
 * @Create 2025/1/13 16:11
 * @Version 1.0
 */
@Configuration
@EnableConfigurationProperties({WxLoginProperties.class})
@Slf4j
public class WxLoginAutoConfiguration {

    /**
     * 自动配置成功日志
     */
    @PostConstruct
    public void logConfigSuccess() {
        log.info("WxLoginAutoConfiguration has been loaded successfully!");
    }

    /**
     * 注入WxLoginUtil
     *
     * @return
     */
    @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.目录结构

CleanShot 2025-01-23 at 21.25.48@2x

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>
        <!-- combinations-wx-login-starter -->
        <dependency>
            <groupId>cn.sunxiansheng</groupId>
            <artifactId>combinations-wx-login-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- common-web-starter -->
        <dependency>
            <groupId>cn.sunxiansheng</groupId>
            <artifactId>common-web-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

    <!-- maven 打包常规配置 -->
    <build>
        <!-- 打包成 jar 包时的名字为项目的artifactId + version -->
        <finalName>${project.artifactId}-${project.version}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 引用父模块中统一管理的插件版本(与SpringBoot的版本一致! -->
                <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 # .env文件的绝对路径
  wx:
    login:
      app-id: ${WX_LOGIN_APP_ID} # 微信开放平台应用的AppID
      app-secret: ${WX_LOGIN_APP_SECRET} # 微信开放平台应用的AppSecret
spring:
  profiles:
    active: prod # 激活的环境
4.application-prod.yml 配置生产环境的日志和.env文件路径
sun-rays:
  log4j2:
    home: /www/wwwroot/sunrays-framework/logs # 日志存储根目录
  env:
    path: /www/wwwroot/sunrays-framework # .env文件的绝对路径
5.CodeAndState.java 接受code和state的bean
package cn.sunxiansheng.wx.entity;

import lombok.Data;

/**
 * Description: 接受code和state的bean
 *
 * @Author sun
 * @Create 2025/1/16 19:15
 * @Version 1.0
 */

@Data
public class CodeAndState {
    /**
     * 微信的code
     */
    private String code;
    /**
     * 微信的state
     */
    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;

/**
 * Description: 微信登录Controller
 *
 * @Author sun
 * @Create 2025/1/13 16:26
 * @Version 1.0
 */
@Slf4j
@RestController
@RequestMapping("/wx")
public class WxLoginController {

    @Resource
    private WxLoginUtil wxLoginUtil;

    @RequestMapping("/test")
    public String test() {
        return "test";
    }

    /**
     * 微信登录
     *
     * @param codeAndState 前端传过来的code和state
     * @return 返回unionId
     */
    @RequestMapping("/login")
    public String login(@RequestBody CodeAndState codeAndState) {
        // 使用code来完成微信登录
        WxLoginUtil.AccessTokenResponse accessTokenResponse = wxLoginUtil.wxLogin(codeAndState.getCode());
        if (accessTokenResponse == null) {
            log.error("accessToken is null");
            return "null";
        }
        // 获取unionId
        String unionId = accessTokenResponse.getUnionId();
        if (unionId == null) {
            log.error("unionId is null");
            return "null";
        }
        // 获取unionId
        return accessTokenResponse.getUnionId();
    }
}
7.WxApplication.java 启动类
package cn.sunxiansheng.wx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Description: 微信启动类
 *
 * @Author sun
 * @Create 2025/1/13 17:54
 * @Version 1.0
 */
@SpringBootApplication
public class WxApplication {

    public static void main(String[] args) {
        SpringApplication.run(WxApplication.class, args);
    }
}

4.微信登录流程梳理

1.用户点击微信登录按钮

CleanShot 2025-01-23 at 21.36.20@2x

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.此时开放平台会弹出一个扫码的页面,用户扫码确认

CleanShot 2025-01-23 at 21.36.33@2x

4.用户确认成功后,开放平台会将code和state作为参数去请求redirectUri(前端页面)

CleanShot 2025-01-23 at 21.36.47@2x

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;
}

http://www.kler.cn/a/530267.html

相关文章:

  • Kafka常见问题之 java.io.IOException: Disk error when trying to write to log
  • MoonBit 编译器(留档学习)
  • Games104——网络游戏的进阶架构
  • Windows11 不依赖docker搭建 deepseek-R1 1.5B版本(附 Open WebUi搭建方式)
  • springCload快速入门
  • 架构技能(四):需求分析
  • 第一性原理:游戏开发成本的思考
  • 索罗斯的“反身性”(Reflexivity)理论:市场如何扭曲现实?(中英双语)
  • 【PyQt】lambda函数,实现动态传递参数
  • 本地Deepseek添加个人知识库(Page Assist/AnythingLLM)
  • 不确定性采样在分类任务中的应用
  • 【Navicat】设置字段根据当前时间更新
  • C++模板初了解
  • Vue 2 项目中 Mock.js 的完整集成与使用教程
  • C# 继承与多态详解
  • 新到手路由器宽带上网设置八步法
  • 2025.2.1——八、Web_php_wrong_nginx_config
  • 【大模型专栏—基础篇】智能体入门
  • TypeScript语言的语法糖
  • 硕成C语言3
  • 基于Java的林业盗砍盗伐监测算法研究与实现——读取Shp文件并比较
  • 韩语字符分析
  • 你需要更深层次的解放
  • 队列—学习
  • 基于RAG的知识库问答系统
  • DedeBIZ资源系统源码 高仿XDGAME模板源码