杰瑞科技汇

Java如何开发微信公众号?

目录

  1. 准备工作:公众号与开发环境
    • 1 注册并配置公众号
    • 2 准备开发环境
  2. 核心概念:通信原理与消息格式
    • 1 服务器配置与 Token 验证
    • 2 消息推送机制
    • 3 消息类型(XML 格式)
  3. 实战:搭建 Java 后端服务
    • 1 创建 Spring Boot 项目
    • 2 接入公众号(处理 Token 验证)
    • 3 接收并解析用户消息
    • 4 回复用户消息
  4. 进阶功能开发
    • 1 获取 Access Token
    • 2 菜单管理
    • 3 发送模板消息
  5. 开发工具与调试
    • 1 微信开发者工具
    • 2 本地开发与穿透
  6. 完整代码示例
  7. 学习资源与官方文档

准备工作:公众号与开发环境

在开始编码之前,你需要完成以下准备工作。

Java如何开发微信公众号?-图1
(图片来源网络,侵删)

1 注册并配置公众号

  1. 注册公众号

    • 访问 微信公众平台
    • 根据你的需求选择注册类型:
      • 订阅号:适合个人或媒体,主要用于信息发布。
      • 服务号:适合企业和组织,功能更强大(如微信支付、高级接口等)。
    • 按照指引完成注册,并完成认证(认证后可获得更多接口权限)。
  2. 获取开发者凭证

    • 登录公众号后台,在 “设置与开发” -> “基本配置” 中找到开发者凭证。
    • 你需要记录下 AppID (应用ID)AppSecret (应用密钥),这两个是调用公众号 API 的钥匙。
  3. 获取服务器配置信息

    • “设置与开发” -> “基本配置” 中,找到 “服务器配置”
    • 这里需要你填写:
      • URL (服务器地址):你的 Java Web 服务的访问地址,必须以 http://https:// 开头,并且能被外网访问。http://yourdomain.com/wechat
      • Token (令牌):可以任意填写,用于验证请求是否来自微信。myWechatToken
      • EncodingAESKey (消息加解密密钥):可以随机生成,用于消息的加解密,初期开发可以先留空。

2 准备开发环境

  • JDK:建议使用 JDK 8 或更高版本。
  • IDE:IntelliJ IDEA 或 Eclipse。
  • 构建工具:Maven 或 Gradle。
  • Web 框架:推荐使用 Spring Boot,它能极大地简化 Web 应用的开发,本教程将基于 Spring Boot 2.x/3.x。

核心概念:通信原理与消息格式

理解微信和你的服务器是如何通信的,是开发的第一步。

Java如何开发微信公众号?-图2
(图片来源网络,侵删)

1 服务器配置与 Token 验证

当你填写完服务器配置并点击“提交”时,微信服务器会向你的 URL 发送一个 GET 请求,用于验证你的服务器所有权,请求参数如下:

参数 描述
signature 微信加密签名,结合了 token、timestamp、nonce
timestamp 时间戳
nonce 随机数
echostr 随机字符串

验证逻辑: 你的服务器需要做以下几件事:

  1. 获取 token(就是你填写的那个)、timestampnonce
  2. 将这三个参数进行字典序排序。
  3. 将排序后的三个参数字符串拼接成一个字符串,并进行 SHA1 加密。
  4. 将加密后的字符串与 signature 进行比较。
  5. 如果一致,则说明请求来自微信,你需要将 echostr 原样返回,如果不一致,则返回错误。

2 消息推送机制

当用户与公众号进行交互(如发送消息、点击菜单等)时,微信服务器会通过一个 POST 请求,将消息数据发送到你配置的 URL

  • 请求方式POST
  • 请求体,格式为 XML
  • Content-Typetext/xml; charset=UTF-8

你的服务器需要解析这个 XML 请求,根据消息类型(文本、图片、事件等)进行处理,然后将处理结果以 XML 格式 响应给微信服务器,微信服务器再将你的响应推送给用户。

Java如何开发微信公众号?-图3
(图片来源网络,侵删)

3 消息类型(XML 格式)

微信推送的消息有多种类型,每种类型都有其特定的 XML 结构。

文本消息 用户发送文本时,微信推送的结构如下:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>

事件推送 当用户关注、取消关注、点击菜单时,会收到事件推送,用户关注时:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
</xml>

回复消息 当你回复用户时,也需要遵循 XML 格式,回复文本消息:

