在Spring Boot中集成企业微信API的统一异常处理与日志追踪方案

在Spring Boot中集成企业微信API的统一异常处理与日志追踪方案

企业微信(WeCom)API调用过程中可能因网络波动、权限不足、参数错误或频率限制等原因抛出异常。若未进行统一处理,将导致接口返回格式不一致、错误信息暴露敏感细节,且难以排查问题。本文基于Spring Boot,结合@ControllerAdvice、MDC日志上下文和企业微信官方SDK,构建一套结构清晰、可追溯、安全合规的异常处理与日志追踪体系。

1. 自定义企业微信异常类型

首先定义业务相关的异常类,区分不同错误场景:

java 复制代码
package wlkankan.cn.exception;

public class WecomApiException extends RuntimeException {
    private final int errcode;
    private final String errmsg;

    public WecomApiException(int errcode, String errmsg) {
        super("Wecom API error: " + errmsg + " (code=" + errcode + ")");
        this.errcode = errcode;
        this.errmsg = errmsg;
    }

    public int getErrcode() { return errcode; }
    public String getErrmsg() { return errmsg; }
}

2. 全局异常处理器

使用@ControllerAdvice捕获所有控制器异常,并返回标准化JSON响应:

java 复制代码
package wlkankan.cn.handler;

import wlkankan.cn.exception.WecomApiError;
import wlkankan.cn.exception.WecomApiException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.UUID;

@RestControllerAdvice
public class GlobalWecomExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalWecomExceptionHandler.class);

    @ExceptionHandler(WecomApiException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleWecomApiException(WecomApiException ex) {
        String traceId = MdcUtil.getTraceId();
        log.warn("Wecom API call failed [traceId={}], errcode={}, errmsg={}",
                traceId, ex.getErrcode(), ex.getErrmsg());

        return new ErrorResponse(traceId, "WECHAT_API_ERROR", ex.getErrmsg());
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleUnexpectedException(Exception ex) {
        String traceId = MdcUtil.getTraceId();
        log.error("Unexpected error in wecom service [traceId={}]", traceId, ex);
        return new ErrorResponse(traceId, "INTERNAL_ERROR", "系统内部错误");
    }

    public static class ErrorResponse {
        private final String traceId;
        private final String code;
        private final String message;

        public ErrorResponse(String traceId, String code, String message) {
            this.traceId = traceId;
            this.code = code;
            this.message = message;
        }

        // getters
    }
}

3. 请求级唯一追踪ID(Trace ID)

通过拦截器生成并注入traceId到MDC(Mapped Diagnostic Context),确保日志可串联:

java 复制代码
package wlkankan.cn.log;

import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Component
public class TraceIdInterceptor implements HandlerInterceptor {
    private static final String TRACE_ID_HEADER = "X-Trace-ID";
    public static final String TRACE_ID_MDC_KEY = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceId = request.getHeader(TRACE_ID_HEADER);
        if (traceId == null || traceId.isEmpty()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put(TRACE_ID_MDC_KEY, traceId);
        response.setHeader(TRACE_ID_HEADER, traceId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        MDC.clear();
    }
}

配套工具类:

java 复制代码
package wlkankan.cn.log;

public class MdcUtil {
    public static String getTraceId() {
        return org.slf4j.MDC.get(TraceIdInterceptor.TRACE_ID_MDC_KEY);
    }
}

4. 企业微信API调用封装与异常转换

在调用企业微信SDK时,主动捕获底层异常并转换为自定义异常:

java 复制代码
package wlkankan.cn.wecom;

import com.tencent.wework.api.WxClient;
import com.tencent.wework.api.model.SendResult;
import wlkankan.cn.exception.WecomApiException;
import wlkankan.cn.log.MdcUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WecomMessageService {
    private static final Logger log = LoggerFactory.getLogger(WecomMessageService.class);
    private final WxClient wxClient;

    public void sendTextMessage(String userId, String content) {
        String traceId = MdcUtil.getTraceId();
        try {
            SendResult result = wxClient.sendMessage(userId, content);
            if (!"0".equals(result.getErrcode())) {
                throw new WecomApiException(Integer.parseInt(result.getErrcode()), result.getErrmsg());
            }
            log.info("Wecom message sent successfully [traceId={}, to={}]", traceId, userId);
        } catch (RuntimeException e) {
            if (e instanceof WecomApiException) throw e;
            // 转换第三方SDK异常
            log.error("Failed to send wecom message [traceId={}]", traceId, e);
            throw new WecomApiException(-1, "企业微信服务调用异常");
        }
    }
}

5. 日志配置(logback-spring.xml)

在日志输出中包含traceId字段:

xml 复制代码
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
  </root>
</configuration>

通过上述设计,所有企业微信API调用均具备统一的错误响应格式、完整的请求链路追踪能力及结构化日志输出,极大提升系统可观测性与运维效率。

相关推荐
linmoo19863 分钟前
Agent应用实践之四 - 基础:AgentScope-SpringBoot集成源码解析
人工智能·spring boot·agent·agentscope·openclaw
海兰43 分钟前
【第21篇-续】graph-Stream-Node改造为适配openAI模型示例
java·人工智能·spring boot·spring·spring ai
Albert Edison1 小时前
基于 SpringBoot + RabbitMQ 完成企业级应用通信
spring boot·rabbitmq·java-rabbitmq
A_QXBlms2 小时前
企微私域运营提效缓慢成因分析与企销宝技术落地方案
企业微信
2501_941982052 小时前
如何将企业微信 RPA 抽象为高可用的外部群自动化 API?
企业微信·rpa
happymaker06263 小时前
Spring学习日记——DAY03(yml文件)
java·spring boot·spring
hikktn4 小时前
企业级Spring Boot应用管理:从零打造生产级启动脚本
java·spring boot·后端
霸道流氓气质4 小时前
Spring Boot + MyBatis-Plus 实现异常隔离的 Upsert 数据落库(含远程调用数据补全)
spring boot·后端·mybatis
不懂的浪漫4 小时前
01|从 Spring Boot 项目理解 RAG:ingest、query、rerank、trace 到 eval
java·人工智能·spring boot·后端·ai·rag
__log4 小时前
NestJS vs Spring Boot:从架构哲学到实战选择的技术全景解析
spring boot·后端·架构·typescript