微信公众平台Java开发完整教程
本教程将引导你使用Java语言,结合主流的Spring Boot框架,一步步开发一个功能完善的微信公众号服务号。

目录
- 准备工作:你需要什么?
- 第一步:接入微信公众平台(服务器验证)
- 第二步:接收和回复用户消息
- 1 消息类型解析
- 2 回复文本消息
- 3 回复图文消息
- 第三步:主动发送消息(客服接口)
- 第四步:网页授权获取用户信息
- 第五步:调用其他核心API(如自定义菜单)
- 第六步:项目结构与最佳实践
- 进阶与总结
准备工作:你需要什么?
在开始编码之前,请确保你已经具备以下条件:
-
一个Java开发环境:JDK 8或更高版本。
-
一个IDE:如IntelliJ IDEA或Eclipse。
-
Maven:用于项目管理,方便引入依赖。
(图片来源网络,侵删) -
一个微信公众号:
- 访问 微信公众平台。
- 注册一个 服务号(个人只能订阅号,功能受限,企业可申请服务号)。
- 登录后,进入“开发” -> “基本配置”。
- 关键信息:
- AppID (应用ID):公众号的唯一标识。
- AppSecret (应用密钥):非常重要,请妥善保管,不要泄露!
- 服务器配置 (URL, Token, EncodingAESKey):这是我们开发的核心配置。
-
一个外网可访问的服务器:微信公众平台的服务器验证和消息推送都需要你的服务有一个公网IP和域名,本地开发可以使用 内网穿透工具,如 ngrok、frp 等。
第一步:接入微信公众平台(服务器验证)
这是开发的第一步,也是最重要的一步,微信服务器会向你配置的URL发送一个GET请求,以验证你服务器的所有权。
1 验证原理
微信服务器会发送如下参数到你的URL:
signature: 微信加密签名,结合了token、timestamp、nonce。timestamp: 时间戳。nonce: 随机数。echostr: 随机字符串。
你需要做的是:
- 将
token、timestamp、nonce三个参数进行字典序排序。 - 将三个参数字符串拼接成一个字符串进行
SHA1加密。 - 将加密后的字符串与
signature进行对比。 - 如果一致,则说明请求来自微信,请原样返回
echostr参数内容。 - 如果不一致,则不是来自微信,返回空。
2 创建Spring Boot项目
-
使用 Spring Initializr 创建一个新项目。
- Project: Maven
- Language: Java
- Spring Boot: 选择一个稳定版本 (如 2.7.x)
- Project Metadata: 填写你的Group和Artifact。
- Dependencies: 添加
Spring Web。
-
项目创建后,用IDE打开。
3 编写验证Controller
在 src/main/java/com/yourpackage 下创建一个Controller类。
package com.example.wechatdemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@RestController
@RequestMapping("/wechat")
public class WeChatController {
// 在微信公众平台配置的Token,必须与公众号后台的Token一致
private final String TOKEN = "yourWechatToken";
@GetMapping
public String validate(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
// 1. 将token, timestamp, nonce三个参数进行字典序排序
String[] arr = {TOKEN, timestamp, nonce};
Arrays.sort(arr);
// 2. 将三个参数字符串拼接成一个字符串
StringBuilder content = new StringBuilder();
for (String s : arr) {
content.append(s);
}
// 3. SHA1加密
String temp = SHA1(content.toString());
// 4. 将加密后的字符串与signature进行对比
if (temp.equals(signature)) {
// 验证成功,原样返回echostr
return echostr;
} else {
// 验证失败
return "error";
}
}
/**
* SHA1加密方法
*/
private String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte[] messageDigest = digest.digest();
// Create Hex String
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String shaHex = Integer.toHexString(b & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
4 本地测试与配置
- 运行你的Spring Boot应用。
- 使用ngrok等工具将本地端口(如8080)映射到公网,ngrok http 8080。
- ngrok会提供一个类似
https://xxxxx.ngrok.io的公网地址。 - 登录微信公众平台后台,在“开发”->“基本配置”中点击“修改配置”:
- URL: 填入
https://xxxxx.ngrok.io/wechat(ngrok地址 + 你的Controller路径) - Token: 填入代码中定义的
yourWechatToken - EncodingAESKey: 可以随机生成,或留空(使用明文模式)。
- 消息加解密方式: 选择“安全模式”或“兼容模式”,初学者建议用“安全模式”或“明文模式”。
- URL: 填入
- 点击“提交”,如果配置正确,会提示“成功”。
恭喜!你的服务器已经成功接入了微信公众平台!
第二步:接收和回复用户消息
服务器验证成功后,用户在公众号中的任何操作(如发送消息、点击菜单等)都会通过POST请求发送到你配置的URL。
1 消息类型解析
微信发送过来的XML消息格式根据用户操作不同而不同,常见的有:
- 文本消息
- 图片消息
- 语音消息
- 视频消息
- 小视频消息
- 地理位置消息
- 链接消息
- 事件推送(如关注、取消关注、点击菜单等)
我们需要一个工具类来将XML请求解析成Java对象。
引入依赖 (pom.xml):
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.18</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
创建消息基类和实体类:
// 基础消息类
public class BaseMessage {
protected String ToUserName; // 开发者微信号
protected String FromUserName; // 发送方微信号(OpenID)
protected long CreateTime; // 消息创建时间
protected String MsgType; // 消息类型
// ... getters and setters
}
// 文本消息
public class TextMessage extends BaseMessage {
private String Content; // 消息内容
// ... getters and setters
// 构造函数等
}
// 图文消息
public class NewsMessage extends BaseMessage {
private int ArticleCount; // 图文消息个数,限制为10条以内
private List<Article> Articles; // 多条图文消息信息
// ... getters and setters
}
// 图文条目
public class Article {
private String Title; // 标题
private String Description; // 描述
private String PicUrl; // 图片链接
private String Url; // 点击后跳转的链接
// ... getters and setters
}
创建XML解析工具类:
import com.thoughtworks.xstream.XStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MessageUtil {
/**
* 解析微信发来的请求 XML
*/
public static Map<String, String> parseXml(HttpServletRequest request) throws IOException, DocumentException {
Map<String, String> map = new HashMap<>();
InputStream inputStream = request.getInputStream();
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
List<Element> elementList = root.elements();
for (Element e : elementList) {
map.put(e.getName(), e.getText());
}
inputStream.close();
return map;
}
/**
* 文本消息对象转换成XML
*/
public static String textMessageToXml(TextMessage textMessage) {
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 图文消息对象转换成XML
*/
public static String newsMessageToXml(NewsMessage newsMessage) {
XStream xstream = new XStream();
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", Article.class);
return xstream.toXML(newsMessage);
}
}
2 回复文本消息
修改 WeChatController,添加一个POST方法来处理消息。
package com.example.wechatdemo.controller;
// ... imports
import com.example.wechatdemo.message.TextMessage;
import com.example.wechatdemo.message.MessageUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@RestController
@RequestMapping("/wechat")
public class WeChatController {
// ... GET方法(验证逻辑)保持不变
@PostMapping
public String handlePost(HttpServletRequest request) throws Exception {
// 1. 解析XML
Map<String, String> map = MessageUtil.parseXml(request);
// 2. 提取消息类型
String msgType = map.get("MsgType");
// 3. 根据消息类型进行处理
if ("text".equals(msgType)) {
// 处理文本消息
return handleTextMessage(map);
}
// ... 可以添加其他类型的处理
return "success"; // 必须返回success,否则微信会认为消息处理失败
}
private String handleTextMessage(Map<String, String> map) {
// 1. 获取发送方和接收方
String fromUserName = map.get("FromUserName");
String toUserName = map.get("ToUserName");
// 2. 获取用户发送的内容
String content = map.get("Content");
// 3. 创建要回复的文本消息对象
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(System.currentTimeMillis() / 1000);
textMessage.setMsgType("text");
// 4. 设置回复内容(这里简单回复收到的内容)
textMessage.setContent("你发送的是: " + content);
// 5. 将消息对象转换为XML
return MessageUtil.textMessageToXml(textMessage);
}
}
重启你的应用,用微信关注你的公众号,给它发送任意文本消息,它会回复你相同的内容。
3 回复图文消息
图文消息稍微复杂一些,因为它包含一个Article列表。
// 在 WeChatController 中添加处理方法
private String handleTextMessage(Map<String, String> map) {
String fromUserName = map.get("FromUserName");
String toUserName = map.get("ToUserName");
String content = map.get("Content");
// 如果用户发送“图文”,则回复图文消息
if ("图文".equals(content)) {
NewsMessage newsMessage = new NewsMessage();
newsMessage.setToUserName(fromUserName);
newsMessage.setFromUserName(toUserName);
newsMessage.setCreateTime(System.currentTimeMillis() / 1000);
newsMessage.setMsgType("news");
Article article1 = new Article();
article1.setTitle("欢迎关注我的公众号");
article1.setDescription("这是一个图文消息示例,点击可跳转。");
article1.setPicUrl("https://your-image-url.com/image1.jpg"); // 替换为你的图片地址
article1.setUrl("https://www.baidu.com"); // 替换为你的链接
Article article2 = new Article();
article2.setTitle("Java学习资料");
article2.setDescription("这里有很多优质的Java学习资源。");
article2.setPicUrl("https://your-image-url.com/image2.jpg");
article2.setUrl("https://www.csdn.net");
List<Article> articles = new ArrayList<>();
articles.add(article1);
articles.add(article2);
newsMessage.setArticles(articles);
newsMessage.setArticleCount(articles.size());
return MessageUtil.newsMessageToXml(newsMessage);
}
// ... 普通文本回复逻辑
}
第三步:主动发送消息(客服接口)
除了被动回复,公众号还可以主动给用户发送消息,这通过“客服消息接口”实现。
1 获取Access Token
调用任何API都需要一个凭证:access_token,它有效期为2小时,需要缓存并定时刷新。
创建一个AccessTokenService:
package com.example.wechatdemo.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.TimeUnit;
@Service
public class AccessTokenService {
@Value("${wechat.appid}")
private String appId;
@Value("${wechat.secret}")
private String appSecret;
private String accessToken;
private long expireTime;
public synchronized String getAccessToken() {
if (accessToken != null && System.currentTimeMillis() < expireTime) {
return accessToken;
}
// 否则,重新获取
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(response.body());
accessToken = node.get("access_token").asText();
expireTime = System.currentTimeMillis() + (node.get("expires_in").asLong() - 300) * 1000; // 提前5分钟过期
return accessToken;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return null;
}
}
}
注意:在 application.properties 中配置你的 appid 和 secret。
2 发送客服消息
创建一个新的Controller或Service来处理发送逻辑。
// 在 WeChatController 中添加一个用于测试发送消息的接口
@PostMapping("/sendCustomerMessage")
public String sendCustomerMessage(@RequestParam String openId, @RequestParam String content) {
String accessToken = accessTokenService.getAccessToken();
if (accessToken == null) {
return "获取AccessToken失败";
}
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken;
// 构造JSON请求体
String jsonBody = String.format(
"{\"touser\":\"%s\",\"msgtype\":\"text\",\"text\":{\"content\":\"%s\"}}",
openId, content
);
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json; charset=utf-8")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body(); // 返回微信服务器的响应
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return "发送失败";
}
}
你可以使用Postman等工具调用这个接口,向某个用户的 openId 发送消息。
第四步:网页授权获取用户信息
当用户通过公众号菜单或链接访问你的网页时,你可以通过网页授权获取用户的 openId 和基本信息。
1 配置网页授权域名
在微信公众平台后台,“设置与开发” -> “网页授权获取用户基本信息”,配置你的授权回调页面域名。
2 实现授权流程
授权流程分为两步:snsapi_base(静默获取openId)和snsapi_userinfo(弹出授权,获取详细信息),这里以snsapi_base为例。
-
构造授权URL:
String oauthUrl = String.format( "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect", appId, URLEncoder.encode(yourRedirectUrl, "UTF-8") );将用户访问的链接重定向到这个
oauthUrl。 -
处理回调: 用户同意授权后,微信会重定向到你的
redirect_uri,并带上code参数,你用这个code换取access_token和openid。// 在你的Controller中添加处理回调的方法 @GetMapping("/oauth/callback") public String handleOAuthCallback(@RequestParam("code") String code) { String url = String.format( "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appId, appSecret, code ); // 使用HttpClient发送GET请求,获取包含openid的JSON // 解析JSON,得到openid // ... // openid就是你在这个公众号下的唯一用户标识 return "获取到用户OpenId: " + openid; }
第五步:调用其他核心API(如自定义菜单)
创建菜单、获取用户列表、发送模板消息等,都遵循类似的模式:
- 获取
access_token。 - 按照微信API文档构造请求体(通常是JSON)。
- 发送HTTP请求到对应API地址。
- 解析返回结果。
创建菜单:
// 在你的Controller或Service中
public String createMenu() {
String accessToken = accessTokenService.getAccessToken();
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;
// 按照微信API文档构造菜单的JSON结构
String menuJson = "{...}"; // 这里是一个复杂的JSON字符串
// 发送POST请求
// ...
}
详细的API请参考 微信公众平台官方文档。
第六步:项目结构与最佳实践
一个良好的项目结构能让你的代码更清晰、更易维护。
wechat-demo/
├── src/main/java/
│ └── com/example/wechatdemo/
│ ├── config/ // 配置类
│ ├── controller/ // 控制器
│ │ └── WeChatController.java
│ ├── message/ // 消息实体类
│ │ ├── BaseMessage.java
│ │ ├── TextMessage.java
│ │ └── NewsMessage.java
│ ├── service/ // 业务逻辑层
│ │ ├── AccessTokenService.java
│ │ └── MenuService.java
│ ├── util/ // 工具类
│ │ └── MessageUtil.java
│ └── WechatDemoApplication.java
├── src/main/resources/
│ ├── application.properties // 配置文件
│ └── static/
│ └── templates/
└── pom.xml
最佳实践:
- 配置管理:所有敏感信息(AppID, Secret, Token)都放在
application.properties中,并通过@Value注入。 - 异常处理:添加全局异常处理器,统一处理API调用失败等异常情况。
- 日志记录:使用SLF4J+Logback记录关键操作和错误信息,方便排查问题。
- 代码复用:将HTTP请求、JSON解析等逻辑封装到专门的Service或工具类中。
- 版本控制:将代码托管到Git等版本控制系统中。
进阶与总结
- 消息加解密:如果选择了“安全模式”或“兼容模式”,你需要实现消息的加解密逻辑,微信提供了多种语言的示例库,Java可以使用 WxJava 等开源框架,它们已经封装好了这些复杂逻辑。
- 使用WxJava框架:对于生产环境,强烈推荐使用成熟的第三方框架,如 WxJava,它集成了微信几乎所有API,封装了消息加解密、模板消息、JSSDK、小程序等几乎所有功能,能极大提高开发效率,避免重复造轮子和踩坑。
- 部署上线:开发完成后,将你的应用打包成jar或war包,部署到云服务器(如阿里云、腾讯云)上,并配置好域名解析,确保服务稳定运行。
本教程为你提供了一个从零开始开发公众号的完整路径,掌握了这些基础后,你就可以根据业务需求,不断探索和调用微信提供的更多强大API,构建出功能丰富的公众号应用。