<xml>
  <ToUserName><![CDATA[fromUser]]></ToUserName>
  <FromUserName><![CDATA[toUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好,欢迎关注!]]></Content>
</xml>

实战:搭建 Java 后端服务

我们开始用 Java 和 Spring Boot 来实现上述逻辑。

1 创建 Spring Boot 项目

使用 Spring Initializr 快速创建一个项目。

  • Project: Maven
  • Language: Java
  • Spring Boot: 选择一个稳定版本 (如 2.7.x)
  • Project Metadata:
    • Group: com.example
    • Artifact: wechat-demo
  • Dependencies:
    • Spring Web: 用于创建 Web 应用和 RESTful 接口。

下载项目并用你的 IDE 打开。

2 接入公众号(处理 Token 验证)

创建一个 Controller 来处理来自微信的请求。

WeChatController.java

package com.example.wechatdemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@RestController
public class WeChatController {
    // 在公众号后台配置的 Token
    private final String TOKEN = "myWechatToken";
    /**
     * 处理微信服务器的 GET 请求(Token 验证)
     */
    @GetMapping("/wechat")
    public String validate(HttpServletRequest request, HttpServletResponse response) {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        // 1. 将 token, timestamp, nonce 三个参数进行字典序排序
        String[] arr = {TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        // 2. 将三个参数字符串拼接成一个字符串
        String content = arr[0] + arr[1] + arr[2];
        // 3. 对拼接后的字符串进行 SHA1 加密
        String tmpStr = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.getBytes());
            tmpStr = bytesToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        // 4. 将加密后的字符串与 signature 对比
        if (tmpStr != null && tmpStr.equals(signature)) {
            // 验证成功,原样返回 echostr
            return echostr;
        } else {
            return "error";
        }
    }
    // 字节数组转十六进制字符串
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    // ... 后续会添加处理 POST 请求的方法
}

测试: 将你的项目运行起来,获取到外网可访问的地址(可以使用 ngrok 等工具进行内网穿透),将这个地址和你在 1.1 中设置的 TOKEN 填入公众号后台的“服务器配置”中,点击“提交”,如果配置成功,说明你的服务器已经成功接入公众号。

3 接收并解析用户消息

现在我们来处理用户发送的 POST 请求,由于请求是 XML 格式,我们需要一个 XML 解析库,这里推荐使用 dom4j

添加依赖pom.xml 中添加 dom4j

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.4</version>
</dependency>

创建消息实体类 为了方便处理 XML,我们先创建一些 Java 类来映射 XML 结构。

BaseMessage.java (基类)

package com.example.wechatdemo.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class BaseMessage {
    protected String ToUserName;
    protected String FromUserName;
    protected long CreateTime;
    protected String MsgType;
    // Getters and Setters
    public String getToUserName() { return ToUserName; }
    public void setToUserName(String toUserName) { ToUserName = toUserName; }
    public String getFromUserName() { return FromUserName; }
    public void setFromUserName(String fromUserName) { FromUserName = fromUserName; }
    public long getCreateTime() { return CreateTime; }
    public void setCreateTime(long createTime) { CreateTime = createTime; }
    public String getMsgType() { return MsgType; }
    public void setMsgType(String msgType) { MsgType = msgType; }
}

TextMessage.java (文本消息)

package com.example.wechatdemo.model;
public class TextMessage extends BaseMessage {
    private String Content;
    // Getter and Setter
    public String getContent() { return Content; }
    public void setContent(String content) { Content = content; }
}

EventMessage.java (事件消息)

package com.example.wechatdemo.model;
public class EventMessage extends BaseMessage {
    private String Event;
    // Getter and Setter
    public String getEvent() { return Event; }
    public void setEvent(String event) { Event = event; }
}

解析 XML 并处理 修改 WeChatController,添加 POST 方法。

WeChatController.java (更新)

