Browse Source

v1.0.0-feature: 增加网关的异常和服务不可用的处理和业务包装

sckw-developer 2 tháng trước cách đây
mục cha
commit
9d5a03cd1c

+ 6 - 0
sckw-gateway/pom.xml

@@ -86,6 +86,12 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>2.0.57</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 55 - 0
sckw-gateway/src/main/java/com/sckw/gateway/config/GatewayExceptionConfig.java

@@ -0,0 +1,55 @@
+package com.sckw.gateway.config;
+
+import com.sckw.gateway.exception.GatewayGlobalExceptionHandler;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.codec.ServerCodecConfigurer;
+import org.springframework.web.reactive.result.view.ViewResolver;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @desc: 网关异常配置
+ * @date: 2025-11-10
+ */
+@Configuration
+public class GatewayExceptionConfig {
+
+    private final ServerProperties serverProperties;
+
+    private final ApplicationContext applicationContext;
+
+    private final List<ViewResolver> viewResolvers;
+
+    private final ServerCodecConfigurer serverCodecConfigurer;
+
+    public GatewayExceptionConfig(ServerProperties serverProperties,
+                                  ObjectProvider<List<ViewResolver>> viewResolversProvider,
+                                  ServerCodecConfigurer serverCodecConfigurer,
+                                  ApplicationContext applicationContext) {
+        this.serverProperties = serverProperties;
+        this.applicationContext = applicationContext;
+        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
+        this.serverCodecConfigurer = serverCodecConfigurer;
+    }
+
+    /**
+     * 自定义异常处理
+     */
+    @Bean
+    @Order(Ordered.HIGHEST_PRECEDENCE)
+    public ErrorWebExceptionHandler errorWebExceptionHandler() {
+        GatewayGlobalExceptionHandler exceptionHandler = new GatewayGlobalExceptionHandler();
+        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
+        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
+        exceptionHandler.setViewResolvers(this.viewResolvers);
+        return exceptionHandler;
+    }
+}

+ 119 - 0
sckw-gateway/src/main/java/com/sckw/gateway/exception/GatewayGlobalExceptionHandler.java

