首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

微信公眾平台開發入門:從環境搭建到消息收發完整實戰

     編輯:Java 綜合教學

微信公眾平台開發入門全攻略 (一):環境配置與基礎消息處理

微信公眾號開發是 Java 開發者常遇場景。本文詳述從賬號註冊、內網穿透、伺服器驗證到收發消息的完整流程,附帶完整 Servlet 實作代碼,助你快速構建第一個微信應用。

一、開發前準備

1. 公眾號類型選擇

類型適用主體群發頻率核心能力
訂閱號個人/媒體/組織每天 1 次資訊傳播、品牌宣傳
服務號企業/政府/組織每月 4 次服務互動、自定義菜單、微信支付、模板消息
企業號/企業微信企業內部無限制OA 辦公、客戶聯繫、應用開放平台

建議:初學者可先申請「訂閱號」或使用「微信公眾平台測試賬號」 (無需認證,功能完整)。

2. 內網穿透:讓本地 Tomcat 対外可訪問

微信回調 URL 必須為 公網可訪問的 80/443 端口。開發階段常用工具:

  • ngrok (推薦):ngrok http 8080 產生 https://xxx.ngrok.io
  • 花生殼:圖形化介面,適合 Windows 用戶
  • frp / cpolar:自建穿透服務,穩定性更高

花生殼配置界面

外網訪問驗證成功

3. 生產環境部署選項

  • 雲服務器 (阿里雲、騰訊雲、AWS EC2) + Nginx 反向代理 + SSL 憑證
  • 雲原生平台 (SAE、BAE、Heroku) 直接部署 WAR/JAR
  • 容器化部署:Docker + Kubernetes

二、伺服器驗證:接入微信的第一道關卡

驗證流程圖解

  1. 開發者在公眾平台填寫 URL、Token
  2. 微信服務器發送 GET 請求攜帶 4 參數:signature, timestamp, nonce, echostr
  3. 開發者驗證簽名:
// 1. 將 token、timestamp、nonce 字典序排序
// 2. 拼接成字串進行 SHA1 加密
// 3. 比對加密結果與 signature
String[] arr = {token, timestamp, nonce};
Arrays.sort(arr);
String content = String.join("", arr);
String sha1 = SHA1.encode(content);
if (sha1.equals(signature)) {
    return echostr; // 原樣返回,驗證通過
}
  1. 驗證通過返回 echostr,接入生效

三、消息收發核心實作

依賴項 (Maven)


    org.dom4j
    dom4j
    2.1.4


    com.thoughtworks.xstream
    xstream
    1.4.18


    javax.servlet
    javax.servlet-api
    4.0.1
    provided

1. 簽名驗證工具類

public class CheckUtil {
    private static final String TOKEN = "your_token_here"; // 與公眾平台配置一致

    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = {TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        String content = String.join("", arr);
        return SHA1Util.encode(content).equals(signature);
    }
}

public class SHA1Util {
    public static String encode(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(digest).toLowerCase();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-1 algorithm not found", e);
        }
    }
}

2. XML 與 Java Bean 互轉工具

public class MessageUtil {
    // 消息類型常量
    public static final String MSG_TEXT = "text";
    public static final String MSG_EVENT = "event";
    public static final String EVENT_SUBSCRIBE = "subscribe";

    /** XML 請求體轉 Map */
    public static Map xmlToMap(HttpServletRequest request) throws Exception {
        Map map = new HashMap<>();
        SAXReader reader = new SAXReader();
        Document doc = reader.read(request.getInputStream());
        Element root = doc.getRootElement();
        for (Element element : root.elements()) {
            map.put(element.getName(), element.getText());
        }
        return map;
    }

    /** 文本消息 Bean 轉 XML 回覆 */
    public static String textMessageToXml(TextMessage msg) {
        XStream xstream = new XStream();
        xstream.alias("xml", msg.getClass());
        xstream.useAttributeFor(TextMessage.class, "MsgType"); // CDATA 處理需額外配置
        return xstream.toXML(msg);
    }

