从0到1:手把手教你开发微信公众号模板消息推送服务
2026/5/8 16:44:41 网站建设 项目流程

文章目录

  • 前言
    • 1. 项目简介
      • 1.1 什么是微信模板消息?
      • 1.2 本项目实现了什么功能?
      • 1.3 技术栈
    • 2. 项目结构
    • 3. 核心功能实现
      • 3.1 access_token 自动管理
      • 3.2 模板消息发送
      • 3.3 配置文件
    • 4. 踩坑经验分享
      • 4.1 测试公众号 vs 正式公众号
      • openId 可以这里取(也可以通过code获取):
      • 消息模版id 这里取
      • 4.2 openid 注意事项
      • 4.3 关于 .gitignore
    • 5. 完整代码获取
    • 6. 单元测试与集成测试实战
      • 6.1 什么是单元测试?
      • 6.2 什么是集成测试?
      • 6.3 我们项目的测试架构
      • 6.4 WxPushServiceTest(单元测试)详解
      • 6.5 WxPushApplicationTests(集成测试)详解
      • 6.6 如何选择什么时候用什么测试?
    • 7. 总结

前言

1. 项目简介

微信公众号模板消息推送服务Demo

1.1 什么是微信模板消息?

微信模板消息是微信提供给公众号的一种消息推送能力,允许公众号主动向关注者发送符合特定模板的消息,比如订单通知、物流提醒、账户变动等。

1.2 本项目实现了什么功能?

  • access_token 自动获取和刷新(有效期7200秒,提前10分钟刷新)
  • 模板消息发送(支持自定义数据和颜色)
  • 获取关注者列表(通过/wx/users接口)
  • OAuth2 授权回调(通过 code 获取 openid)

1.3 技术栈

技术用途
Java 1.8开发语言
Spring Boot 2.7.18应用框架
OkHttp 4.12.0HTTP 客户端
Lombok简化代码
Maven构建工具

2. 项目结构

wx-push-demo/├── pom.xml ├── src/│ └── main/│ ├── java/com/example/wxpush/│ │ ├── WxPushApplication.java # 启动类 │ │ ├── config/│ │ │ └── WxMpConfig.java # 微信配置 │ │ ├── dto/│ │ │ ├── AccessTokenResponse.java # access_token 响应 │ │ │ ├── TemplateDataItem.java # 模板数据项 │ │ │ ├── TemplateMessage.java # 模板消息请求 │ │ │ └── WxBaseResponse.java # 微信通用响应 │ │ ├── service/│ │ │ ├── AccessTokenService.java # access_token 管理 │ │ │ └── WxPushService.java # 模板消息发送 │ │ └── controller/│ │ └── WxUserController.java # 关注者/授权接口 │ └── resources/│ └── application.yml # 配置文件

3. 核心功能实现

3.1 access_token 自动管理

微信的 access_token 有效期 7200 秒(2小时),我们需要:

  1. 启动时立即获取
  2. 缓存到内存
  3. 定时刷新(提前10分钟)
  4. 双重检查锁保证线程安全

关键代码:

@Slf4j@ServicepublicclassAccessTokenService{@AutowiredprivateWxMpConfigwxMpConfig;privatevolatileStringaccessToken;privatevolatilelongexpireTime=0;privateScheduledExecutorServicescheduler;@PostConstructpublicvoidinit(){refreshToken();scheduler=Executors.newSingleThreadScheduledExecutor(r->{Threadt=newThread(r,"wx-token-refresh");t.setDaemon(true);returnt;});scheduler.scheduleAtFixedRate(this::refreshToken,110,110,TimeUnit.MINUTES);}publicStringgetAccessToken(){if(System.currentTimeMillis()>expireTime-5*60*1000){synchronized(this){if(System.currentTimeMillis()>expireTime-5*60*1000){refreshToken();}}}returnaccessToken;}privatevoidrefreshToken(){try{Stringurl=String.format(WxMpConfig.TOKEN_URL,wxMpConfig.getAppId(),wxMpConfig.getAppSecret());Requestrequest=newRequest.Builder().url(url).get().build();try(Responseresponse=newOkHttpClient().newCall(request).execute()){Stringbody=response.body().string();AccessTokenResponsetokenResp=newObjectMapper().readValue(body,AccessTokenResponse.class);if(tokenResp.isSuccess()){this.accessToken=tokenResp.getAccessToken();this.expireTime=System.currentTimeMillis()+tokenResp.getExpiresIn()*1000L;log.info("access_token 刷新成功");}}}catch(IOExceptione){log.error("刷新 access_token 异常",e);}}}

3.2 模板消息发送

发送模板消息的步骤:

  1. 构造模板数据
  2. 获取有效的 access_token
  3. 调用微信 API

关键代码:

@Slf4j@ServicepublicclassWxPushService{@AutowiredprivateWxMpConfigwxMpConfig;@AutowiredprivateAccessTokenServiceaccessTokenService;publicbooleansendTemplateMessage(StringopenId,Map<String,TemplateDataItem>data,Stringurl){returnsendTemplateMessage(openId,wxMpConfig.getTemplateId(),data,url,null);}publicbooleansendTemplateMessage(StringopenId,StringtemplateId,Map<String,TemplateDataItem>data,Stringurl,TemplateMessage.MiniProgramminiProgram){TemplateMessagemessage=TemplateMessage.builder().toUser(openId).templateId(templateId).url(url).miniProgram(miniProgram).data(data).build();Stringtoken=accessTokenService.getAccessToken();if(token==null||token.isEmpty()){log.error("发送模板消息失败: access_token 为空");returnfalse;}StringapiUrl=String.format(WxMpConfig.TEMPLATE_SEND_URL,token);try{StringjsonBody=newObjectMapper().writeValueAsString(message);log.info("发送模板消息, openId: {}",openId);Requestrequest=newRequest.Builder().url(apiUrl).post(RequestBody.create(jsonBody,MediaType.get("application/json; charset=utf-8"))).build();try(Responseresponse=newOkHttpClient().newCall(request).execute()){Stringbody=response.body().string();WxBaseResponsewxResp=newObjectMapper().readValue(body,WxBaseResponse.class);if(wxResp.isSuccess()){log.info("模板消息发送成功");returntrue;}else{log.error("模板消息发送失败: {}",wxResp.getErrMsg());returnfalse;}}}catch(IOExceptione){log.error("发送模板消息异常",e);returnfalse;}}}

3.3 配置文件

server:port:8081# 微信公众号配置wx:mp:app-id:你的AppIDapp-secret:你的AppSecrettemplate-id:你的模板IDlogging:level:com.example.wxpush:DEBUG

4. 踩坑经验分享

4.1 测试公众号 vs 正式公众号

特性测试公众号正式公众号
firstremark占位符❌ 不支持✅ 支持
keyword1~keywordN✅ 支持✅ 支持
正式使用❌ 仅测试✅ 推荐

测试公众号平台,没有的可以申请一个:
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

openId 可以这里取(也可以通过code获取):

消息模版id 这里取

4.2 openid 注意事项

  • 每个公众号的 openid 不同:同一用户在不同公众号的 openid 不一样
  • 测试号 openid:通过测试号二维码关注后,用/wx/users接口获取
  • 用户必须关注:发送模板消息前,用户必须已关注该公众号

4.3 关于 .gitignore

一定要配置.gitignore,忽略以下文件:

target/ .idea/ *.log application-local.yml

5. 完整代码获取

项目已完整实现,所有代码都在:
[wx-push-demo 项目地址]


6. 单元测试与集成测试实战

现在我们的项目有完整的测试覆盖了!这是非常重要的一步。

6.1 什么是单元测试?

单元测试是对代码中最小可测试单元的测试,目的是验证这些单元在隔离状态下是否正常工作。

| 特点:

  • ❌ **不调用真实接口和依赖
  • 运行速度快
  • 🎯只测单个类或方法
  • ✅ **使用 Mock 模拟外部依赖

6.2 什么是集成测试?

集成测试是验证多个模块或整个系统一起工作是否正常。

| 特点:

  • ✅ **调用真实接口和依赖
  • 🐢运行速度较慢
  • 🌐启动完整 Spring 容器
  • 🎯测试系统集成效果

6.3 我们项目的测试架构

测试类型测试类测试内容
单元测试WxPushServiceTest参数校验、核心业务逻辑
单元测试AccessTokenServiceTest组件加载验证
集成测试WxPushApplicationTests完整流程、真实微信 API

6.4 WxPushServiceTest(单元测试)详解

我们用 Mockito 模拟依赖,只测试 WxPushService 自身逻辑:

@ExtendWith(MockitoExtension.class)classWxPushServiceTest{@MockprivateWxMpConfigwxMpConfig;@MockprivateAccessTokenServiceaccessTokenService;@InjectMocksprivateWxPushServicewxPushService;@TestvoidsendTemplateMessage_WhenTokenIsNull_ShouldReturnFalse(){when(accessTokenService.getAccessToken()).thenReturn(null);booleanresult=wxPushService.sendTemplateMessage("test-openid",testData,null);assertFalse(result);}}

**测试了以下边界情况:

  • token 为空
  • openId 为空
  • data 为 null
  • data 为空 Map

6.5 WxPushApplicationTests(集成测试)详解

使用 @SpringBootTest 注解启动完整 Spring 上下文,调用真实接口:

@SpringBootTestclassWxPushApplicationTests{@AutowiredprivateWxPushServicewxPushService;@TestvoidcontextLoads(){assertNotNull(wxPushService);}@TestvoidsendTemplateMessage(){StringopenId="oiAR-xxxxx";Map<String,TemplateDataItem>data=newHashMap<>();data.put("keyword1",TemplateDataItem.of("测试内容1"));data.put("keyword2",TemplateDataItem.of("测试内容2"));booleanresult=wxPushService.sendTemplateMessage(openId,data,null);System.out.println("发送结果: "+(result?"成功":"失败");}}

6.6 如何选择什么时候用什么测试?

场景推荐测试类型
日常开发、写代码时单元测试(速度快,反馈及时)
提交代码前、发布前所有测试(确保集成没问题)
调试微信接口时单独运行集成测试

7. 总结

通过这个项目,我们学习了:

  • ✅ Spring Boot 项目搭建
  • ✅ 微信公众平台 API 调用
  • ✅ access_token 的管理和刷新
  • ✅ 模板消息发送
  • ✅ Maven 项目测试和构建
  • ✅ **单元测试与 Mockito 使用
  • ✅ **集成测试与 @SpringBootTest
  • ✅ **测试最佳实践

希望这篇博客对你有帮助!🎉

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询