杰瑞科技汇

Java如何开发微信公众平台?

微信公众平台Java开发完整教程

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

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

目录

  1. 准备工作:你需要什么?
  2. 第一步:接入微信公众平台(服务器验证)
  3. 第二步:接收和回复用户消息
    • 1 消息类型解析
    • 2 回复文本消息
    • 3 回复图文消息
  4. 第三步:主动发送消息(客服接口)
  5. 第四步:网页授权获取用户信息
  6. 第五步:调用其他核心API(如自定义菜单)
  7. 第六步:项目结构与最佳实践
  8. 进阶与总结

准备工作:你需要什么?

在开始编码之前,请确保你已经具备以下条件:

  • 一个Java开发环境:JDK 8或更高版本。

  • 一个IDE:如IntelliJ IDEA或Eclipse。

  • Maven:用于项目管理,方便引入依赖。

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

    • 访问 微信公众平台
    • 注册一个 服务号(个人只能订阅号,功能受限,企业可申请服务号)。
    • 登录后,进入“开发” -> “基本配置”。
    • 关键信息
      • AppID (应用ID):公众号的唯一标识。
      • AppSecret (应用密钥)非常重要,请妥善保管,不要泄露!
      • 服务器配置 (URL, Token, EncodingAESKey):这是我们开发的核心配置。
  • 一个外网可访问的服务器:微信公众平台的服务器验证和消息推送都需要你的服务有一个公网IP和域名,本地开发可以使用 内网穿透工具,如 ngrokfrp 等。


第一步:接入微信公众平台(服务器验证)

这是开发的第一步,也是最重要的一步,微信服务器会向你配置的URL发送一个GET请求,以验证你服务器的所有权。

1 验证原理

微信服务器会发送如下参数到你的URL:

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

你需要做的是:

  1. tokentimestampnonce 三个参数进行字典序排序。
  2. 将三个参数字符串拼接成一个字符串进行 SHA1 加密。
  3. 将加密后的字符串与 signature 进行对比。
  4. 如果一致,则说明请求来自微信,请原样返回 echostr 参数内容。
  5. 如果不一致,则不是来自微信,返回空。

2 创建Spring Boot项目

  1. 使用 Spring Initializr 创建一个新项目。

    • Project: Maven
    • Language: Java
    • Spring Boot: 选择一个稳定版本 (如 2.7.x)
    • Project Metadata: 填写你的Group和Artifact。
    • Dependencies: 添加 Spring Web
  2. 项目创建后,用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 本地测试与配置

  1. 运行你的Spring Boot应用。
  2. 使用ngrok等工具将本地端口(如8080)映射到公网,ngrok http 8080。
  3. ngrok会提供一个类似 https://xxxxx.ngrok.io 的公网地址。
  4. 登录微信公众平台后台,在“开发”->“基本配置”中点击“修改配置”:
    • URL: 填入 https://xxxxx.ngrok.io/wechat (ngrok地址 + 你的Controller路径)
    • Token: 填入代码中定义的 yourWechatToken
    • EncodingAESKey: 可以随机生成,或留空(使用明文模式)。
    • 消息加解密方式: 选择“安全模式”或“兼容模式”,初学者建议用“安全模式”或“明文模式”。
  5. 点击“提交”,如果配置正确,会提示“成功”。

恭喜!你的服务器已经成功接入了微信公众平台!


第二步:接收和回复用户消息

服务器验证成功后,用户在公众号中的任何操作(如发送消息、点击菜单等)都会通过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 中配置你的 appidsecret

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为例。

  1. 构造授权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

  2. 处理回调: 用户同意授权后,微信会重定向到你的 redirect_uri,并带上 code 参数,你用这个 code 换取 access_tokenopenid

    // 在你的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(如自定义菜单)

创建菜单、获取用户列表、发送模板消息等,都遵循类似的模式:

  1. 获取 access_token
  2. 按照微信API文档构造请求体(通常是JSON)。
  3. 发送HTTP请求到对应API地址。
  4. 解析返回结果。

创建菜单:

// 在你的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,构建出功能丰富的公众号应用。

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