    /** 快速建立文本回覆 */
    public static String initText(String toUser, String fromUser, String content) {
        TextMessage msg = new TextMessage();
        msg.setToUserName(fromUser);      // 注意:回覆時 To/From 互換
        msg.setFromUserName(toUser);
        msg.setCreateTime(System.currentTimeMillis() / 1000); // 秒級時間戳
        msg.setMsgType(MSG_TEXT);
        msg.setContent(content);
        return textMessageToXml(msg);
    }

    // 菜單回覆內容
    public static String menuText() { return "歡迎關注!回覆 \"幫助\" 查看功能。"; }
    public static String firstMenu() { return "收到:喜歡你 ❤️"; }
    public static String secondMenu() { return "收到:很喜歡你 💕"; }
}

3. 文本消息實體類

@XStreamAlias("xml")
public class TextMessage {
    private String ToUserName;
    private String FromUserName;
    private long CreateTime;
    private String MsgType;
    private String Content;
    private long MsgId;
    // Getter/Setter 省略,建議使用 Lombok @Data
}

4. 核心 Servlet 處理 GET/POST

@WebServlet("/weixin")
public class WeixinServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce");
        String echostr = req.getParameter("echostr");

        PrintWriter out = resp.getWriter();
        if (CheckUtil.checkSignature(signature, timestamp, nonce)) {
            out.print(echostr); // 驗證通過原樣返回
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("application/xml;charset=UTF-8");

        PrintWriter out = resp.getWriter();
        try {
            Map map = MessageUtil.xmlToMap(req);
            String msgType = map.get("MsgType");
            String fromUser = map.get("FromUserName");
            String toUser = map.get("ToUserName");
            String reply = null;

            // 1. 文本消息處理
            if (MessageUtil.MSG_TEXT.equals(msgType)) {
                String content = map.get("Content");
                if ("喜歡你".equals(content)) {
                    reply = MessageUtil.initText(toUser, fromUser, MessageUtil.firstMenu());
                } else if ("很喜歡你".equals(content)) {
                    reply = MessageUtil.initText(toUser, fromUser, MessageUtil.secondMenu());
                } else if ("?".equals(content) || "?".equals(content)) {
                    reply = MessageUtil.initText(toUser, fromUser, MessageUtil.menuText());
                } else {
                    reply = MessageUtil.initText(toUser, fromUser, "收到:" + content);
                }
            }
            // 2. 事件推送:關注/取消關注
            else if (MessageUtil.MSG_EVENT.equals(msgType)) {
                String event = map.get("Event");
                if (MessageUtil.EVENT_SUBSCRIBE.equals(event)) {
                    reply = MessageUtil.initText(toUser, fromUser, MessageUtil.menuText());
                }
            }

            if (reply != null) out.print(reply);
        } catch (Exception e) {
            log.error("微信消息處理異常", e);
        } finally {
            out.close();
        }
    }
}

四、消息類型與 XML 結構速查

微信消息類型 XML 結構

  • 文本:Content
  • 圖片:PicUrl, MediaId
  • 語音:MediaId, Format, Recognition (語音識別結果)
  • 視頻/短視頻:MediaId, ThumbMediaId
  • 地理位置:Location_X, Location_Y, Scale, Label
  • 鏈接:Title, Description, Url
  • 事件:Event (subscribe/unsubscribe/CLICK/VIEW/掃碼/上報地理位置)

五、開發模式與編輯模式互斥說明

開發模式與編輯模式互斥

  • 開發模式:配置回調 URL,程式碼自動回覆,適合開發者
  • 編輯模式:在公眾平台網頁配置自動回覆規則,適合運營人員
  • 同一時間只能啟用一種,切換需在公眾平台「基本配置」頁面操作

六、常見坑點與最佳實踐

  1. Token 一致性:Java 代碼與公眾平台配置的 Token 必須完全一致
  2. 編碼統一 UTF-8:Request/Response/DB/檔案全鏈路 UTF-8,避免中文亂碼
  3. 5 秒超時限制:微信要求 5 秒內回覆,耗時邏輯必須異步處理 (回覆「處理中」+ 客服消息異步下發)
  4. 重複消息去重:微信可能重發,使用 MsgId 或 FromUserName+CreateTime 去重
  5. 安全加密模式:生產環境建議啟用「安全模式」(AES 加密),需實作加解密介面
  6. Access Token 緩存:調用微信 API 需要的 access_token 有效期 2 小時,必須全局緩存定時刷新
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved