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

微信企業號(企業微信)驗證、接收、發送訊息完整 Java 實作

     編輯:Java 相關知識

架構概覽

  • 回調模式:企業微信主動推送訊息至我方 HTTPS URL。
  • 加解密:採用 AES 加密,官方提供 WXBizMsgCrypt 工具類。
  • 開發框架:JAX-RS (Jersey) 實作 REST 端點,部署於 Servlet 容器。

前置準備

  1. 註冊企業微信,建立「應用」→ 選擇「回調模式」。
  2. 設定回調 URL(需公網域名,內網穿透可用 ngrok/花生殼)。
  3. 記錄:Token、EncodingAESKey、CorpId、AgentId、Secret。
  4. 下載官方 WXBizMsgCrypt.java 放入專案。

核心程式碼:StationResource.java

package com.base.pf.restful;

import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.*;
import org.w3c.dom.*;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.servlet.http.*;
import javax.xml.parsers.*;
import java.io.*;
import java.net.*;
import javax.net.ssl.HttpsURLConnection;

@Path("wx")
public class StationResource {
    // === 企業微信後台取得的參數 ===
    private final String token = "spm";
    private final String encodingAesKey = "nT6ZWTVFlyNXOhFOGGOZWdJpAgeFSV8ln5CNeYw7mwl";
    private final String corpId = "wxe49318eb604cf00b";
    private final String agentId = "1";
    private final String secret = "M-YFKmgl_kXBVEtginZH3RQWbz4xb6MFeQXXLk77mkpxZenFDYq-UgerxdUF8rel";

    @Context HttpServletRequest request;
    @Context HttpServletResponse response;
    private final WXBizMsgCrypt wxcpt;

    public StationResource() {
        try { wxcpt = new WXBizMsgCrypt(token, encodingAesKey, corpId); }
        catch (Exception e) { throw new RuntimeException(e); }
    }

    // ================== 1. 驗證回調 URL (GET) ==================
    @GET @Path("station")
    public String verify() {
        String msgSig = request.getParameter("msg_signature");
        String ts = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        try {
            return wxcpt.VerifyURL(msgSig, ts, nonce, echostr); // 回傳解密後的明文
        } catch (Exception e) {
            e.printStackTrace();
            return "verify failed";
        }
    }

    // ================== 2. 接收訊息 (POST) ==================
    @POST @Path("station")
    public String receiveMsg(String reqData) {
        String msgSig = request.getParameter("msg_signature");
        String ts = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        try {
            String xml = wxcpt.DecryptMsg(msgSig, ts, nonce, reqData); // 解密得明文 XML
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                    .parse(new InputSource(new StringReader(xml)));
            Element root = doc.getDocumentElement();
            String content = getNodeText(root, "Content");
            System.out.println("收到訊息:" + content);
            // TODO: 業務邏輯處理
        } catch (Exception e) { e.printStackTrace(); }
        return "ok"; // 必須回傳 "ok" 確認收到
    }

    private String getNodeText(Element root, String tag) {
        NodeList nl = root.getElementsByTagName(tag);
        return nl.getLength() > 0 ? nl.item(0).getTextContent() : null;
    }

    // ================== 3. 發送訊息 (主動推送) ==================
    private static final String TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
    private static final String SEND_URL = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=";

    /** 發送任意類型訊息 */
    public void sendWeChatMsg(String msgType, String toUser, String toParty, String toTag,
                              String content, String mediaId, String title, String description,
                              String url, String picUrl, String safe) {
        String accessToken = getAccessToken();
        String json = buildJson(msgType, toUser, toParty, toTag, content, mediaId, title, description, url, picUrl, safe);
        postHttps(SEND_URL + accessToken, json);
    }

