杰瑞科技汇

Java微信公众号开发怎么学?

目录

  1. 准备工作:你需要什么?
  2. 第一步:获取公众号测试账号
  3. 第二步:开发环境搭建
  4. 第三步:配置与接入(最关键的一步)
  5. 第四步:接收和发送消息
    • 1 接收用户发送的文本消息
    • 2 发送回复文本消息
    • 3 发送回复图文消息
  6. 第五步:常用API调用
    • 1 获取Access Token
    • 2 菜单管理
  7. 第六步:部署上线
  8. 进阶学习与资源

准备工作:你需要什么?

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

Java微信公众号开发怎么学?-图1
(图片来源网络,侵删)
  • Java基础:熟悉Java语言、Spring Boot框架、Maven等。
  • 开发工具
    • IDE:IntelliJ IDEA 或 Eclipse。
    • API调试工具:Postman 或 Apifox。
  • 服务器:一台可以公网访问的服务器(Linux推荐),用于部署你的应用并接收微信服务器的回调,如果没有,可以使用内网穿透工具(如 ngrok)进行本地调试。
  • 域名:一个备案的域名,用于服务器配置。

第一步:获取公众号测试账号

为了避免影响正式的公众号,我们强烈建议使用测试公众号来进行开发。

  1. 访问微信公众平台:https://mp.weixin.qq.com/
  2. 使用你的个人微信扫描登录。
  3. 在左侧菜单栏找到 “开发” -> “接口测试账号”
  4. 进入测试账号页面,你将获得:
    • AppID (开发者ID):相当于你的公众号的身份证。
    • AppSecret (开发者密码):相当于你的密码,请妥善保管。
    • 测试二维码:用于关注你的测试号,模拟用户交互。
    • 网页授权获取用户基本信息:用于开发网页应用,本教程暂不涉及。

第二步:开发环境搭建

我们将使用Spring Boot快速搭建一个Web项目。

  1. 创建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 或更高版本
    • Dependencies: 添加以下依赖
      • Spring Web: 用于构建Web应用。
      • Thymeleaf: 用于模板引擎(可选,但方便开发)。
      • Lombok: 简化Java代码(可选,但强烈推荐)。
    • 点击 "Generate" 下载项目压缩包,并用IDE打开。
  2. 项目结构 你的项目结构应该大致如下:

    Java微信公众号开发怎么学?-图2
    (图片来源网络,侵删)
    wechat-demo
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── com
    │   │   │       └── example
    │   │   │           └── wechatdemo
    │   │   │               ├── WechatDemoApplication.java
    │   │   │               └── controller
    │   │   │                   └── WeChatController.java  // 我们将在这里处理微信消息
    │   │   └── resources
    │   │       ├── application.properties // 配置文件
    │   │       └── static
    │   │       └── templates
    └── pom.xml

第三步:配置与接入(最关键的一步)

这是微信开发的第一道关卡,目的是让微信服务器确认你的服务器是可靠的。

  1. 配置 application.propertiessrc/main/resources/application.properties 文件中,填入你在测试号页面获取的信息。

    # 微信公众号配置
    wechat.appId=你的AppID
    wechat.appSecret=你的AppSecret
    wechat.token=你的Token (可以自己定义,如 mytoken123)
    wechat.aesKey=你的EncodingAESKey (测试号页面会自动生成)
  2. 编写Controller处理接入请求 当用户在测试号后台点击“配置”时,微信服务器会向你的 http://你的服务器地址/wechat 发送一个GET请求,并携带四个参数:signature, timestamp, nonce, echostr

    你需要做的是:

    1. token, timestamp, nonce 三个参数进行字典序排序。
    2. 将三个参数字符串拼接成一个字符串进行 SHA1 加密。
    3. 将加密后的字符串与 signature 对比。
    4. 如果一致,说明请求来自微信,请返回 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;
        }
    }
  3. 本地调试与配置

    • 启动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)
        • 消息加解密方式: 选择 安全模式 (测试号默认) 或 兼容模式,本教程先用 安全模式
      • 点击“提交”。
      • 如果配置正确,会提示“配置成功”。

第四步:接收和发送消息

我们来处理用户实际发送的消息。

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; // 文本消息内容
}

修改 WeChatControllerhandleWechatMessage 方法来解析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 XMLXStream 等库来进行对象与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>

你可以创建 NewsMessageArticle 类,然后用Jackson XML库来轻松转换。


第五步:常用API调用

除了被动回复消息,我们还可以主动调用微信API,如获取用户信息、自定义菜单等。

调用任何API前,都需要一个全局唯一的凭证:Access Token

1 获取Access Token

  1. 创建配置类 WeChatConfig.java

    package 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;
        // ... 其他配置
    }
  2. 创建Service WeChatService.java

    package 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) 和配置 RestTemplate Bean。

2 菜单管理

创建自定义菜单也是通过API调用实现的。

  1. 创建菜单实体类 (MenuButton.java, Menu.java 等),对应微信的菜单JSON结构。

  2. 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;
        }
    }
  3. 在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 即可创建菜单,创建成功后,去测试号页面刷新就能看到菜单了。


第六步:部署上线

当本地开发测试完成后,你需要将应用部署到公网服务器上。

  1. 打包应用:在项目根目录执行 mvn clean package,会生成一个 .jar 文件。
  2. 上传服务器:将 .jar 文件上传到你的Linux服务器。
  3. 运行应用
    • 确保服务器已安装Java运行环境。
    • 使用 nohup java -jar wechat-demo.jar > wechat.log 2>&1 & 命令在后台运行应用。
    • nohup 表示让命令持续运行,即使你关闭了终端。
    • wechat.log 是日志输出文件。
  4. 配置Nginx (可选但推荐)
    • 使用Nginx作为反向代理,将 http://yourdomain.com 的请求转发到你的Spring Boot应用端口(如8080)。
    • 同时可以配置SSL证书,实现HTTPS访问(微信要求回调URL必须是 HTTPS 协议)。
  5. 修改测试号配置
    • 将测试号的URL从 ngrok 的地址改为你的正式域名地址(如 https://yourdomain.com/wechat)。
    • 重新提交。

进阶学习与资源

  • 官方文档微信公众平台开发者文档,这是最重要的资料,没有之一。
  • 安全模式与加密解密:当选择“安全模式”时,所有收发消息都是加密的,你需要实现AES解密和SHA1签名验证,官方文档有详细的加解密算法示例。
  • 网页授权:如果你的应用需要获取用户信息(如OpenID),需要学习OAuth2.0网页授权流程。
  • 模板消息:用于发送重要的服务通知,如订单确认、物流更新等。
  • JFinal、JeecgBoot等框架:这些框架也提供了非常成熟的微信开发插件,可以大大简化开发流程。

这份教程为你铺平了微信公众号Java开发的基本道路,从简单的消息回复到复杂的API调用,每一步都需要仔细阅读官方文档并动手实践,祝你开发顺利!

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