SpringBoot整合微信小程序完整实现方案

179 阅读5分钟

SpringBoot整合微信小程序完整实现方案

一、准备工作

1. 微信小程序基本配置

  • 在微信公众平台注册并创建小程序
  • 获取小程序的AppID和AppSecret
  • 配置小程序的合法域名(开发/生产环境)

2. 项目依赖配置

pom.xml文件中添加以下依赖:

<!-- 微信小程序Java开发工具包 -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>4.5.5.B</version>
</dependency>

<!-- Spring Boot Web依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 如果需要Redis存储session/token -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

二、配置类实现

1. 微信小程序配置属性类

package com.example.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
    private String appId;
    private String secret;
    private String token;
    private String aesKey;
    private String msgDataFormat;
}

2. 微信小程序服务配置类

package com.example.config;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WxMaConfig {

    @Autowired
    private WxMaProperties wxMaProperties;

    @Bean
    public WxMaService wxMaService() {
        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
        config.setAppid(wxMaProperties.getAppId());
        config.setSecret(wxMaProperties.getSecret());
        config.setToken(wxMaProperties.getToken());
        config.setAesKey(wxMaProperties.getAesKey());
        config.setMsgDataFormat(wxMaProperties.getMsgDataFormat());

        WxMaService service = new WxMaServiceImpl();
        service.setWxMaConfig(config);
        return service;
    }
}

3. 配置application.yml

spring:
  application:
    name: springboot-miniapp-demo

wx:
  miniapp:
    appId: your-app-id  # 替换为你的小程序AppID
    secret: your-secret  # 替换为你的小程序AppSecret
    token: your-token  # 可自定义
    aesKey: your-aes-key  # 可自定义
    msgDataFormat: JSON

server:
  port: 8080

三、微信登录实现

1. 实体类设计

package com.example.entity;

import lombok.Data;

@Data
public class User {
    private String id;
    private String openId;
    private String unionId;
    private String nickName;
    private String avatarUrl;
    private Integer gender;
    private String country;
    private String province;
    private String city;
    private String language;
    // 其他字段...
}

@Data
public class LoginRequest {
    private String code;
    private String rawData;
    private String signature;
    private String encryptedData;
    private String iv;
}

@Data
public class LoginResponse {
    private String token;
    private User userInfo;
    private boolean success;
    private String message;
}

2. 登录Service实现

package com.example.service;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import com.example.entity.LoginRequest;
import com.example.entity.LoginResponse;
import com.example.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class WxLoginService {

    @Autowired
    private WxMaService wxMaService;

    /**
     * 处理微信小程序登录
     */
    public LoginResponse login(LoginRequest request) {
        LoginResponse response = new LoginResponse();
        try {
            // 1. 调用微信接口,获取openid和session_key
            WxMaJscode2SessionResult sessionInfo = wxMaService.jsCode2SessionInfo(request.getCode());
            String openId = sessionInfo.getOpenid();
            String sessionKey = sessionInfo.getSessionKey();
            String unionId = sessionInfo.getUnionid();

            // 2. 验证用户信息完整性(可选)
            if (!wxMaService.getUserService().checkUserInfo(sessionKey, request.getRawData(), request.getSignature())) {
                response.setSuccess(false);
                response.setMessage("用户信息签名验证失败");
                return response;
            }

            // 3. 解密用户信息
            WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, request.getEncryptedData(), request.getIv());

            // 4. 生成自定义登录态(Token)
            String token = UUID.randomUUID().toString();
            
            // 5. 创建或更新用户信息
            User user = new User();
            user.setOpenId(openId);
            user.setUnionId(unionId);
            user.setNickName(userInfo.getNickName());
            user.setAvatarUrl(userInfo.getAvatarUrl());
            user.setGender(userInfo.getGender());
            user.setCountry(userInfo.getCountry());
            user.setProvince(userInfo.getProvince());
            user.setCity(userInfo.getCity());
            user.setLanguage(userInfo.getLanguage());
            
            // TODO: 保存用户到数据库
            
            // TODO: 将token与用户信息存入Redis或其他存储,设置过期时间

            // 6. 返回结果
            response.setSuccess(true);
            response.setToken(token);
            response.setUserInfo(user);
            response.setMessage("登录成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.setSuccess(false);
            response.setMessage("登录失败: " + e.getMessage());
        }
        return response;
    }
}

3. 登录Controller实现

package com.example.controller;

import com.example.entity.LoginRequest;
import com.example.entity.LoginResponse;
import com.example.service.WxLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/wx")
public class WxLoginController {

    @Autowired
    private WxLoginService wxLoginService;

    /**
     * 微信小程序登录接口
     */
    @PostMapping("/login")
    public LoginResponse login(@RequestBody LoginRequest request) {
        return wxLoginService.login(request);
    }

    /**
     * 验证Token有效性(可选)
     */
    @GetMapping("/checkToken")
    public boolean checkToken(@RequestHeader String token) {
        // TODO: 验证token是否有效
        return true; // 简化实现,实际应从Redis等存储中校验
    }
}

四、微信小程序前端实现

1. 登录页面示例

