:feat: HCS接口500错误信息还原(Feign降级工厂还原远程msg/traceId,落库hcs_sync_msg)

This commit is contained in:
刘泉佳
2026-10-10 20:16:39 +08:00
parent 9ba9fe4dd8
commit 7e6970277c
4 changed files with 246 additions and 1 deletions
@@ -27,6 +27,7 @@ package org.springblade.hcs.config;
import feign.Request;
import feign.RequestInterceptor;
import org.springblade.hcs.feign.HcsClientFallbackFactory;
import org.springblade.hcs.interceptor.HcsAuthInterceptor;
import org.springframework.context.annotation.Bean;
@@ -49,6 +50,14 @@ public class HcsFeignClientConfig {
};
}
/**
* HCS 降级工厂:非 2xx 时还原远程错误体 msg/traceId,避免全局兜底吞成 null(配合 IHcsClient 的 fallbackFactory 声明)
*/
@Bean
public HcsClientFallbackFactory hcsClientFallbackFactory() {
return new HcsClientFallbackFactory();
}
/**
* HCS 认证拦截器:所有请求注入 Authorization + Key-Auth 两个静态认证头(v4 接口说明)
*/
@@ -0,0 +1,116 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.hcs.feign;
import feign.FeignException;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.hcs.dto.HcsResult;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* HCS Feign 客户端降级工厂
* <p>
* HCS 返回非 2xx(如 500)时 Feign 抛 {@link FeignException},全局 BladeFeignFallback
* 对 HcsResult 返回值只会返回 null,导致远程错误信息丢失(调用侧只见"无响应/响应为空")。
* 本工厂在降级时将错误体还原为失败的 {@link HcsResult}:HCS 错误体与 HcsResult 同构
* (success/code/msg/traceId),直接反序列化取回远程 msg 与 traceId,
* 供调用侧日志与 hcs_sync_msg 等失败原因落库使用。
* <p>
* 通过 {@link IHcsClient} 的 fallbackFactory 声明并注册于 {@link org.springblade.hcs.config.HcsFeignClientConfig},
* 所有 HCS 接口(含双层嵌套 HcsResult 外层)统一生效,调用侧零改动。
*
* @author Chill
*/
public class HcsClientFallbackFactory implements FallbackFactory<IHcsClient> {
@Override
public IHcsClient create(Throwable cause) {
return (IHcsClient) Proxy.newProxyInstance(IHcsClient.class.getClassLoader(), new Class<?>[]{IHcsClient.class},
(Object proxy, Method method, Object[] args) -> {
// Object 基础方法(toString/hashCode/equals)直接执行
if (method.getDeclaringClass() != IHcsClient.class) {
return method.invoke(this, args);
}
// 附件下载等非 HcsResult 返回值无业务结果可还原,保持 null(与原全局兜底行为一致)
if (!HcsResult.class.isAssignableFrom(method.getReturnType())) {
return null;
}
return buildFailResult(cause);
});
}
/**
* 按异常构造失败的 HcsResult,优先还原远程错误体中的 msg/traceId
*/
private HcsResult<?> buildFailResult(Throwable cause) {
HcsResult<?> result = new HcsResult<>();
result.setSuccess(false);
// HCS 非 2xx 时错误体随 FeignException 下发,结构与 HcsResult 一致
if (cause instanceof FeignException feignException) {
result.setCode("errors.http." + feignException.status());
HcsResult<?> remote = parseRemoteResult(feignException.contentUTF8());
if (remote != null && StringUtil.isNotBlank(remote.getMsg())) {
result.setMsg(remote.getMsg());
result.setTraceId(remote.getTraceId());
} else {
result.setMsg(resolveFallbackMessage(cause));
}
return result;
}
// 非 HTTP 错误(熔断打开、连接超时等)
result.setCode("errors.feignFallback");
result.setMsg(resolveFallbackMessage(cause));
return result;
}
/**
* 错误体反序列化为 HcsResult(非 JSON 或解析失败时返回 null)
*/
private HcsResult<?> parseRemoteResult(String body) {
if (StringUtil.isBlank(body)) {
return null;
}
try {
return JsonUtil.parse(body, HcsResult.class);
} catch (Exception exception) {
return null;
}
}
/**
* 兜底消息:异常摘要非空用之,否则给可读默认值
*/
private String resolveFallbackMessage(Throwable cause) {
if (StringUtil.isNotBlank(cause.getMessage())) {
return cause.getMessage();
}
return "HCS 接口调用异常(" + cause.getClass().getSimpleName() + ")";
}
}
@@ -78,7 +78,7 @@ import org.springframework.web.multipart.MultipartFile;
*
* @author Chill
*/
@FeignClient(name = "HCS", url = "${thirdParty.hcs.baseUrl}", configuration = HcsFeignClientConfig.class)
@FeignClient(name = "HCS", url = "${thirdParty.hcs.baseUrl}", configuration = HcsFeignClientConfig.class, fallbackFactory = HcsClientFallbackFactory.class)
public interface IHcsClient {
String PROJECT_ADD_URL = "${thirdParty.hcs.projectAddUrl:/openapi/sys-modeling/apis/data/km-project1/V1/projectApproval/add}";