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");
}
}
六、安全注意事项
-
敏感信息保护
- AppSecret不应硬编码在前端代码中
- 在服务端妥善保管AppSecret
-
接口安全
- 实现请求频率限制,防止恶意调用
- 验证用户信息签名,确保数据完整性
- 对token设置合理的过期时间
-
数据存储安全
- 敏感用户数据加密存储
- 实现定期清理过期token
-
HTTPS配置
- 生产环境必须使用HTTPS协议
- 在小程序后台配置正确的域名白名单
七、部署与测试
-
本地测试
- 使用微信开发者工具进行调试
- 配置正确的请求域名或使用代理
-
生产环境部署
- 确保服务器配置符合要求
- 配置SSL证书启用HTTPS
- 在微信公众平台配置正确的域名
通过以上步骤,您可以成功实现SpringBoot与微信小程序的整合,包括用户登录、授权和信息获取等核心功能。此方案提供了完整的代码示例和安全实践,可根据实际需求进行扩展和优化。