package com.example.wechatdemo.controller;
// ... imports ...
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.wechatdemo.model.TextMessage;
import com.example.wechatdemo.model.EventMessage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@RestController
public class WeChatController {
    // ... 前面的 GET 方法保持不变 ...
    /**
     * 处理微信服务器的 POST 请求(接收用户消息)
     */
    @PostMapping("/wechat")
    public String handlePost(HttpServletRequest request, HttpServletResponse response) throws IOException, DocumentException {
        // 1. 获取请求输入流(XML 格式)
        InputStream is = request.getInputStream();
        // 2. 解析 XML
        SAXReader reader = new SAXReader();
        Document document = reader.read(is);
        Element root = document.getRootElement();
        // 3. 获取消息类型
        String msgType = root.element("MsgType").getTextTrim();
        // 4. 根据消息类型进行处理
        if ("text".equals(msgType)) {
            // 处理文本消息
            TextMessage textMessage = parseTextMessage(root);
            return handleTextMessage(textMessage);
        } else if ("event".equals(msgType)) {
            // 处理事件消息
            EventMessage eventMessage = parseEventMessage(root);
            return handleEventMessage(eventMessage);
        }
        return ""; // 其他消息类型暂时不处理
    }
    // 解析文本消息
    private TextMessage parseTextMessage(Element root) {
        TextMessage msg = new TextMessage();
        msg.setToUserName(root.element("ToUserName").getTextTrim());
        msg.setFromUserName(root.element("FromUserName").getTextTrim());
        msg.setCreateTime(Long.parseLong(root.element("CreateTime").getTextTrim()));
        msg.setMsgType(root.element("MsgType").getTextTrim());
        msg.setContent(root.element("Content").getTextTrim());
        return msg;
    }
    // 解析事件消息
    private EventMessage parseEventMessage(Element root) {
        EventMessage msg = new EventMessage();
        msg.setToUserName(root.element("ToUserName").getTextTrim());
        msg.setFromUserName(root.element("FromUserName").getTextTrim());
        msg.setCreateTime(Long.parseLong(root.element("CreateTime").getTextTrim()));
        msg.setMsgType(root.element("MsgType").getTextTrim());
        msg.setEvent(root.element("Event").getTextTrim());
        return msg;
    }
    // 处理文本消息并返回回复
    private String handleTextMessage(TextMessage receivedMsg) {
        // 1. 构造回复消息
        TextMessage replyMsg = new TextMessage();
        replyMsg.setToUserName(receivedMsg.getFromUserName()); // 发送方和接收方互换
        replyMsg.setFromUserName(receivedMsg.getToUserName());
        replyMsg.setCreateTime(System.currentTimeMillis() / 1000);
        replyMsg.setMsgType("text");
        // 2. 设置回复内容(简单 echo)
        String content = "你发送的是: " + receivedMsg.getContent();
        replyMsg.setContent(content);
        // 3. 将对象转换为 XML 字符串
        return convertToXml(replyMsg);
    }
    // 处理事件消息
    private String handleEventMessage(EventMessage eventMsg) {
        if ("subscribe".equals(eventMsg.getEvent())) {
            // 用户关注事件
            TextMessage replyMsg = new TextMessage();
            replyMsg.setToUserName(eventMsg.getFromUserName());
            replyMsg.setFromUserName(eventMsg.getToUserName());
            replyMsg.setCreateTime(System.currentTimeMillis() / 1000);
            replyMsg.setMsgType("text");
            replyMsg.setContent("感谢关注!这是一个 Java 开发的测试公众号。");
            return convertToXml(replyMsg);
        }
        return "";
    }
    // 将消息对象转换为 XML 字符串(简化版,实际项目中建议使用 JAXB)
    private String convertToXml(BaseMessage message) {
        // 这里为了简化,直接拼接字符串,生产环境请使用 JAXB 或其他 XML 绑定库。
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        sb.append("<ToUserName><![CDATA[").append(message.getToUserName()).append("]]></ToUserName>");
        sb.append("<FromUserName><![CDATA[").append(message.getFromUserName()).append("]]></FromUserName>");
        sb.append("<CreateTime>").append(message.getCreateTime()).append("</CreateTime>");
        sb.append("<MsgType><![CDATA[").append(message.getMsgType()).append("]]></MsgType>");
        if (message instanceof TextMessage) {
            sb.append("<Content><![CDATA[").append(((TextMessage) message).getContent()).append("]]></Content>");
        }
        sb.append("</xml>");
        return sb.toString();
    }
}

你的公众号已经可以接收文本消息并回复,并且在用户关注时自动欢迎了。


进阶功能开发

1 获取 Access Token

调用绝大多数公众号 API(如创建菜单、发送模板消息)都需要一个全局唯一的接口调用凭据——access_token

  • 获取方式:通过 GET 请求 https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
  • 特点
    • 有效期为 2 小时(7200秒)。
    • 每天获取次数有限。
    • 必须全局缓存,不能每次调用都去请求,否则会触发频率限制。

