核心原理:微信服务器与我们的服务器如何通信?
微信公众平台开发的核心是 服务器配置 和 消息交互。

-
服务器配置:
- 你需要一台公网可访问的服务器(可以是云服务器如阿里云、腾讯云,也可以是内网穿透工具如
ngrok)。 - 在微信公众平台后台(
mp.weixin.qq.com)的“开发” -> “基本配置”中,配置你的服务器信息。 - URL:你的服务器上接收微信消息的接口地址,
http://yourdomain.com/wechat。 - Token:你自定义的字符串,用于验证请求是否来自微信服务器。
- EncodingAESKey:用于消息加解密的密钥(可选,安全起见建议开启)。
- 你需要一台公网可访问的服务器(可以是云服务器如阿里云、腾讯云,也可以是内网穿透工具如
-
消息交互流程:
- 验证阶段(首次配置):微信服务器会向你的
URL发送一个GET请求,携带signature,timestamp,nonce,echostr四个参数,你的服务器需要根据Token、timestamp、nonce进行加密(SHA1),并与signature对比,如果一致,说明请求合法,需要将echostr原样返回。 - 消息接收阶段:验证通过后,当用户在公众号里发送消息、点击菜单、关注/取关等事件发生时,微信服务器会向你的
URL发送一个POST请求,请求体中包含 XML 格式的消息数据。 - 消息回复阶段:你的服务器解析 XML 消息,根据业务逻辑生成要回复的 XML 格式消息,通过 HTTP 响应返回给微信服务器,微信服务器再将此消息推送给用户。
- 验证阶段(首次配置):微信服务器会向你的
项目结构与技术选型
一个典型的 Java 微信公众号项目结构如下:
wechat-demo/
├── pom.xml <!-- Maven 依赖管理文件 -->
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── config/
│ │ │ │ └── WeChatConfig.java <!-- 微信配置信息(Token, AppID等) -->
│ │ │ ├── controller/
│ │ │ │ └── WeChatController.java <!-- 核心控制器,处理所有微信请求 -->
│ │ │ ├── service/
│ │ │ │ └── WeChatService.java <!-- 业务逻辑处理层 -->
│ │ │ └── utils/
│ │ │ ├── SHA1Util.java <!-- SHA1 加密工具类 -->
│ │ │ └── XmlUtils.java <!-- XML 解析工具类 -->
│ │ └── resources/
│ │ └── application.properties <!-- 配置文件,存放敏感信息 -->
└── README.md
技术选型:

- 框架:Spring Boot (简化开发,快速构建项目)
- Web 容器:Spring Boot 内嵌 Tomcat
- XML 解析:Dom4J 或 JDK 自带的
javax.xml - 加密:JDK 自带的
java.security.MessageDigest
核心代码实现
pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version> <!-- 使用较新的稳定版本 -->
</parent>
<groupId>com.example</groupId>
<artifactId>wechat-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dom4J 用于 XML 解析 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Lombok (可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties 配置文件
# 服务器端口 server.port=8080 # 微信公众号配置 wechat.token=your_wechat_token wechat.appid=your_app_id wechat.secret=your_app_secret wechat.encodingaeskey=your_encoding_aes_key
WeChatConfig.java 配置类
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "wechat")
public class WeChatConfig {
private String token;
private String appid;
private String secret;
private String encodingaeskey;
// Getters and Setters
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getAppid() { return appid; }
public void setAppid(String appid) { this.appid = appid; }
public String getSecret() { return secret; }
public void setSecret(String secret) { this.secret = secret; }
public String getEncodingaeskey() { return encodingaeskey; }
public void setEncodingaeskey(String encodingaeskey) { this.encodingaeskey = encodingaeskey; }
}
SHA1Util.java 加密工具类
package com.example.utils;
import java.security.MessageDigest;
import java.util.Arrays;
public class SHA1Util {
public static String getSHA1(String token, String timestamp, String nonce, String echostr) {
String[] arr = new String[]{token, timestamp, nonce, echostr};
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (String s : arr) {
sb.append(s);
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hexStr = new StringBuilder();
for (byte b : digest) {
String shaHex = Integer.toHexString(b & 0xFF);
if (shaHex.length() < 2) {
hexStr.append(0);
}
hexStr.append(shaHex);
}
return hexStr.toString();
} catch (Exception e) {
throw new RuntimeException("SHA1加密失败", e);
}
}
}
WeChatController.java 核心控制器
这是整个项目的核心,负责处理所有来自微信的请求。
package com.example.controller;
import com.example.config.WeChatConfig;
import com.example.utils.SHA1Util;
import com.example.utils.XmlUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/wechat")
public class WeChatController {
@Autowired
private WeChatConfig weChatConfig;
/**
* 处理微信服务器的验证请求(GET)
*/
@GetMapping
public String validate(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
// 1. 将token, timestamp, nonce三个参数进行字典序排序
// 2. 将三个参数字符串拼接成一个字符串进行SHA1加密
// 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
String mySignature = SHA1Util.getSHA1(weChatConfig.getToken(), timestamp, nonce, "");
if (mySignature.equals(signature)) {
return echostr; // 验证成功,返回echostr
}
return "Invalid Request";
}
/**
* 处理用户发送的消息和事件(POST)
*/
@PostMapping
public String handlePost(@RequestBody String requestBody) {
try {
// 1. 解析XML消息
Document document = XmlUtils.readXml(requestBody);
Element root = document.getRootElement();
String msgType = root.element("MsgType").getStringValue();
String fromUserName = root.element("FromUserName").getStringValue();
String toUserName = root.element("ToUserName").getStringValue();
// 2. 根据消息类型处理
String replyContent = "";
if ("text".equals(msgType)) {
// 文本消息
String content = root.element("Content").getStringValue();
replyContent = "你发送的是文本消息: " + content;
} else if ("event".equals(msgType)) {
// 事件消息
String event = root.element("Event").getStringValue();
if ("subscribe".equals(event)) {
// 关注事件
replyContent = "感谢关注!";
} else if ("unsubscribe".equals(event)) {
// 取消关注事件
replyContent = ""; // 通常不回复
}
}
// 其他消息类型(图片、语音等)可以在这里扩展
// 3. 构造回复消息
return buildReplyXml(toUserName, fromUserName, replyContent);
} catch (DocumentException e) {
e.printStackTrace();
return "Error parsing XML";
}
}
/**
* 构造回复消息的XML
*/
private String buildReplyXml(String toUserName, String fromUserName, String content) {
return "<xml>" +
"<ToUserName><![CDATA[" + toUserName + "]]></ToUserName>" +
"<FromUserName><![CDATA[" + fromUserName + "]]></FromUserName>" +
"<CreateTime>" + System.currentTimeMillis() / 1000 + "</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[" + content + "]]></Content>" +
"</xml>";
}
}
XmlUtils.java XML 解析工具类
package com.example.utils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4.io.SAXReader;
import java.io.StringReader;
public class XmlUtils {
public static Document readXml(String xmlStr) throws DocumentException {
SAXReader reader = new SAXReader();
return reader.read(new StringReader(xmlStr));
}
// 如果需要将Map转为XML,可以添加此方法
public static String mapToXml(Map<String, String> map, String rootName) {
StringBuilder sb = new StringBuilder();
sb.append("<").append(rootName).append(">");
for (Map.Entry<String, String> entry : map.entrySet()) {
sb.append("<").append(entry.getKey()).append(">")
.append("<![CDATA[").append(entry.getValue()).append("]]>")
.append("</").append(entry.getKey()).append(">");
}
sb.append("</").append(rootName).append(">");
return sb.toString();
}
}
如何运行和部署
-
本地运行:
- 将上述代码文件放入对应目录。
- 确保
application.properties中的wechat.token与你在微信后台设置的Token一致。 - 运行
WeChatDemoApplication.java(Spring Boot 启动类)。 - 你的服务将在
http://localhost:8080/wechat启动。
-
内网穿透(用于本地测试):
- 由于微信服务器无法访问你的
localhost,你需要使用内网穿透工具,如ngrok。 - 下载并安装
ngrok。 - 在终端运行命令:
ngrok http 8080。 ngrok会为你提供一个公网 URL,http://a1b2c3d4.ngrok.io。- 将这个 URL(加上
/wechat路径)填写到微信公众平台后台的URL字段中,Token填写你的。
- 由于微信服务器无法访问你的
-
部署到云服务器:
(图片来源网络,侵删)- 购买一台云服务器(如阿里云ECS、腾讯云CVM)。
- 安装 Java 运行环境 和 Maven。
- 将你的 Spring Boot 项目打包成 JAR 文件:
mvn clean package。 - 将生成的
wechat-demo-0.0.1-SNAPSHOT.jar文件上传到云服务器。 - 在服务器上运行 JAR:
java -jar wechat-demo-0.0.1-SNAPSHOT.jar。 - 配置服务器的防火墙(安全组),开放 8080 端口。
- 将云服务器的公网 IP 和端口(如
http://your_server_ip:8080/wechat)填写到微信后台。
进阶功能
- 接收和回复图片、图文等多媒体消息:解析对应类型的 XML,并构造相应类型的回复 XML。
- 自定义菜单创建:通过调用微信提供的
POST接口(需要access_token)来创建菜单。access_token需要通过你的AppID和Secret调用微信接口获取。 - 模板消息:用于发送重要的服务通知,如订单确认、物流更新等。
- 网页授权获取用户信息:当用户在公众号内点击链接时,可以通过 OAuth2.0 授权获取用户的
openid和基本信息。 - 安全与加密:如果开启了消息加解密,你需要实现
AES-256-CBC的加解密逻辑来处理所有收发消息。
这份指南和源码为您提供了一个坚实的基础,您可以根据这个框架,逐步添加更复杂的业务逻辑,构建功能完善的微信公众号应用。
