微信机器人API接口开发中Java后端的接口联调与抓包分析实战技巧

1. 联调环境搭建:本地代理 + HTTPS解密

微信机器人通常通过企业微信或第三方协议接收消息,回调地址需公网 HTTPS。开发阶段使用 ngrok + mitmproxy 组合实现本地调试:

# 启动内网穿透
ngrok http 8080

# 启动抓包代理(监听8081,生成CA证书)
mitmproxy -p 8081 --set ssl_insecure=true

在 Java 应用启动参数中配置代理:

-Dhttp.proxyHost=localhost -Dhttp.proxyPort=8081 \
-Dhttps.proxyHost=localhost -Dhttps.proxyPort=8081 \
-Djavax.net.ssl.trustStore=/path/to/mitmproxy-ca-cert.jks

2. 拦截并记录所有出入站请求

自定义 LoggingInterceptor 记录原始报文:

package wlkankan.cn.wechat.robot.interceptor;

import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;

public class FullTrafficLoggingInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger("TRAFFIC");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long start = System.nanoTime();

        // 记录请求
        log.debug("→→→ {} {}{}", request.method(), request.url(), 
            request.body() != null ? "\n" + bodyToString(request) : "");

        Response response = chain.proceed(request);
        long tookMs = (System.nanoTime() - start) / 1_000_000;

        // 记录响应
        String responseBody = response.peekBody(1024 * 1024).string();
        log.debug("←←← {} {} ({} ms)\n{}", 
            response.code(), response.message(), tookMs, responseBody);

        return response.newBuilder()
            .body(ResponseBody.create(responseBody, response.body().contentType()))
            .build();
    }

    private String bodyToString(Request request) {
        try {
            Request copy = request.newBuilder().build();
            if (copy.body() == null) return "";
            Buffer buffer = new Buffer();
            copy.body().writeTo(buffer);
            return buffer.readUtf8();
        } catch (IOException e) {
            return "failed to read body";
        }
    }
}

注册到 OkHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new FullTrafficLoggingInterceptor())
    .build();

在这里插入图片描述

3. 模拟微信回调请求进行本地测试

编写测试工具类直接触发 Controller:

@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class CallbackSimulationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void simulateWeComTextMessage() throws Exception {
        String encryptedXml = """
            <xml>
              <ToUserName><![CDATA[ww1234567890ab]]></ToUserName>
              <FromUserName><![CDATA[zhangsan]]></FromUserName>
              <CreateTime>1700000000</CreateTime>
              <MsgType><![CDATA[text]]></MsgType>
              <Content><![CDATA[测试消息]]></Content>
              <MsgId>1234567890</MsgId>
            </xml>
            """;

        // 构造签名参数(开发模式可跳过校验)
        mockMvc.perform(post("/robot/callback/ww1234567890ab")
                .param("msg_signature", "dummy_sign")
                .param("timestamp", "1700000000")
                .param("nonce", "abcd1234")
                .content(encryptedXml)
                .contentType(MediaType.APPLICATION_XML))
            .andExpect(status().isOk())
            .andExpect(content().string("success"));
    }
}

4. 解析并验证加密消息体

企业微信回调使用 AES 加密,需正确解密:

@Service
public class WeComCryptoService {

    public String decrypt(String encrypt, String corpId) {
        try {
            byte[] aesKey = Base64.getDecoder().decode(config.getEncodingAesKey() + "=");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(aesKey, 0, 16));
            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encrypt));

            // 去除随机前缀和尾部corpid
            int pad = decrypted[16];
            int xmlEnd = decrypted.length - 20 - pad;
            return new String(decrypted, 20, xmlEnd - 20, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new SecurityException("解密失败", e);
        }
    }
}

在 Controller 中调用:

@PostMapping(value = "/callback/{corpId}", consumes = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String handleCallback(@PathVariable String corpId,
                             @RequestParam String msg_signature,
                             @RequestParam String timestamp,
                             @RequestParam String nonce,
                             @RequestBody String encryptedXml) {
    // 验证签名(略)
    String plainXml = weComCryptoService.decrypt(extractEncrypt(encryptedXml), corpId);
    // 解析 plainXml 并处理
    return "success";
}

5. 使用 Wireshark 辅助分析二进制协议

若对接的是 iPad 协议等私有 TCP 流,启动 Wireshark 抓包:

# 过滤目标IP和端口
tcp.port == 8080 and ip.addr == 127.0.0.1

在 Java 代码中添加唯一追踪 ID 便于过滤:

public class MessageSender {
    public void sendToDevice(String content) {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        log.info("[TRACE] Sending message with traceId={}", traceId);
        
        Map<String, Object> payload = new HashMap<>();
        payload.put("trace_id", traceId);
        payload.put("content", content);
        
        httpClient.post("/send", payload); // 此ID会出现在TCP载荷中
    }
}

在 Wireshark 中搜索 traceId 即可定位完整交互流。

6. 联调问题快速定位 checklist

  • ✅ 回调 URL 是否能被微信服务器访问(通过 ngrok 公网地址);
  • ✅ 签名算法是否与文档一致(注意排序、URL 编码);
  • ✅ AES 解密是否使用 PKCS7(需 BouncyCastle Provider);
  • ✅ 响应是否为纯文本 "success"(不能带 JSON 或空格);
  • ✅ 日志是否开启 DEBUG 级别记录完整请求体。

通过代理抓包、请求模拟、加密解析、流量标记与工具链组合,可在复杂微信机器人 API 对接中快速复现、定位并修复联调问题,显著缩短开发周期。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