// pages/login/login.js
Page({
  data: {
    userInfo: null,
    hasUserInfo: false
  },

  onLoad() {
    // 检查是否已登录
    const token = wx.getStorageSync('token');
    if (token) {
      // 验证token有效性
      wx.request({
        url: 'https://your-api-domain/api/wx/checkToken',
        header: {
          'token': token
        },
        success: (res) => {
          if (res.data) {
            this.setData({
              userInfo: wx.getStorageSync('userInfo'),
              hasUserInfo: true
            });
          }
        }
      });
    }
  },

  // 微信授权登录
  onGetUserInfo(e) {
    if (e.detail.userInfo) {
      // 获取用户信息成功,进行登录
      this.login();
    }
  },

  // 登录流程
  login() {
    wx.showLoading({
      title: '登录中...',
    });

    // 1. 获取登录code
    wx.login({
      success: (res) => {
        if (res.code) {
          // 2. 获取用户信息
          wx.getUserProfile({
            desc: '用于完善会员资料',
            success: (userRes) => {
              // 3. 发送到后端登录
              this.sendLoginRequest(res.code, userRes);
            },
            fail: () => {
              wx.hideLoading();
              wx.showToast({
                title: '获取用户信息失败',
                icon: 'none'
              });
            }
          });
        } else {
          wx.hideLoading();
          wx.showToast({
            title: '登录失败,请重试',
            icon: 'none'
          });
        }
      },
      fail: () => {
        wx.hideLoading();
        wx.showToast({
          title: '登录失败,请重试',
          icon: 'none'
        });
      }
    });
  },

  // 发送登录请求到后端
  sendLoginRequest(code, userRes) {
    wx.request({
      url: 'https://your-api-domain/api/wx/login',
      method: 'POST',
      data: {
        code: code,
        rawData: userRes.rawData,
        signature: userRes.signature,
        encryptedData: userRes.encryptedData,
        iv: userRes.iv
      },
      success: (res) => {
        wx.hideLoading();
        if (res.data.success) {
          // 保存登录状态
          wx.setStorageSync('token', res.data.token);
          wx.setStorageSync('userInfo', res.data.userInfo);
          
          this.setData({
            userInfo: res.data.userInfo,
            hasUserInfo: true
          });

          wx.showToast({
            title: '登录成功'
          });
        } else {
          wx.showToast({
            title: res.data.message || '登录失败',
            icon: 'none'
          });
        }
      },
      fail: () => {
        wx.hideLoading();
        wx.showToast({
          title: '网络请求失败',
          icon: 'none'
        });
      }
    });
  },

  // 退出登录
  logout() {
    wx.removeStorageSync('token');
    wx.removeStorageSync('userInfo');
    this.setData({
      userInfo: null,
      hasUserInfo: false
    });
  }
});

2. WXML页面

<!-- pages/login/login.wxml -->
<view class="container">
  <!-- 已登录状态 -->
  <view wx:if="{{hasUserInfo}}" class="user-info">
    <image src="{{userInfo.avatarUrl}}" class="avatar"></image>
    <text class="nickname">{{userInfo.nickName}}</text>
    <button class="logout-btn" bindtap="logout">退出登录</button>
  </view>
  
  <!-- 未登录状态 -->
  <view wx:else class="login-view">
    <text class="title">欢迎使用</text>
    <button class="login-btn" type="primary" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">
      微信授权登录
    </button>
  </view>
</view>

3. WXSS样式

/* pages/login/login.wxss */
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-top: 100rpx;
}

.login-view {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.title {
  font-size: 36rpx;
  margin-bottom: 60rpx;
  color: #333;
}

.login-btn {
  width: 600rpx;
  margin-top: 20rpx;
}

.user-info {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.avatar {
  width: 200rpx;
  height: 200rpx;
  border-radius: 100rpx;
  margin-bottom: 30rpx;
}

.nickname {
  font-size: 36rpx;
  margin-bottom: 40rpx;
  color: #333;
}

.logout-btn {
  width: 400rpx;
  margin-top: 20rpx;
}

五、高级功能扩展

1. 拦截器实现(Token验证)

package com.example.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取token
        String token = request.getHeader("token");
        
        // TODO: 从Redis等存储中验证token有效性
        if (token == null || !isValidToken(token)) {
            response.setStatus(401);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("{\"success\":false,\"message\":\"未授权,请登录\"}");
            return false;
        }
        
        return true;
    }
    
    private boolean isValidToken(String token) {
        // TODO: 实现token验证逻辑
        return true; // 简化实现
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 处理完请求后执行
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求完成后执行
    }
}

2. 注册拦截器

package com.example.config;

import com.example.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求,除了登录接口
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/api/wx/login", "/api/wx/checkToken");
    }
}

六、安全注意事项

  1. 敏感信息保护

    • AppSecret不应硬编码在前端代码中
    • 在服务端妥善保管AppSecret
  2. 接口安全

    • 实现请求频率限制,防止恶意调用
    • 验证用户信息签名,确保数据完整性
    • 对token设置合理的过期时间
  3. 数据存储安全

    • 敏感用户数据加密存储
    • 实现定期清理过期token
  4. HTTPS配置

    • 生产环境必须使用HTTPS协议
    • 在小程序后台配置正确的域名白名单

七、部署与测试

  1. 本地测试

    • 使用微信开发者工具进行调试
    • 配置正确的请求域名或使用代理
  2. 生产环境部署

    • 确保服务器配置符合要求
    • 配置SSL证书启用HTTPS
    • 在微信公众平台配置正确的域名

通过以上步骤,您可以成功实现SpringBoot与微信小程序的整合,包括用户登录、授权和信息获取等核心功能。此方案提供了完整的代码示例和安全实践,可根据实际需求进行扩展和优化。