    private String getAccessToken() {
        try {
            HttpClient client = new HttpClient();
            PostMethod post = new PostMethod(TOKEN_URL);
            post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
            post.setRequestBody(new NameValuePair[]{
                new NameValuePair("corpid", corpId),
                new NameValuePair("corpsecret", secret)
            });
            client.executeMethod(post);
            JSONObject jo = JSONObject.fromObject(post.getResponseBodyAsString());
            return jo.getString("access_token");
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    private String buildJson(String msgType, String toUser, String toParty, String toTag,
                             String content, String mediaId, String title, String description,
                             String url, String picUrl, String safe) {
        StringBuilder sb = new StringBuilder("{");
        sb.append("\"touser\":\"").append(toUser).append("\",");
        sb.append("\"toparty\":\"").append(toParty).append("\",");
        sb.append("\"totag\":\"").append(toTag).append("\",");
        sb.append("\"msgtype\":\"").append(msgType).append("\",");
        sb.append("\"").append(msgType).append("\":{");
        switch (msgType) {
            case "text": sb.append("\"content\":\"").append(content).append("\""); break;
            case "image": case "voice": case "file":
                sb.append("\"media_id\":\"").append(mediaId).append("\""); break;
            case "video":
                sb.append("\"media_id\":\"").append(mediaId).append("\",")
                  .append("\"title\":\"").append(title).append("\",")
                  .append("\"description\":\"").append(description).append("\""); break;
            case "news":
                sb.append("\"articles\":[{\"title\":\"").append(title).append("\",")
                  .append("\"description\":\"").append(description).append("\",")
                  .append("\"url\":\"").append(url).append("\",")
                  .append("\"picurl\":\"").append(picUrl).append("\"}]"); break;
        }
        sb.append("},");
        sb.append("\"safe\":\"").append(safe).append("\",");
        sb.append("\"agentid\":\"").append(agentId).append("\",");
        sb.append("\"debug\":\"1\"}");
        return sb.toString();
    }

    private void postHttps(String urlStr, String json) {
        try {
            URL url = new URL(urlStr);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            try (OutputStream os = conn.getOutputStream()) {
                os.write(json.getBytes("UTF-8"));
            }
            try (InputStream is = conn.getInputStream()) {
                byte[] buf = new byte[is.available()];
                is.read(buf);
                System.out.println("發送結果:" + new String(buf, "UTF-8"));
            }
        } catch (Exception e) { e.printStackTrace(); }
    }

    // ================== 測試主程式 ==================
    public static void main(String[] args) {
        StationResource wx = new StationResource();
        // 發送圖文訊息給全體成員
        wx.sendWeChatMsg("news", "@all", "", "", "測試標題", "", "測試描述", "真的是測試",
                "https://www.baidu.com", "https://example.com/image.jpg", "0");
    }
}

web.xml 註冊 Jersey Servlet

<servlet>
  <servlet-name>JAX-RS REST Servlet</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>com.base.pf.restful</param-value>
  </init-param>
  <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>JAX-RS REST Servlet</servlet-name>
  <url-pattern>/rest/*</url-pattern>
</servlet-mapping>

最終回調 URL 為:https://your-domain.com/rest/wx/station

關鍵注意事項

項目說明
HTTPS 憑證企業微信**強制要求 HTTPS**,且憑證需受信任(Let's Encrypt 即可),自簽憑證會驗證失敗。
回應超時接收訊息必須在 **5 秒內** 回傳 ok 或解密後明文,否則會重試。
加解密庫官方 WXBizMsgCrypt 依賴 bcprov-jdk15on,需加入 Maven 依賴。
Access Token 快取Token 有效期 7200 秒,建議用 Redis 快取並提前刷新,避免頻繁呼叫 /gettoken 觸發頻率限制。
被動回覆 vs 主動發送被動回覆(5 秒內)直接回傳加密 XML;主動發送需先拿 Token 再呼叫 /message/send。

總結

本文提供基於 Jersey + 官方加解密庫的企業微信完整整合範例,涵蓋 **URL 驗證、訊息接收解密、主動發送多種訊息類型**。實務上建議將 WXBizMsgCrypt 封裝為 Spring Bean、Token 交由排程器快取、訊息處理採非同步佇列,提升穩定性與擴展性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved