目录
- 准备工作:你需要什么?
- 第一步:获取公众号测试账号
- 第二步:开发环境搭建
- 第三步:配置与接入(最关键的一步)
- 第四步:接收和发送消息
- 1 接收用户发送的文本消息
- 2 发送回复文本消息
- 3 发送回复图文消息
- 第五步:常用API调用
- 1 获取Access Token
- 2 菜单管理
- 第六步:部署上线
- 进阶学习与资源
准备工作:你需要什么?
在开始之前,请确保你已经具备以下条件:

- Java基础:熟悉Java语言、Spring Boot框架、Maven等。
- 开发工具:
- IDE:IntelliJ IDEA 或 Eclipse。
- API调试工具:Postman 或 Apifox。
- 服务器:一台可以公网访问的服务器(Linux推荐),用于部署你的应用并接收微信服务器的回调,如果没有,可以使用内网穿透工具(如
ngrok)进行本地调试。 - 域名:一个备案的域名,用于服务器配置。
第一步:获取公众号测试账号
为了避免影响正式的公众号,我们强烈建议使用测试公众号来进行开发。
- 访问微信公众平台:https://mp.weixin.qq.com/
- 使用你的个人微信扫描登录。
- 在左侧菜单栏找到 “开发” -> “接口测试账号”。
- 进入测试账号页面,你将获得:
- AppID (开发者ID):相当于你的公众号的身份证。
- AppSecret (开发者密码):相当于你的密码,请妥善保管。
- 测试二维码:用于关注你的测试号,模拟用户交互。
- 网页授权获取用户基本信息:用于开发网页应用,本教程暂不涉及。
第二步:开发环境搭建
我们将使用Spring Boot快速搭建一个Web项目。
-
创建Spring Boot项目
- 访问 Spring Initializr。
- Project: Maven Project
- Language: Java
- Spring Boot: 选择一个稳定版本 (如 2.7.x 或 3.x.x)
- Project Metadata:
- Group:
com.example - Artifact:
wechat-demo - Name:
wechat-demo - Packaging: Jar
- Java: 8 或更高版本
- Group:
- Dependencies: 添加以下依赖
Spring Web: 用于构建Web应用。Thymeleaf: 用于模板引擎(可选,但方便开发)。Lombok: 简化Java代码(可选,但强烈推荐)。
- 点击 "Generate" 下载项目压缩包,并用IDE打开。
-
项目结构 你的项目结构应该大致如下:
(图片来源网络,侵删)wechat-demo ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── example │ │ │ └── wechatdemo │ │ │ ├── WechatDemoApplication.java │ │ │ └── controller │ │ │ └── WeChatController.java // 我们将在这里处理微信消息 │ │ └── resources │ │ ├── application.properties // 配置文件 │ │ └── static │ │ └── templates └── pom.xml
第三步:配置与接入(最关键的一步)
这是微信开发的第一道关卡,目的是让微信服务器确认你的服务器是可靠的。
-
配置
application.properties在src/main/resources/application.properties文件中,填入你在测试号页面获取的信息。# 微信公众号配置 wechat.appId=你的AppID wechat.appSecret=你的AppSecret wechat.token=你的Token (可以自己定义,如 mytoken123) wechat.aesKey=你的EncodingAESKey (测试号页面会自动生成)
-
编写Controller处理接入请求 当用户在测试号后台点击“配置”时,微信服务器会向你的
http://你的服务器地址/wechat发送一个GET请求,并携带四个参数:signature,timestamp,nonce,echostr。你需要做的是:
- 将
token,timestamp,nonce三个参数进行字典序排序。 - 将三个参数字符串拼接成一个字符串进行
SHA1加密。 - 将加密后的字符串与
signature对比。 - 如果一致,说明请求来自微信,请返回
echostr参数内容。
创建
WeChatController.java:package com.example.wechatdemo.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; import java.util.List; @RestController public class WeChatController { @Value("${wechat.token}") private String token; /** * 处理微信服务器的验证请求 (GET) * @param signature 微信加密签名 * @param timestamp 时间戳 * @param nonce 随机数 * @param echostr 随机字符串 * @return */ @GetMapping("/wechat") public String verify(String signature, String timestamp, String nonce, String echostr) { System.out.println("收到验证请求..."); // 1. 将token, timestamp, nonce三个参数进行字典序排序 List<String> params = Arrays.asList(token, timestamp, nonce); Collections.sort(params); // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 StringBuilder sb = new StringBuilder(); for (String param : params) { sb.append(param); } String mySignature = sha1(sb.toString()); // 3. 开发者获得加密后的字符串可与signature对比 if (mySignature.equals(signature)) { // 验证成功,返回echostr return echostr; } else { // 验证失败 return "error"; } } /** * 处理用户发送的消息 (POST) * @param requestBody 微服务器发送的XML数据 * @return */ @PostMapping("/wechat") public String handleWechatMessage(@RequestBody String requestBody) { System.out.println("收到消息: " + requestBody); // TODO: 解析XML,并根据消息类型和内容进行回复 return "success"; // 先返回一个成功响应,避免微信重试 } /** * SHA1加密方法 */ private String sha1(String str) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] digest = md.digest(str.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } } - 将
-
本地调试与配置
- 启动Spring Boot应用:在IDE中运行
WechatDemoApplication.java。 - 使用内网穿透:由于你的本地电脑无法被公网访问,需要使用工具,推荐
ngrok。- 下载并安装
ngrok。 - 在终端运行命令:
ngrok http 8080(假设你的Spring Boot应用运行在8080端口)。 ngrok会给你一个公网URL,https://xxxx-8080.ap.ngrok.io。
- 下载并安装
- 配置测试号:
- 回到测试号页面,在“接口配置信息”部分:
- URL: 填入
https://xxxx-8080.ap.ngrok.io/wechat - Token: 填入你在
application.properties中设置的wechat.token(如mytoken123) - 消息加解密方式: 选择
安全模式(测试号默认) 或兼容模式,本教程先用安全模式。
- URL: 填入
- 点击“提交”。
- 如果配置正确,会提示“配置成功”。
- 回到测试号页面,在“接口配置信息”部分:
- 启动Spring Boot应用:在IDE中运行
第四步:接收和发送消息
我们来处理用户实际发送的消息。
1 接收用户发送的文本消息
用户在公众号里发送消息时,微信服务器会向你的 /wechat 接口发送一个 POST 请求,请求体是XML格式,我们需要解析这个XML。
创建一些Java类来映射XML结构。
MessageConstants.java (消息类型常量)
public class MessageConstants {
public static final String MSG_TYPE_TEXT = "text";
public static final String MSG_TYPE_IMAGE = "image";
public static final String MSG_TYPE_EVENT = "event";
// ... 其他消息类型
}
BaseMessage.java (基础消息实体)
import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@Data
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class BaseMessage {
@XmlElement
private String ToUserName; // 开发者微信号
@XmlElement
private String FromUserName; // 发送方帐号(一个OpenID)
@XmlElement
private long CreateTime; // 消息创建时间 (整型)
@XmlElement
private String MsgType; // 消息类型
}
TextMessage.java (文本消息实体)
import lombok.Data;
import javax.xml.bind.annotation.XmlElement;
@Data
public class TextMessage extends BaseMessage {
@XmlElement
private String Content; // 文本消息内容
}
修改 WeChatController 的 handleWechatMessage 方法来解析XML并回复。
2 发送回复文本消息
微信的回复消息也是XML格式,我们需要构建一个XML字符串返回给微信服务器。
修改后的 WeChatController.java (关键部分)
// ... imports ...
import com.example.wechatdemo.model.BaseMessage;
import com.example.wechatdemo.model.TextMessage;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.util.StringUtils;
import java.util.Date;
// ... class WeChatController ...
@PostMapping("/wechat")
public String handleWechatMessage(@RequestBody String requestBody) {
System.out.println("收到消息: " + requestBody);
try {
Document document = DocumentHelper.parseText(requestBody);
Element root = document.getRootElement();
String toUserName = root.elementText("ToUserName");
String fromUserName = root.elementText("FromUserName");
String msgType = root.elementText("MsgType");
String content = root.elementText("Content");
// 1. 解析消息
if (MessageConstants.MSG_TYPE_TEXT.equals(msgType)) {
// 用户发送的是文本消息
if ("你好".equals(content)) {
// 回复固定的文本
return buildTextResponse(toUserName, fromUserName, "你好,欢迎使用我的公众号!");
} else if ("文章".equals(content)) {
// 回复图文消息
return buildNewsResponse(toUserName, fromUserName);
}
}
// 其他消息类型处理...
} catch (Exception e) {
e.printStackTrace();
}
// 默认回复
return "success";
}
/**
* 构造回复文本消息的XML
*/
private String buildTextResponse(String toUser, String fromUser, String content) {
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUser);
textMessage.setFromUserName(toUser);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(MessageConstants.MSG_TYPE_TEXT);
textMessage.setContent(content);
return convertToXml(textMessage);
}
/**
* 构造回复图文消息的XML
*/
private String buildNewsResponse(String toUser, String fromUser) {
BaseMessage baseMessage = new BaseMessage();
baseMessage.setToUserName(fromUser);
baseMessage.setFromUserName(toUser);
baseMessage.setCreateTime(new Date().getTime());
baseMessage.setMsgType("news"); // 图文消息类型
// 这里简化处理,实际应该构建完整的图文消息XML
// 详细图文消息XML结构请参考微信官方文档
// 一个图文消息包含一个或多个 Article
String newsXml = "<ArticleCount>1</ArticleCount>" +
"<Articles>" +
"<item>" +
"<Title>一篇示例文章</Title>" +
"<Description>这是文章的描述。</Description>" +
"<PicUrl>https://your-image-url.com/image.jpg</PicUrl>" +
"<Url>https://your-article-url.com</Url>" +
"</item>" +
"</Articles>";
// 将图文部分拼接到基础消息中
String baseXml = convertToXml(baseMessage);
// 注意:实际拼接时,需要更严谨地处理XML结构,这里仅作演示
return baseXml.replace("</xml>", newsXml + "</xml>");
}
/**
* 对象转XML (使用Lombok的@ToString可以简化,但这里手动构建)
* 实际项目中建议使用Jackson或XStream等库进行XML转换
*/
private String convertToXml(Object object) {
// 这是一个简化的示例,实际项目中应使用更健壮的库,如Jackson XML或XStream
// 这里我们直接手动构建,因为消息结构相对固定
if (object instanceof TextMessage) {
TextMessage msg = (TextMessage) object;
return "<xml>" +
"<ToUserName><![CDATA[" + msg.getToUserName() + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + msg.getFromUserName() + "]]></FromUserName>" +
"<CreateTime>" + msg.getCreateTime() + "</CreateTime>" +
"<MsgType><![CDATA[" + msg.getMsgType() + "]]></MsgType>" +
"<Content><![CDATA[" + msg.getContent() + "]]></Content>" +
"</xml>";
}
return "<xml></xml>";
}
注意:手动拼接XML非常容易出错,在生产环境中,强烈建议使用 Jackson XML 或 XStream 等库来进行对象与XML之间的转换,你需要添加 jackson-dataformat-xml 依赖。
3 发送回复图文消息
图文消息比文本消息复杂,它包含一个或多个 Article,XML结构如下:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>2</ArticleCount>
<Articles>
<item>
<Title><![CDATA[title1]]></Title>
<Description><![CDATA[description1]]></Description>
<PicUrl><![CDATA[picurl1]]></PicUrl>
<Url><![CDATA[url1]]></Url>
</item>
<item>
<Title><![CDATA[title2]]></Title>
<Description><![CDATA[description2]]></Description>
<PicUrl><![CDATA[picurl2]]></PicUrl>
<Url><![CDATA[url2]]></Url>
</item>
</Articles>
</xml>
你可以创建 NewsMessage 和 Article 类,然后用Jackson XML库来轻松转换。
第五步:常用API调用
除了被动回复消息,我们还可以主动调用微信API,如获取用户信息、自定义菜单等。
调用任何API前,都需要一个全局唯一的凭证:Access Token。
1 获取Access Token
-
创建配置类
WeChatConfig.javapackage com.example.wechatdemo.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "wechat") public class WeChatConfig { private String appId; private String appSecret; // ... 其他配置 } -
创建Service
WeChatService.javapackage com.example.wechatdemo.service; import com.example.wechatdemo.config.WeChatConfig; com.fasterxml.jackson.databind.ObjectMapper; org.springframework.stereotype.Service; org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; @Service public class WeChatService { private final WeChatConfig weChatConfig; private final RestTemplate restTemplate; private final ObjectMapper objectMapper; // 缓存AccessToken,避免频繁请求 private String accessToken; private long expireTime; public WeChatService(WeChatConfig weChatConfig, RestTemplate restTemplate, ObjectMapper objectMapper) { this.weChatConfig = weChatConfig; this.restTemplate = restTemplate; this.objectMapper = objectMapper; } public String getAccessToken() { // 如果token未过期,直接返回 if (accessToken != null && System.currentTimeMillis() < expireTime) { return accessToken; } // 否则,重新获取 String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + weChatConfig.getAppId() + "&secret=" + weChatConfig.getAppSecret(); try { String response = restTemplate.getForObject(url, String.class); Map<String, Object> map = objectMapper.readValue(response, Map.class); accessToken = (String) map.get("access_token"); int expiresIn = (Integer) map.get("expires_in"); expireTime = System.currentTimeMillis() + (expiresIn - 200) * 1000L; // 提前200秒过期 return accessToken; } catch (Exception e) { // 处理异常 return null; } } }别忘了在启动类上添加
@EnableConfigurationProperties(WeChatConfig.class)和配置RestTemplateBean。
2 菜单管理
创建自定义菜单也是通过API调用实现的。
-
创建菜单实体类 (
MenuButton.java,Menu.java等),对应微信的菜单JSON结构。 -
在
WeChatService中添加创建菜单的方法:// 在 WeChatService 中 public boolean createMenu(String menuJson) { String accessToken = getAccessToken(); if (accessToken == null) { return false; } String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken; try { // 使用 restTemplate 发送 POST 请求,body为menuJson restTemplate.postForEntity(url, menuJson, String.class); return true; } catch (Exception e) { e.printStackTrace(); return false; } } -
在Controller中调用:
// 在 WeChatController 中 @Autowired private WeChatService weChatService; @GetMapping("/createMenu") public String createMenu() { // 这是一个示例菜单的JSON String menuJson = "{\"button\":[{\"type\":\"click\",\"name\":\"今日歌曲\",\"key\":\"V1001_TODAY_MUSIC\"},{\"name\":\"菜单\",\"sub_button\":[{\"type\":\"view\",\"name\":\"搜索\",\"url\":\"http://www.soso.com/\"},{\"type\":\"view\",\"name\":\"视频\",\"url\":\"http://v.qq.com/\"},{\"type\":\"click\",\"name\":\"赞一下我们\",\"key\":\"V1001_GOOD\"}]}]}"; boolean result = weChatService.createMenu(menuJson); return result ? "菜单创建成功" : "菜单创建失败"; }访问
http://你的服务器地址/createMenu即可创建菜单,创建成功后,去测试号页面刷新就能看到菜单了。
第六步:部署上线
当本地开发测试完成后,你需要将应用部署到公网服务器上。
- 打包应用:在项目根目录执行
mvn clean package,会生成一个.jar文件。 - 上传服务器:将
.jar文件上传到你的Linux服务器。 - 运行应用:
- 确保服务器已安装Java运行环境。
- 使用
nohup java -jar wechat-demo.jar > wechat.log 2>&1 &命令在后台运行应用。 nohup表示让命令持续运行,即使你关闭了终端。wechat.log是日志输出文件。
- 配置Nginx (可选但推荐):
- 使用Nginx作为反向代理,将
http://yourdomain.com的请求转发到你的Spring Boot应用端口(如8080)。 - 同时可以配置SSL证书,实现HTTPS访问(微信要求回调URL必须是
HTTPS协议)。
- 使用Nginx作为反向代理,将
- 修改测试号配置:
- 将测试号的URL从
ngrok的地址改为你的正式域名地址(如https://yourdomain.com/wechat)。 - 重新提交。
- 将测试号的URL从
进阶学习与资源
- 官方文档:微信公众平台开发者文档,这是最重要的资料,没有之一。
- 安全模式与加密解密:当选择“安全模式”时,所有收发消息都是加密的,你需要实现AES解密和SHA1签名验证,官方文档有详细的加解密算法示例。
- 网页授权:如果你的应用需要获取用户信息(如OpenID),需要学习OAuth2.0网页授权流程。
- 模板消息:用于发送重要的服务通知,如订单确认、物流更新等。
- JFinal、JeecgBoot等框架:这些框架也提供了非常成熟的微信开发插件,可以大大简化开发流程。
这份教程为你铺平了微信公众号Java开发的基本道路,从简单的消息回复到复杂的API调用,每一步都需要仔细阅读官方文档并动手实践,祝你开发顺利!