@@ -0,0 +1,119 @@
+package com.sckw.gateway.exception;
+
+import com.sckw.gateway.pojo.HttpResult;
+import com.sckw.gateway.utils.GatewayExceptionUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.http.codec.HttpMessageWriter;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.util.Assert;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @desc: 网关全局异常处理器
+ * @date: 2025-11-10
+ */
+@Slf4j
+public class GatewayGlobalExceptionHandler implements ErrorWebExceptionHandler {
+
+    /**
+     * MessageReader
+     */
+    private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
+
+    /**
+     * MessageWriter
+     */
+    private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();
+
+    /**
+     * ViewResolvers
+     */
+    private List<ViewResolver> viewResolvers = Collections.emptyList();
+
+    /**
+     * 存储处理异常后的信息
+     */
+    private ThreadLocal<Map<String, Object>> exceptionHandlerResult = new ThreadLocal<>();
+
+    /**
+     * 参考AbstractErrorWebExceptionHandler
+     */
+    public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
+        Assert.notNull(messageReaders, "'messageReaders' must not be null");
+        this.messageReaders = messageReaders;
+    }
+
+    /**
+     * 参考AbstractErrorWebExceptionHandler
+     */
+    public void setViewResolvers(List<ViewResolver> viewResolvers) {
+        this.viewResolvers = viewResolvers;
+    }
+
+    /**
+     * 参考AbstractErrorWebExceptionHandler
+     */
+    public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
+        Assert.notNull(messageWriters, "'messageWriters' must not be null");
+        this.messageWriters = messageWriters;
+    }
+
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
+        // 按照异常类型进行处理
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+        String path = request.getPath().value();
+        String method = request.getMethod().name();
+        // 使用工具类构建统一的异常响应
+        HttpResult result = GatewayExceptionUtil.buildServiceUnavailableResponse(request, throwable);
+        response.setStatusCode(HttpStatus.OK);
+        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+
+        // 记录异常日志
+        log.warn("网关统一异常处理: 路径={}, 方法={}, 响应={}", path, method, result);
+
+        // 返回统一的错误响应
+        return response.writeWith(Mono.fromSupplier(() -> {
+            try {
+                byte[] bytes = com.alibaba.fastjson2.JSON.toJSONBytes(result);
+                return response.bufferFactory().wrap(bytes);
+            } catch (Exception e) {
+                log.error("网关异常响应序列化失败", e);
+                return response.bufferFactory().wrap("{\"code\":60500,\"msg\":\"系统异常\",\"data\":null}".getBytes());
+            }
+        }));
+    }
+
+    /**
+     * 参考AbstractErrorWebExceptionHandler
+     */
+    private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
+        Map<String, Object> result = exceptionHandlerResult.get();
+        return ServerResponse.status(HttpStatus.OK)
+                .contentType(MediaType.APPLICATION_JSON)
+                .body(BodyInserters.fromValue(result));
+    }
+
+    /**
+     * 参考AbstractErrorWebExceptionHandler
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getErrorAttributes(ServerRequest request) {
+        return exceptionHandlerResult.get();
+    }
+}

+ 89 - 0
sckw-gateway/src/main/java/com/sckw/gateway/pojo/HttpResult.java

@@ -0,0 +1,89 @@
+package com.sckw.gateway.pojo;
+
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * HTTP结果封装
+ * @author zk
+ * @date Oct 29, 2018
+ */
+@Data
+public class HttpResult implements Serializable {
+
+    private int code = HttpStatus.SUCCESS_CODE;
+    private String msg = HttpStatus.SUCCESS_MESSAGE;
+    private Object data;
+
+    public static HttpResult error() {
+        return error(HttpStatus.GLOBAL_EXCEPTION_CODE, HttpStatus.GLOBAL_EXCEPTION_MESSAGE);
+    }
+
+    public static HttpResult error(String msg) {
+        return error(HttpStatus.GLOBAL_EXCEPTION_CODE, msg);
+    }
+
+    public static HttpResult error(int code, String msg) {
+        HttpResult r = new HttpResult();
+        r.setCode(code);
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static HttpResult error(String msg, Object data) {
+        HttpResult r = new HttpResult();
+        r.setCode(HttpStatus.GLOBAL_EXCEPTION_CODE);
+        r.setMsg(msg);
+        r.setData(data);
+        return r;
+    }
+
+    public static HttpResult error(int code, String msg, Object data) {
+        HttpResult r = new HttpResult();
+        r.setCode(code);
+        r.setMsg(msg);
+        r.setData(data);
+        return r;
+    }
+
+
+    public static HttpResult ok() {
+        return new HttpResult();
+    }
+
+    public static HttpResult ok(String msg) {
+        HttpResult r = new HttpResult();
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static HttpResult ok(Object data) {
+        HttpResult r = new HttpResult();
+        r.setData(data);
+        return r;
+    }
+
+    public static HttpResult ok(int code, String msg) {
+        HttpResult r = new HttpResult();
+        r.setCode(code);
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static HttpResult ok(String msg, Object data) {
+        HttpResult r = new HttpResult();
+        r.setMsg(msg);
+        r.setData(data);
+        return r;
+    }
+
+    public static HttpResult ok(int code, String msg, Object data) {
+        HttpResult r = new HttpResult();
+        r.setCode(code);
+        r.setMsg(msg);
+        r.setData(data);
+        return r;
+    }
+}

+ 198 - 0
sckw-gateway/src/main/java/com/sckw/gateway/pojo/HttpStatus.java

@@ -0,0 +1,198 @@
+package com.sckw.gateway.pojo;
+
+/**
+ * @Description 自定义接口请求状态码(和http请求码无关)
+ * @Author zk
+ * @Date 2019/5/13 0013
+ */
+public class HttpStatus {
+
+    /**成功状态码*/
+    public static final int SUCCESS_CODE = 60200;
+
+    /**成功提示信息*/
+    public static final String SUCCESS_MESSAGE = "success";
+
+    /**未登录状态码*/
+    public static final int UN_LOGIN_CODE = 60300;
+    /**未登录提示信息*/
+    public static final String UN_LOGIN_MESSAGE = "您尚未登录,请先登录!";
+
+    /**访问权限状态码*/
+    public static final int AUTHORITY_NO_CODE = 60403;
+    public static final String ACCESS_FIAL = "暂无该功能权限!";
+
+    /**全局异常状态码*/
+    public static final int GLOBAL_EXCEPTION_CODE = 60500;
+
+    /**完结贸易订单失败异常码*/
+    public static final int COMPLETE_TORDER_FAIL_CODE = 60666;
+
+    /** 商品上架失败异常码*/
+    public static final int GOODS_PUT_ON_SHELVES_FAIL_CODE = 60667;
+
+    /**全局异常提示信息*/
+    public static final String GLOBAL_EXCEPTION_MESSAGE = "攻城狮正在拼命优化,请您稍候再试!";
+
+    /**参数缺失状态码*/
+    public static final int PARAMETERS_MISSING_CODE = 60600;
+    public static final String ID_MISSING = "id不能为空!";
+    public static final String ACCOUNT_MISSING = "用户账号必填!";
+    public static final String ACCOUNT_FREEZE = "用户账号不能重复冻结!";
+    public static final String ACCOUNT_UNFREEZE = "用户账号不能重复解冻!";
+    public static final String PWD_MISSING = "密码不能为空!";
+    public static final String TOKEN_MISSING = "token不能为空!";
+    public static final String TOKEN_ERROR = "非法token!";
+    public static final String CAPCHA_ERROR = "验证码无效!";
+    public static final String ADDRESS_EXISTS = "地点已存在,不可重复!";
+    public static final String INVALID_REQUEST = "无效的接口请求!";
+
+    /**其他自定义状态码*/
+    public static final int CODE_60603 = 60603;
+    public static final String ENTCERTIFICATES_INVAILD = "您的企业资质已失效,暂没有权限访问,请尽快更新资质";
+    public static final String ENTCERTIFICATES_NOT_REGISTER = "您未做企业资质认证,暂没有权限访问";
+    public static final String ENTCERTIFICATES_NOT_PASS = "您的企业资质认证还在审核中,暂没有权限访问";
+
+
+
+    public static final int CODE_60604 = 60604;
+    public static final int CODE_60605 = 60605;
+    public static final int CODE_60606 = 60606;
+    public static final int CODE_60607 = 60607;
+    public static final int CODE_60608 = 60608;
+    public static final int CODE_60609 = 60609;
+    /**sentinel异常code定义*/
+    public static final int CODE_60701 = 60701;
+    public static final int CODE_60801 = 60801;
+    public static final int CODE_60901 = 60901;
+    public static final int CODE_601001 = 601001;
+    public static final int CODE_601101 = 601101;
+    public static final int CODE_601201 = 601201;
+
+    public static final String FLOW_EXCEPTION_ERROR_MESSAGE = "您的访问过于频繁,请稍后重试";
+    public static final String DEGRADE_EXCEPTION_ERROR_MESSAGE = "调用服务响应异常,请稍后重试";
+    public static final String PARAM_FLOW_EXCEPTION_ERROR_MESSAGE = "您对热点参数访问过于频繁,请稍后重试";
+    public static final String SYSTEM_BLOCK_EXCEPTION_ERROR_MESSAGE = "已触碰系统的红线规则,请检查访问参数";
+    public static final String AUTHORITY_EXCEPTION_ERROR_MESSAGE = "授权规则检测不同,请检查访问参数";
+    public static final String OTHER_EXCEPTION_ERROR_MESSAGE = "非法访问,请稍后重试";
+
+    /**版本号和接口版本不对称状态码*/
+    public static final int VERSION_NOT_NEWEST_CODE = 60700;
+    /**版本号和接口版本不对称提示信息*/
+    public static final String VERSION_NOT_NEWEST_MESSAGE = "当前版本较低,请更新升级后再试!";
+
+
+    /**参数格式不正确状态码*/
+    public static final int PARAMETERS_PATTERN_ERROR_CODE = 60800;
+    /**参数格式不正确提示信息*/
+    public static final String PARAMETERS_PATTERN_ERROR_MESSAGE = "参数格式不正确";
+    public static final String CONTACTS_ERROR = "联系人格式不正确";
+    public static final String CONTACTS_PHONE_ERROR = "联系人手机号格式不正确";
+    public static final String LEGAL_NAME_ERROR = "法人格式不正确";
+    public static final String LEGAL_PHONE_ERROR = "法人手机号格式不正确";
+    public static final String LEGAL_ID_CARD_ERROR = "法人身份证号格式不正确";
+    public static final String ENT_CODE_ERROR = "营业执照编号格式不正确";
+
+    /**账号在别处登录状态码*/
+    public static final int ACCOUNT_OTHER_LOGIN_CODE = 60900;
+    /**账号在别处登录提示信息*/
+    public static final String ACCOUNT_OTHER_LOGIN_MESSAGE = "您的账号已在其他设备登录,如非本人操作,请及时修改密码!";
+
+
+    /**token无效状态码*/
+    public static final int TOKEN_INVALID_CODE = 60901;
+    /**token无效提示信息*/
+    public static final String TOKEN_INVALID_MESSAGE = "由于您一段时间未操作,为了您的账户安全,请重新登录!";
+
+    /**请求超过次数*/
+    public static final int FREQUENCY_OUT = 60902;
+    /**请求超过次数提示信息*/
+    public static final String FREQUENCY_OUT_MESSAGE = "您的操作过于频繁,请刷新浏览器或稍候重试!";
+
+    /**审核状态状态码*/
+    public static final int ACCOUNT_AUDIT_CODE = 60903;
+    /**审核状态状提示信息*/
+    public static final String ACCOUNT_AUDIT_MESSAGE = "您所属企业企业资质审批未通过,请核实确认!";
+
+    /**微信账号未绑定态码*/
+    public static final int WECHAR_BIND_CODE = 60904;
+    /**微信账号未绑定提示信息*/
+    public static final String WECHAR_BIND_MESSAGE = "您的微信还未绑定危品汇账号!";
+
+    /**
+     * 自定义状态码,该状态码没有特殊含义,只是提供一个状态标识(目前提供9个,可自行扩展)
+     * 现作为校验失败的一类异常码
+     */
+    public static final int CODE_10301 = 10301;
+
+    /**数据库的操作失败*/
+    public static final int CRUD_FAIL_CODE = 60601;
+    public static final String UPDATE_FAIL = "更新失败";
+    public static final String INSERT_FAIL = "新增失败";
+    public static final String DELETE_FAIL = "删除失败";
+
+    /**未查询到相关信息*/
+    public static final int QUERY_FAIL_CODE = 60602;
+    public static final String ACCOUNT_NOT_EXISTS = "用户信息不存在或已失效";
+    public static final String ENT_NOT_EXISTS = "企业信息不存在或已失效";
+    public static final String DEPT_NOT_EXISTS = "机构信息不存在或已失效";
+    public static final String ENTCERTIFICATES_NOT_EXISTS = "未查询到企业资质信息";
+    public static final String ROLE_NOT_EXISTS = "未查询到关联的角色";
+    public static final String USER_DEPT_NOT_EXISTS = "未查询到用户-机构关联信息";
+    public static final String MENU_NOT_EXISTS = "未查询到菜单信息";
+    public static final String PARENT_MENU_NOT_EXISTS = "未查询到父菜单信息";
+    public static final String PARENT_UNIT_NOT_EXISTS = "未查询到父级单位信息";
+    public static final String COOPERATE_ATTRIBUTE_NOT_EXISTS = "未查询到已有的合作属性";
+    public static final String COOPERATE_NOT_EXISTS = "未查询到合作记录或已失效";
+    public static final String COOPERATE_CANCEL_EXISTS = "未查询到可撤销的记录";
+    public static final String ADDRESS_NOT_EXISTS = "未查询到地址记录或已失效";
+    public static final String CONTRACT_NOT_EXISTS = "未查询到合同或已失效";
+    public static final String BANNER_NOT_EXISTS = "未查询到banner数据或已失效";
+
+    /**自定义提示消息*/
+    public static final String PASSWD_ERROR = "密码不正确";
+    public static final String PASSWD_REPEAT = "新密码与旧密码不能一样!";
+    public static final String CAPTCHA_ERROR = "验证码输入错误";
+    public static final String MSG_001 = "密码重置成功";
+    public static final String MSG_002 = "密码修改成功";
+    public static final String MSG_003 = "新增成功";
+    public static final String MSG_004 = "审批完成";
+    public static final String MSG_005 = "更新成功";
+    public static final String MSG_006 = "当前系统不允许绑定多个岗位!";
+    public static final String MSG_007 = "注册成功!";
+    public static final String MSG_008 = "删除成功!";
+    public static final String MSG_009 = "认证提交成功!";
+    public static final String MSG_010 = "绑定成功!";
+    public static final String MSG_011 = "当前企业还存在员工数据,不能删除!";
+    public static final String MSG_012 = "只能解除状态为合作中的记录!";
+    public static final String MSG_013 = "只能删除状态为已解除的记录!";
+    public static final String MSG_014 = "导出失败";
+    public static final String MSG_015 = "与对方企业存在待审核的合作关系,请撤销或者完成审核再申请!";
+    public static final String MSG_016 = "与对方企业已存在相同属性的合作关系!";
+    public static final String MSG_017 = "发起申请成功!";
+    public static final String MSG_018 = "撤销成功!";
+    public static final String MSG_019 = "只能操作正在审核中的记录!";
+    public static final String MSG_020 = "当前机构还存在员工数据,不能删除!";
+    public static final String MSG_021 = "不能重复设置默认地址!";
+    public static final String MSG_022 = "只能对草稿进行删除!";
+    public static final String MSG_023 = "解除成功!";
+    public static final String MSG_024 = "名称不能重复!";
+    public static final String MSG_025 = "禁止删除本人信息!";
+    public static final String MSG_026 = "资质认证审核中,请等待审核后再提交!";
+    public static final String MSG_027 = "一级机构不能删除!";
+    public static final String MSG_028 = "合同未签约,不能手动完结!";
+    public static final String MSG_029 = "合同编号超长!";
+    public static final String MSG_030 = "合同名称超长!";
+    public static final String MSG_031 = "您与所选企业存在未完结销售订单,当前无法删除!";
+    public static final String MSG_032 = "您与所选企业存在未完结托运承运订单,当前无法删除!";
+    public static final String MSG_033 = "当前企业资质在审核中,无法删除!";
+    public static final String MSG_034 = "您与所选企业存在未完结对账单,当前无法删除!";
+    public static final String MSG_035 = "地址信息已存在";
+
+    public static final String ENT_EXISTS = "企业已存在,不可重复!";
+    public static final String ACCOUNT_EXISTS = "用户账号已存在!";
+    public static final String DICTTYPE_EXISTS = "字典类型已存在,不可重复!";
+    public static final String DICT_EXISTS = "字典键值已存在,不可重复!";
+    public static final String PL34 = "3PL和4PL不能同时注册!";
+
+}

+ 51 - 0
sckw-gateway/src/main/java/com/sckw/gateway/utils/GatewayExceptionUtil.java

@@ -0,0 +1,51 @@
+package com.sckw.gateway.utils;
+
+import com.sckw.gateway.pojo.HttpResult;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+/**
+ * @desc: 网关异常处理工具类
+ * @date: 2025-11-10
+ */
+@Slf4j
+public class GatewayExceptionUtil {
+
+    /**
+     * 构建服务不可用响应
+     */
+    public static HttpResult buildServiceUnavailableResponse(ServerHttpRequest request, Throwable throwable) {
+        String path = request.getPath().value();
+        String method = request.getMethod().name();
+
+        log.error("网关服务不可用异常: 路径={}, 方法={}, 异常信息={}", path, method, throwable.getMessage());
+
+        // 根据异常类型返回不同的错误信息
+        if (throwable instanceof org.springframework.cloud.gateway.support.NotFoundException) {
+            return HttpResult.error(60500, "目标服务不存在或未启动");
+        } else if (throwable instanceof org.springframework.cloud.gateway.support.TimeoutException) {
+            return HttpResult.error(60500, "服务响应超时,请稍后重试");
+        } else if (throwable instanceof org.springframework.web.server.ResponseStatusException) {
+            org.springframework.web.server.ResponseStatusException statusException =
+                    (org.springframework.web.server.ResponseStatusException) throwable;
+            if (statusException.getStatusCode().value() == 503) {
+                return HttpResult.error(60500, "服务暂时不可用,请稍后重试");
+            }
+        }
+
+        // 默认返回服务不可用
+        return HttpResult.error(60500, "服务暂时不可用,请稍后重试");
+    }
+
+    /**
+     * 构建网关内部错误响应
+     */
+    public static HttpResult buildInternalErrorResponse(ServerHttpRequest request, Throwable throwable) {
+        String path = request.getPath().value();
+        String method = request.getMethod().name();
+
+        log.error("网关内部错误: 路径={}, 方法={}, 异常信息={}", path, method, throwable.getMessage(), throwable);
+
+        return HttpResult.error(60500, "网关内部错误,请联系管理员");
+    }
+}