实际项目中由于需要与不同客户、不同协议的平台进行对接,响应信息也不近相同,通过改造现有统一异常处理逻辑,让jeecgboot的统一异常返回不同格式、不同协议的响应信息。
目录
[1. 先定义核心枚举和上下文工具](#1. 先定义核心枚举和上下文工具)
[2. 新增拦截器识别请求所属平台](#2. 新增拦截器识别请求所属平台)
[3. 改造统一异常处理器](#3. 改造统一异常处理器)
[4. 扩展说明](#4. 扩展说明)
一、核心改造思路
在 JeecgBoot 默认的统一异常处理基础上,我们需要实现异常响应的动态适配,核心思路分为三步:
- 定义多协议 / 多格式的响应模板(如 JSON、XML、自定义协议格式);
- 新增上下文标识,用于识别当前请求对应的客户 / 协议类型;
- 改造全局异常处理器,根据上下文标识动态选择响应格式和协议。
二、具体实现方案
1. 先定义核心枚举和上下文工具
首先定义协议 / 客户标识枚举,以及用于存储当前请求上下文的工具类:
java
/**
* 对接平台/协议枚举
*/
public enum PlatformEnum {
// 客户A-JSON格式
CUSTOMER_A_JSON("customerA", "application/json"),
// 客户B-XML格式
CUSTOMER_B_XML("customerB", "application/xml"),
// 客户C-自定义协议格式
CUSTOMER_C_CUSTOM("customerC", "application/custom"),
// 默认-JeecgBoot原生格式
DEFAULT("default", "application/json");
private String code;
private String contentType;
// 构造方法、getter/setter
PlatformEnum(String code, String contentType) {
this.code = code;
this.contentType = contentType;
}
public static PlatformEnum getByCode(String code) {
for (PlatformEnum platform : values()) {
if (platform.getCode().equals(code)) {
return platform;
}
}
return DEFAULT;
}
// getter/setter省略
}
/**
* 请求上下文工具类(存储当前请求对应的平台标识)
*/
public class RequestContextHolderUtil {
private static final ThreadLocal<PlatformEnum> PLATFORM_THREAD_LOCAL = new ThreadLocal<>();
// 设置当前平台标识
public static void setPlatform(PlatformEnum platform) {
PLATFORM_THREAD_LOCAL.set(platform);
}
// 获取当前平台标识
public static PlatformEnum getPlatform() {
return PLATFORM_THREAD_LOCAL.get() == null ? PlatformEnum.DEFAULT : PLATFORM_THREAD_LOCAL.get();
}
// 清除上下文
public static void clear() {
PLATFORM_THREAD_LOCAL.remove();
}
}
2. 新增拦截器识别请求所属平台
通过拦截器解析请求头 / 参数中的平台标识,存入上下文:
java
/**
* 平台标识拦截器
*/
@Component
public class PlatformIdentifyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 方式1:从请求头获取平台标识(推荐)
String platformCode = request.getHeader("X-Platform-Code");
// 方式2:从请求参数获取(备用)
if (StringUtils.isEmpty(platformCode)) {
platformCode = request.getParameter("platformCode");
}
// 设置到上下文
PlatformEnum platform = PlatformEnum.getByCode(platformCode);
RequestContextHolderUtil.setPlatform(platform);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除上下文,避免线程复用导致数据污染
RequestContextHolderUtil.clear();
}
}
/**
* 注册拦截器
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PlatformIdentifyInterceptor platformIdentifyInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有对接外部的接口路径(根据实际项目调整)
registry.addInterceptor(platformIdentifyInterceptor)
.addPathPatterns("/api/third/**");
}
}
3. 改造统一异常处理器
JeecgBoot 默认的GlobalExceptionHandler通常在org.jeecg.common.exception包下,改造后支持多格式响应:
java
/**
* 改造后的全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class CustomGlobalExceptionHandler {
/**
* 处理JeecgBoot基础异常
*/
@ExceptionHandler(value = JeecgBootException.class)
public Object handleJeecgBootException(JeecgBootException e, HttpServletRequest request, HttpServletResponse response) {
return buildMultiFormatResponse(e.getCode(), e.getMessage(), response);
}
/**
* 处理通用业务异常
*/
@ExceptionHandler(value = BusinessException.class)
public Object handleBusinessException(BusinessException e, HttpServletRequest request, HttpServletResponse response) {
return buildMultiFormatResponse(e.getCode(), e.getMessage(), response);
}
/**
* 处理系统异常
*/
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
log.error("系统异常", e);
return buildMultiFormatResponse(500, "系统内部错误", response);
}
/**
* 核心方法:根据平台标识构建不同格式的响应
*/
private Object buildMultiFormatResponse(Integer code, String msg, HttpServletResponse response) {
PlatformEnum platform = RequestContextHolderUtil.getPlatform();
// 设置响应ContentType
response.setContentType(platform.getContentType() + ";charset=UTF-8");
switch (platform) {
case CUSTOMER_A_JSON:
// 客户A:自定义JSON格式
return buildCustomerAJsonResponse(code, msg);
case CUSTOMER_B_XML:
// 客户B:XML格式
return buildCustomerBXmlResponse(code, msg, response);
case CUSTOMER_C_CUSTOM:
// 客户C:自定义协议格式(如固定分隔符的字符串)
return buildCustomerCCustomResponse(code, msg, response);
default:
// 默认:JeecgBoot原生响应格式
return Result.error(code, msg);
}
}
/**
* 客户A - 自定义JSON响应
*/
private Map<String, Object> buildCustomerAJsonResponse(Integer code, String msg) {
Map<String, Object> response = new HashMap<>();
response.put("retCode", code); // 客户A要求的码名字段
response.put("retMsg", msg); // 客户A要求的消息字段
response.put("timestamp", System.currentTimeMillis());
return response;
}
/**
* 客户B - XML响应
*/
private String buildCustomerBXmlResponse(Integer code, String msg, HttpServletResponse response) {
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
xml.append("<response>");
xml.append("<code>").append(code).append("</code>");
xml.append("<message>").append(msg).append("</message>");
xml.append("</response>");
return xml.toString();
}
/**
* 客户C - 自定义协议响应(如 "code|msg|timestamp")
*/
private String buildCustomerCCustomResponse(Integer code, String msg, HttpServletResponse response) {
return code + "|" + msg + "|" + System.currentTimeMillis();
}
}
4. 扩展说明
- 如果需要支持更多协议(如 Protobuf、FastJSON2 等),只需在
PlatformEnum中新增枚举值,在buildMultiFormatResponse中新增分支即可; - 可以将响应模板配置到 yml 文件中,通过
@Value或配置类读取,减少硬编码; - 对于复杂的 XML/Protobuf 响应,可结合 JAXB、Protobuf 插件生成对应的实体类,简化序列化操作。
三、实际对接场景示例
假设对接客户 A 的接口请求:
html
POST /api/third/customerA/query
Headers:
X-Platform-Code: customerA
Content-Type: application/json
Body:
{"orderNo":"123456"}
当接口抛出异常时,返回的响应格式为客户 A 指定的 JSON:
html
{
"retCode": 500,
"retMsg": "订单查询失败",
"timestamp": 1736889600000
}
对接客户 B 的接口请求:
html
POST /api/third/customerB/query
Headers:
X-Platform-Code: customerB
Content-Type: application/json
Body:
{"orderNo":"123456"}
异常响应为 XML 格式:
XML
<?xml version="1.0" encoding="UTF-8"?>
<response>
<code>500</code>
<message>订单查询失败</message>
</response>
四、总结
- 核心是通过线程上下文存储当前请求的平台标识,让异常处理器能识别对接方;
- 异常处理器通过分支判断实现不同格式 / 协议的响应构建,保持统一入口的同时适配多场景;
- 扩展时只需新增枚举值和响应构建方法,符合 "开闭原则",便于后续对接更多客户 / 协议。
这种改造方式既保留了 JeecgBoot 统一异常处理的优势,又解决了多平台对接响应格式不统一的问题