SpringBoot对接微信公众号并实现消息发送功能详解
一、微信公众号开发概述
微信公众号开发是企业在微信生态中进行客户服务、营销推广的重要方式。通过对接微信公众号,企业可以实现自动回复、菜单定制、消息推送等多种功能,极大地提升了与用户的互动体验。
1.1 微信公众号类型
微信公众号主要分为三种类型:
- 订阅号:主要用于信息传播,每天可以群发一条消息,适合媒体和个人使用。
- 服务号:提供更多高级接口和能力,每月可群发4条消息,适合企业客户服务。
- 企业号(现称企业微信):专注于企业内部管理和办公协同。
1.2 开发模式
微信公众号开发有两种模式:
- 编辑模式:通过微信公众平台提供的可视化界面进行配置,适合简单需求。
- 开发模式:通过API接口与自有服务器对接,实现更复杂的功能。
本文主要讲解基于SpringBoot的开发模式对接。
二、开发前准备工作
2.1 申请微信公众号
- 访问微信公众平台(mp.weixin.qq.com/)注册账号%25E6%25B3%25A8%25E5%2586%258C%25E8%25B4%25A6%25E5%258F%25B7 "https://mp.weixin.qq.com/)%E6%B3%A8%E5%86%8C%E8%B4%A6%E5%8F%B7")
- 选择账号类型(服务号或订阅号)
- 完成资质认证(企业用户需要)
2.2 服务器环境准备
- 公网可访问的服务器(建议使用云服务器)
- 备案域名(微信要求)
- SSL证书(必须启用HTTPS)
2.3 开发工具准备
- JDK 1.8+
- Maven或Gradle
- IDE(IntelliJ IDEA或Eclipse)
- 内网穿透工具(开发调试用,如ngrok)
三、SpringBoot项目搭建
3.1 创建SpringBoot项目
使用Spring Initializr创建项目,添加以下依赖:
xml
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 微信开发Java SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.1.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 其他可能需要的依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
3.2 配置微信公众号参数
在application.properties
或application.yml
中添加配置:
bash
# 微信公众号配置
wx:
mp:
configs:
- appId: ${微信公众号AppID}
secret: ${微信公众号AppSecret}
token: ${微信公众号Token}
aesKey: ${微信公众号EncodingAESKey}
3.3 创建配置类
arduino
@Configuration
@RequiredArgsConstructor
public class WxMpConfiguration {
private final WxMpProperties properties;
@Bean
public WxMpService wxMpService() {
WxMpService service = new WxMpServiceImpl();
WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
config.setAppId(properties.getAppId());
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
service.setWxMpConfigStorage(config);
return service;
}
}
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
private String appId;
private String secret;
private String token;
private String aesKey;
}
四、微信公众号服务器配置
4.1 服务器URL验证
微信服务器会发送GET请求验证服务器有效性,需要实现校验逻辑:
less
@RestController
@RequestMapping("/wx")
@RequiredArgsConstructor
public class WxPortalController {
private final WxMpService wxMpService;
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(
@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
// 签名校验失败
return "非法请求";
}
return echostr;
}
}
4.2 配置服务器地址
在微信公众平台后台:
- 进入"开发"->"基本配置"
- 点击"修改配置"
- 填写服务器URL、Token和EncodingAESKey
- 选择消息加解密方式(推荐安全模式)
- 提交并启用
五、接收用户消息
5.1 消息接收控制器
less
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
// 校验签名
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求");
}
// 处理消息
WxMpXmlMessage inMessage;
if (encType == null) {
// 明文模式
inMessage = WxMpXmlMessage.fromXml(requestBody);
} else if ("aes".equalsIgnoreCase(encType)) {
// AES加密模式
inMessage = WxMpXmlMessage.fromEncryptedXml(
requestBody, wxMpService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
} else {
throw new IllegalArgumentException("不支持的加密类型");
}
// 处理消息并返回响应
WxMpXmlOutMessage outMessage = handleMessage(inMessage);
if (outMessage == null) {
return "";
}
if (encType == null) {
return outMessage.toXml();
} else {
return outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage());
}
}
private WxMpXmlOutMessage handleMessage(WxMpXmlMessage inMessage) {
try {
// 根据消息类型进行不同处理
switch (inMessage.getMsgType()) {
case TEXT:
return handleTextMessage(inMessage);
case IMAGE:
return handleImageMessage(inMessage);
case VOICE:
return handleVoiceMessage(inMessage);
case VIDEO:
case SHORTVIDEO:
return handleVideoMessage(inMessage);
case LOCATION:
return handleLocationMessage(inMessage);
case LINK:
return handleLinkMessage(inMessage);
case EVENT:
return handleEventMessage(inMessage);
default:
return null;
}
} catch (Exception e) {
log.error("处理微信消息异常", e);
return null;
}
}
5.2 处理文本消息示例
scss
private WxMpXmlOutMessage handleTextMessage(WxMpXmlMessage message) {
String content = message.getContent();
String fromUser = message.getFromUser();
String toUser = message.getToUser();
// 简单回复
if ("你好".equals(content)) {
return WxMpXmlOutMessage.TEXT()
.content("您好,有什么可以帮您?")
.fromUser(toUser)
.toUser(fromUser)
.build();
}
// 关键词回复
if (content.contains("天气")) {
return WxMpXmlOutMessage.TEXT()
.content("您想查询哪个城市的天气?")
.fromUser(toUser)
.toUser(fromUser)
.build();
}
// 默认回复
return WxMpXmlOutMessage.TEXT()
.content("感谢您的消息,我们会尽快回复您!")
.fromUser(toUser)
.toUser(fromUser)
.build();
}
六、发送消息给用户
6.1 模板消息发送
模板消息是公众号向用户发送的重要通知消息,需要事先申请模板。
6.1.1 申请模板
- 在公众号后台"功能"->"模板消息"中添加模板
- 获取模板ID
6.1.2 发送模板消息
typescript
@Service
@RequiredArgsConstructor
public class WxTemplateMessageService {
private final WxMpService wxMpService;
public void sendTemplateMessage(String openId, String templateId,
String url, Map<String, String> data) {
// 构建模板消息
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(openId)
.templateId(templateId)
.url(url)
.build();
// 添加模板数据
data.forEach((key, value) -> {
templateMessage.addData(new WxMpTemplateData(key, value, "#FF0000"));
});
try {
// 发送消息
wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (WxErrorException e) {
log.error("发送模板消息失败", e);
throw new RuntimeException("发送模板消息失败", e);
}
}
}
6.1.3 使用示例
kotlin
// 发送订单支付成功通知
Map<String, String> data = new HashMap<>();
data.put("first", "尊敬的客户,您的订单已支付成功");
data.put("keyword1", "ORD123456789");
data.put("keyword2", "¥199.00");
data.put("keyword3", "2023-05-20 14:30:45");
data.put("remark", "感谢您的购买,点击查看订单详情");
wxTemplateMessageService.sendTemplateMessage(
"oX8Y35E-7Q2j3k9l0i1n2m3k4l5o6p7q",
"TEMPLATE_ID",
"https://yourdomain.com/order/ORD123456789",
data);
6.2 客服消息发送
客服消息是公众号在48小时内与用户进行互动的消息类型。
6.2.1 发送文本消息
typescript
public void sendTextMessage(String openId, String content) {
try {
wxMpService.getKefuService().sendText(openId, content);
} catch (WxErrorException e) {
log.error("发送客服文本消息失败", e);
throw new RuntimeException("发送客服文本消息失败", e);
}
}
6.2.2 发送图文消息
typescript
public void sendNewsMessage(String openId, String title, String description,
String url, String picUrl) {
WxMpKefuMessage.WxArticle article = new WxMpKefuMessage.WxArticle();
article.setTitle(title);
article.setDescription(description);
article.setUrl(url);
article.setPicUrl(picUrl);
try {
wxMpService.getKefuService().sendNews(openId, Collections.singletonList(article));
} catch (WxErrorException e) {
log.error("发送客服图文消息失败", e);
throw new RuntimeException("发送客服图文消息失败", e);
}
}
6.3 群发消息
群发消息是公众号向大量用户同时发送消息的功能。
6.3.1 按标签群发
scss
public void massSendByTag(String content, int tagId) {
WxMpMassTagMessage message = new WxMpMassTagMessage();
message.setContent(content);
message.setTagId(tagId);
message.setMsgType(WxConsts.MassMsgType.TEXT);
try {
wxMpService.getMassMessageService().massGroupMessageSend(message);
} catch (WxErrorException e) {
log.error("按标签群发消息失败", e);
throw new RuntimeException("按标签群发消息失败", e);
}
}
6.3.2 按OpenID列表群发
scss
public void massSendByOpenIds(String content, List<String> openIds) {
WxMpMassOpenIdsMessage message = new WxMpMassOpenIdsMessage();
message.setContent(content);
message.setMsgType(WxConsts.MassMsgType.TEXT);
message.setToUsers(openIds);
try {
wxMpService.getMassMessageService().massOpenIdsMessageSend(message);
} catch (WxErrorException e) {
log.error("按OpenID列表群发消息失败", e);
throw new RuntimeException("按OpenID列表群发消息失败", e);
}
}
七、用户管理
7.1 获取用户基本信息
typescript
public WxMpUser getUserInfo(String openId) {
try {
return wxMpService.getUserService().userInfo(openId);
} catch (WxErrorException e) {
log.error("获取用户信息失败", e);
throw new RuntimeException("获取用户信息失败", e);
}
}
7.2 获取用户列表
ini
public List<WxMpUser> getUserList() {
try {
List<WxMpUser> users = new ArrayList<>();
String nextOpenId = null;
do {
WxMpUserList userList = wxMpService.getUserService().userList(nextOpenId);
users.addAll(userList.getOpenids().stream()
.map(this::getUserInfo)
.collect(Collectors.toList()));
nextOpenId = userList.getNextOpenid();
} while (nextOpenId != null);
return users;
} catch (WxErrorException e) {
log.error("获取用户列表失败", e);
throw new RuntimeException("获取用户列表失败", e);
}
}
八、自定义菜单管理
8.1 创建自定义菜单
scss
public void createMenu() {
WxMpMenu menu = new WxMpMenu();
// 第一个按钮
WxMenuButton button1 = new WxMenuButton();
button1.setType(WxConsts.MenuButtonType.VIEW);
button1.setName("官网首页");
button1.setUrl("https://yourdomain.com");
// 第二个按钮 - 带子菜单
WxMenuButton button2 = new WxMenuButton();
button2.setName("产品中心");
// 子菜单1
WxMenuButton subButton1 = new WxMenuButton();
subButton1.setType(WxConsts.MenuButtonType.VIEW);
subButton1.setName("产品介绍");
subButton1.setUrl("https://yourdomain.com/products");
// 子菜单2
WxMenuButton subButton2 = new WxMenuButton();
subButton2.setType(WxConsts.MenuButtonType.CLICK);
subButton2.setName("最新活动");
subButton2.setKey("LATEST_ACTIVITY");
button2.getSubButtons().add(subButton1);
button2.getSubButtons().add(subButton2);
// 第三个按钮
WxMenuButton button3 = new WxMenuButton();
button3.setType(WxConsts.MenuButtonType.CLICK);
button3.setName("联系我们");
button3.setKey("CONTACT_US");
menu.getButtons().add(button1);
menu.getButtons().add(button2);
menu.getButtons().add(button3);
try {
wxMpService.getMenuService().menuCreate(menu);
} catch (WxErrorException e) {
log.error("创建菜单失败", e);
throw new RuntimeException("创建菜单失败", e);
}
}
8.2 处理菜单点击事件
在消息处理中添加对EVENT类型消息的处理:
java
private WxMpXmlOutMessage handleEventMessage(WxMpXmlMessage message) {
switch (message.getEvent()) {
case WxConsts.EventType.CLICK:
return handleMenuClickEvent(message);
case WxConsts.EventType.VIEW:
return handleMenuViewEvent(message);
case WxConsts.EventType.SUBSCRIBE:
return handleSubscribeEvent(message);
case WxConsts.EventType.UNSUBSCRIBE:
return handleUnsubscribeEvent(message);
// 其他事件处理...
default:
return null;
}
}
private WxMpXmlOutMessage handleMenuClickEvent(WxMpXmlMessage message) {
String eventKey = message.getEventKey();
String fromUser = message.getFromUser();
String toUser = message.getToUser();
switch (eventKey) {
case "LATEST_ACTIVITY":
return WxMpXmlOutMessage.TEXT()
.content("当前最新活动:五一特惠,全场5折起!")
.fromUser(toUser)
.toUser(fromUser)
.build();
case "CONTACT_US":
return WxMpXmlOutMessage.TEXT()
.content("客服电话:400-123-4567\n工作时间:9:00-18:00")
.fromUser(toUser)
.toUser(fromUser)
.build();
default:
return null;
}
}
九、消息加解密
微信提供了三种消息加解密模式:
- 明文模式
- 兼容模式
- 安全模式(推荐)
9.1 配置加解密
ini
@Bean
public WxMpService wxMpService(WxMpProperties properties) {
WxMpService service = new WxMpServiceImpl();
WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
config.setAppId(properties.getAppId());
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
service.setWxMpConfigStorage(config);
return service;
}
9.2 消息解密
在接收消息时已经包含了消息解密逻辑:
less
if ("aes".equalsIgnoreCase(encType)) {
// AES加密模式
inMessage = WxMpXmlMessage.fromEncryptedXml(
requestBody, wxMpService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
}
9.3 消息加密
在返回消息时:
kotlin
if (encType == null) {
return outMessage.toXml();
} else {
return outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage());
}
十、常见问题与解决方案
10.1 签名验证失败
可能原因:
- Token配置不一致
- 时间戳差异过大
- URL编码问题
解决方案:
- 检查公众号后台配置的Token与代码中是否一致
- 确保服务器时间正确
- 检查URL是否完整包含参数
10.2 消息发送失败
常见错误码:
- 45015:回复时间超过48小时
- 40003:无效的OpenID
- 40037:无效的模板ID
解决方案:
- 对于超过48小时未互动的用户,使用模板消息
- 检查OpenID是否正确获取
- 确保模板消息已申请并正确配置
10.3 性能优化建议
- 异步处理:对于耗时操作,使用异步处理避免阻塞
- 缓存AccessToken:AccessToken获取频率有限制,应缓存使用
- 消息队列:高并发场景下使用消息队列削峰
十一、高级功能扩展
11.1 微信网页授权
获取用户详细信息需要网页授权:
typescript
public String getAuthUrl(String redirectUri, String scope) {
return wxMpService.getOAuth2Service().buildAuthorizationUrl(
redirectUri,
scope,
null);
}
public WxOAuth2AccessToken getAccessToken(String code) {
try {
return wxMpService.getOAuth2Service().getAccessToken(code);
} catch (WxErrorException e) {
log.error("获取网页授权access_token失败", e);
throw new RuntimeException("获取网页授权access_token失败", e);
}
}
public WxOAuth2UserInfo getUserInfo(String accessToken, String openId) {
try {
return wxMpService.getOAuth2Service().getUserInfo(accessToken, openId);
} catch (WxErrorException e) {
log.error("获取用户信息失败", e);
throw new RuntimeException("获取用户信息失败", e);
}
}
11.2 微信JS-SDK配置
前端页面使用微信JS-SDK:
arduino
public Map<String, String> getJsApiConfig(String url) {
try {
WxJsapiSignature signature = wxMpService.createJsapiSignature(url);
Map<String, String> config = new HashMap<>();
config.put("appId", signature.getAppId());
config.put("timestamp", String.valueOf(signature.getTimestamp()));
config.put("nonceStr", signature.getNonceStr());
config.put("signature", signature.getSignature());
return config;
} catch (WxErrorException e) {
log.error("生成JS-SDK配置失败", e);
throw new RuntimeException("生成JS-SDK配置失败", e);
}
}
11.3 素材管理
上传和管理多媒体素材:
typescript
public String uploadMedia(String mediaType, String filePath) {
try {
WxMediaUploadResult result = wxMpService.getMaterialService()
.mediaUpload(mediaType, new File(filePath));
return result.getMediaId();
} catch (WxErrorException e) {
log.error("上传媒体文件失败", e);
throw new RuntimeException("上传媒体文件失败", e);
}
}
public File downloadMedia(String mediaId) {
try {
return wxMpService.getMaterialService().mediaDownload(mediaId);
} catch (WxErrorException e) {
log.error("下载媒体文件失败", e);
throw new RuntimeException("下载媒体文件失败", e);
}
}
十二、安全与监控
12.1 接口调用监控
less
@Aspect
@Component
@Slf4j
public class WxApiMonitorAspect {
@Around("execution(* me.chanjar.weixin.mp.api.impl..*.*(..))")
public Object monitorWxApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - startTime;
log.info("微信API调用成功 - 方法: {}, 耗时: {}ms", methodName, elapsed);
return result;
} catch (WxErrorException e) {
log.error("微信API调用失败 - 方法: {}, 错误码: {}, 错误信息: {}",
methodName, e.getError().getErrorCode(), e.getError().getErrorMsg());
throw e;
}
}
}
12.2 敏感数据保护
- 加密存储用户OpenID等敏感信息
- 使用HTTPS传输数据
- 实现数据访问权限控制
十三、测试与部署
13.1 测试策略
- 单元测试:测试核心业务逻辑
- 集成测试:测试微信API调用
- 端到端测试:模拟用户完整操作流程
13.2 部署建议
- 使用Docker容器化部署
- 配置负载均衡应对高并发
- 设置健康检查接口
十四、总结
本文详细介绍了如何使用SpringBoot对接微信公众号并实现消息发送功能,涵盖了从基础配置到高级功能的完整流程。通过合理的设计和实现,可以构建出稳定、高效的微信公众号后台服务,为用户提供优质的交互体验。
在实际开发中,还需要根据业务需求进行适当的调整和扩展,同时注意微信公众平台的接口调用频率限制和安全规范。希望本文能为开发者提供有价值的参考,助力微信公众号开发工作。