实现一个 Access Token 服务

AccessTokenService.java

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 org.springframework.web.client.RestTemplate;
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;
    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    public AccessTokenService(RestTemplate restTemplate, ObjectMapper objectMapper) {
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }
    public String getAccessToken() {
        // token 为空或已过期,则重新获取
        if (accessToken == null || System.currentTimeMillis() > expireTime) {
            String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret);
            String response = restTemplate.getForObject(url, String.class);
            try {
                JsonNode root = objectMapper.readTree(response);
                this.accessToken = root.get("access_token").asText();
                // 设置过期时间(提前 5 分钟刷新)
                this.expireTime = System.currentTimeMillis() + (root.get("expires_in").asLong() - 300) * 1000;
            } catch (Exception e) {
                // 处理异常
                e.printStackTrace();
            }
        }
        return accessToken;
    }
}

application.properties 中配置 AppID 和 Secret:

wechat.appid=你的AppID
wechat.secret=你的AppSecret

2 菜单管理

通过 API 可以自定义公众号的菜单。

创建菜单按钮的 JSON 结构

{
  "button": [
    {
      "type": "click",
      "name": "今日歌曲",
      "key": "V1001_TODAY_MUSIC"
    },
    {
      "name": "菜单",
      "sub_button": [
        {
          "type": "view",
          "name": "搜索",
          "url": "http://www.soso.com/"
        },
        {
          "type": "click",
          "name": "赞一下我们",
          "key": "V1001_GOOD"
        }
      ]
    }
  ]
}

调用 API 创建菜单AccessTokenService 中添加方法:

public void createMenu(String menuJson) {
    String url = String.format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s", getAccessToken());
    restTemplate.postForObject(url, menuJson, String.class);
}

然后你可以在你的业务逻辑中调用这个方法来创建菜单。

3 发送模板消息

模板消息用于在用户完成特定操作后,向用户发送重要通知。

获取模板 ID 在公众号后台“模板消息”中找到你需要的模板,并记录其 ID。

调用 API 发送 发送模板消息需要接收用户的 openid

public void sendTemplateMessage(String openid, String templateId, String url, Map<String, String> data) {
    String accessToken = getAccessToken();
    String requestUrl = String.format("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s", accessToken);
    // 构建请求 JSON
    Map<String, Object> message = new HashMap<>();
    message.put("touser", openid);
    message.put("template_id", templateId);
    if (url != null) {
        message.put("url", url);
    }
    message.put("data", data);
    // 发送请求
    restTemplate.postForObject(requestUrl, message, String.class);
}

开发工具与调试

1 微信开发者工具

官方提供的桌面工具,非常强大。

  • 模拟器:可以模拟手机界面,查看你的公众号效果。
  • 调试:可以查看网络请求、Console 日志,方便排查问题。
  • 素材管理:可以预览图片、视频等素材。

2 本地开发与穿透

在开发阶段,你的本地电脑通常无法被外网访问,这时需要使用内网穿透工具,将本地的端口映射到一个公网 URL。

  • 推荐工具ngrokfrp
  • 使用方法:下载并运行,它会给你一个类似 http://xxxx.ngrok.io 的地址,将这个地址作为你的服务器 URL 进行配置即可。

完整代码示例

本教程的完整代码已托管在 GitHub 上,你可以参考它来更好地理解: https://github.com/your-github-username/wechat-java-demo (请替换为你的实际地址)


学习资源与官方文档

  • 微信公众平台技术文档这是最重要的资源,所有 API 规则和消息格式都在这里。
  • Java 微信 SDK:为了避免重复造轮子,可以考虑使用成熟的第三方 SDK,它们封装了大量的 API 和消息处理逻辑。
    • me.chanjar:weixin-java-mp: 一个非常流行和功能全面的 Java 微信公众号 SDK。
    • WxJava: 另一个优秀的、支持微信所有平台(公众号、小程序、企业微信等)的 Java 开发框架。

微信公众号开发的核心在于:

  1. 理解通信流程:GET 验证,POST 交互。
  2. 处理 XML 消息:能正确解析和构造微信的 XML 格式数据。
  3. 善用 Access Token:缓存它,避免频繁请求。
  4. 参考官方文档:遇到任何 API 问题,第一时间查阅官方文档。

希望这份教程能帮助你顺利开启 Java 微信公众号开发之旅!祝你编码愉快!

分享:
扫描分享到社交APP
上一篇
下一篇