鸡汤:
● 你不是在等待风暴过去,而是在学习如何在雨中起舞。
● 累了就靠在窗边看云,难过了就允许眼泪流下来,迷茫时就对自己说:“今天已经很努力了。”真正的勇敢,不是永不疲惫,而是带着伤痕依然选择温柔;真正的强大,不是完美无缺,而是接纳自己的脆弱后,依然愿意相信明天。

前言

前面我们封装了 common-core 通用基础包 和 实现了 mstemplate 模板服务和 gateway 网关服务,现在让我们封装一下统一模块。

一、在common 工程下创建 common-domain

怎么创建工程我就不再演示了,结果
在这里插入图片描述

1.1 封装统一状态码

1.1.1 为什么要封装统一状态码?

● 简化接口使用方开发:接口的使用方无需担心不同接口返回不同的状态码,他们可以编写统一的解析逻辑来处理相应。
● 明确性:状态码提供了一种明确的信息来表示接口的状态。与返回的信息相比,状态码能精准地指出问题所在。
● 易检索:状态码通常是数字,便于日志、监控系统或错误跟踪系统中的检索和过滤。
● 维护性:集中管理状态码使得它们更容易维护和更新。如果业务逻辑发生变化,只需要更新错误码的定义,而不需要修改每个使用它们的地方。
● 错误分类:状态码可以帮助将错误分类为不同的级别或类型,如客户端错误、服务器错误、业务逻辑错误等。
● 统一性:不同的项目可能针对于项目的特殊情况需要一些特殊的状态码,但是对于绝大多数状态码都是可以统一的。

1.1.2 在 common-domain 下创建 package

创建 com.my.commomdomain 的package
在这里插入图片描述

1.1.3 在 commomdomain 创建 domain 包

在这里插入图片描述

1.1.4 创建 ResultCode 的统一状态码枚举类并封装

在这里插入图片描述
ResultCode :

package com.my.commondomain.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 响应码
 */
@AllArgsConstructor
@Getter
public enum ResultCode {

    //---------------------------2xx

    /**
     * 操作成功
     */
    SUCCESS                     (200000, "操作成功"),

    //------------------------4xx
    //400

    /**
     * 无效的参数
     */
    INVALID_PARA                      (400000, "无效的参数"),
    /**
     * 无效的验证码
     */
    INVALID_CODE                      (400001, "无效的验证码"),

    /**
     * 错误的验证码
     */
    ERROR_CODE                        (400002, "错误的验证码"),

    /**
     * 手机号格式错误
     */
    ERROR_PHONE_FORMAT                        (400003, "手机号格式错误"),

    /**
     * 超过每日发送次数限制
     */
    SEND_MSG_OVERLIMIT                        (400004, "超过每日发送次数限制"),

    /**
     * 无效的区划
     */
    INVALID_REGION                        (400005, "无效的区划"),

    /**
     * 参数类型不匹配
     */
    PARA_TYPE_MISMATCH                      (400006, "参数类型不匹配"),

    /**
     * 账号已停用,登录失败
     */
    USER_DISABLE                      (400007, "账号已停用,登录失败"),

    //401

    /**
     * 令牌不能为空
     */
    TOKEN_EMPTY                      (401000, "令牌不能为空"),

    /**
     * 令牌已过期或验证不正确!
     */
    TOKEN_INVALID                      (401001, "令牌已过期或验证不正确!"),

    /**
     * 令牌已过期!
     */
    TOKEN_OVERTIME                      (401002, "令牌已过期!"),

    /**
     * 登录状态已过期!
     */
    LOGIN_STATUS_OVERTIME                      (401003, "登录状态已过期!"),

    /**
     * 令牌验证失败!
     */
    TOKEN_CHECK_FAILED                      (401004, "令牌验证失败!"),


    //404

    /**
     * 服务未找到!
     */
    SERVICE_NOT_FOUND                      (404000, "服务未找到"),

    URL_NOT_FOUND                      (404001, "url未找到"),

    //405

    /**
     * 请求方法不支持!
     */
    REQUEST_METNHOD_NOT_SUPPORTED                      (405000, "请求方法不支持"),

    //---------------------5xx

    /**
     * 服务繁忙请稍后重试!
     */
    ERROR                       (500000, "服务繁忙请稍后重试"),

    /**
     * 操作失败
     */
    FAILED                      (500001, "操作失败"),


    /**
     * 短信发送失败
     */
    SEND_MSG_FAILED                        (500002, "短信发送失败"),


    /**
     * 获取直传地址失败
     */
    PRE_SIGN_URL_FAILED                        (500003, "获取直传地址失败"),

    /**
     * 上传oss异常,请稍后重试
     */
    OSS_UPLOAD_FAILED                        (500004, "上传oss异常,请稍后重试"),

    /**
     * 获取地图数据失败,请稍后重试
     */
    QQMAP_QUERY_FAILED                        (500005, "获取地图数据失败,请稍后重试"),

    /**
     * 城市信息获取失败
     */
    QQMAP_CITY_UNKNOW                        (500006, "城市信息获取失败"),

    /**
     * 根据位置获取城市失败
     */
    QQMAP_LOCATE_FAILED                        (500007, "根据位置获取城市失败"),

    /**
     * 地图特性未开启
     */
    MAP_NOT_ENABLED                        (500008, "地图特性未开启,开启方式参考使用手册"),

    /**
     * 地图区划特性未开启
     */
    MAP_REGION_NOT_ENABLED                        (500009, "地图区划特性未开启,开启方式参考使用手册"),


    //---------------------枚举占位
    /**
     * 占位专用
     */
    RESERVED                  (99999999, "占位专用");

    /**
     * 响应码
     */
    private int code;

    /**
     * 响应消息
     */
    private String msg;
}

注:
这里只封装了通用的状态码,开发者可以根据自己的意愿进行修改

1.2 封装统一响应数据结构

1.2.1 为什么要封装统一响应数据结构?

● 简化接口使用方(前端)开发:接口的使用方无需担心不同接口返回不同的响应数据结构,他们可以编写统一的解析逻辑来处理响应。
● 状态码和消息:响应数据结构中包含状态码(状态码:code)和消息( msg 一般为状态码消息,也可自定义),帮助接口使用方(前端)快速识别和处理错误。
● 业务逻辑解耦:将业务逻辑与响应格式分离,使得后端开发者可以专注于业务逻辑的实现,而不必关心如何构建HTTP响应。
● 统一性:不同的项目可能针对于项目的特殊情况对响应数据结构进行微调,一般不会有大的调整。

1.2.2 在 domain 在创建 R 类

R:

package com.my.commondomain.domain;

import lombok.Getter;
import lombok.Setter;


/**
 * 响应报文封装
 *
 * @param <T> 响应数据
 */
@Getter
@Setter
public class R<T> {

    /**
     * 响应码
     */
    private int code;

    /**
     * 消息
     */
    private String msg;

    /**
     * 数据
     */
    private T data;

    /**
     * 成功响应
     *
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> success() {
        return R.buildResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), null);
    }

    /**
     * 成功响应
     * @param data 响应数据
     * @return 响应报文
     * @param <T>  数据类型
     */
    public static <T> R<T> success(T data) {
        return R.buildResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
    }

    /**
     * 成功响应
     * @param data 响应数据
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> success(String msg, T data) {
        return R.buildResult(ResultCode.SUCCESS.getCode(), msg, data);
    }

    /**
     * 失败响应
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail() {
        return R.buildResult(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg(), null);
    }

    /**
     * 失败响应
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(String msg) {
        return R.buildResult(ResultCode.ERROR.getCode(), msg, null);
    }

    /**
     * 失败响应
     * 自定义code要将code放入ResultCode中
     * @param code 响应码
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(int code, String msg) {
        return R.buildResult(code, msg, null);
    }

    /**
     * 失败响应
     * @param data 响应数据
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(T data) {
        return R.buildResult(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg(), data);
    }

    /**
     * 失败响应
     * @param data 响应数据
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(String msg, T data) {
        return R.buildResult(ResultCode.ERROR.getCode(), msg, data);
    }


    /**
     * 响应结果
     * @param data 响应数据
     * @param code 响应编码
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    private static <T> R<T> buildResult(int code, String msg, T data) {
        R<T> result = new R<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

1.3 设置自定义异常

1.3.1 为什么需要自定义异常?

在开发过程中内置异常往往不能满足我们的需求。而自定义异常可以给开发者提供明确的提示

优点:
● 语义清晰:命名直指问题,提升代码可读性与维护性
● 携带上下文:可扩展字段存储错误码、业务数据等,助力调试与日志追踪
● 将技术错误与业务逻辑解耦

1.3.2 在 common-domain 下创建 exception 包

在这里插入图片描述

1.3.3 在 exception 包下创建 ServiceException 自定义异常

在这里插入图片描述
ServiceException:

package com.my.commondomain.domain;

import lombok.Getter;
import lombok.Setter;


/**
 * 响应报文封装
 *
 * @param <T> 响应数据
 */
@Getter
@Setter
public class R<T> {

    /**
     * 响应码
     */
    private int code;

    /**
     * 消息
     */
    private String msg;

    /**
     * 数据
     */
    private T data;

    /**
     * 成功响应
     *
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> success() {
        return R.buildResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), null);
    }

    /**
     * 成功响应
     * @param data 响应数据
     * @return 响应报文
     * @param <T>  数据类型
     */
    public static <T> R<T> success(T data) {
        return R.buildResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
    }

    /**
     * 成功响应
     * @param data 响应数据
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> success(String msg, T data) {
        return R.buildResult(ResultCode.SUCCESS.getCode(), msg, data);
    }

    /**
     * 失败响应
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail() {
        return R.buildResult(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg(), null);
    }

    /**
     * 失败响应
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(String msg) {
        return R.buildResult(ResultCode.ERROR.getCode(), msg, null);
    }

    /**
     * 失败响应
     * 自定义code要将code放入ResultCode中
     * @param code 响应码
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(int code, String msg) {
        return R.buildResult(code, msg, null);
    }

    /**
     * 失败响应
     * @param data 响应数据
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(T data) {
        return R.buildResult(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg(), data);
    }

    /**
     * 失败响应
     * @param data 响应数据
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    public static <T> R<T> fail(String msg, T data) {
        return R.buildResult(ResultCode.ERROR.getCode(), msg, data);
    }


    /**
     * 响应结果
     * @param data 响应数据
     * @param code 响应编码
     * @param msg 响应消息
     * @return 响应报文
     * @param <T> 数据类型
     */
    private static <T> R<T> buildResult(int code, String msg, T data) {
        R<T> result = new R<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

二、在common 工程下创建 common-security

创建 common-security:
在这里插入图片描述

2.1 添加依赖

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.my</groupId>
        <artifactId>common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.my</groupId>
    <artifactId>common-security</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- SpringCloud Loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.my</groupId>
            <artifactId>common-domain</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.my</groupId>
            <artifactId>common-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>

    </dependencies>
</project>

2.2 封装统一异常处理

2.2.1 创建包目录

在这里插入图片描述

2.2.2 在 commonsecurity 下创建 handler 包

在这里插入图片描述

2.2.3 创建 GlobalExceptionHandler 类

在这里插入图片描述

GlobalExceptionHandler:

package com.my.commonsecurity.handler;

import com.my.commondomain.domain.R;
import com.my.commondomain.domain.ResultCode;
import com.my.commondomain.exception.ServiceException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 设置http响应码
     *
     * @param response 响应信息
     * @param errcode 响应码
     */
    private void setResponseCode(HttpServletResponse response,Integer errcode){
        int httpCode = Integer.parseInt(String.valueOf(errcode).substring(0,3));
        response.setStatus(httpCode);
    }

    /**
     * 请求方式不支持
     * @param e 异常信息
     * @param request 请求
     * @param response 响应
     * @return 异常结果
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                    HttpServletRequest request,
                                                    HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        setResponseCode(response, ResultCode.REQUEST_METNHOD_NOT_SUPPORTED.getCode());
        return R.fail(ResultCode.REQUEST_METNHOD_NOT_SUPPORTED.getCode(), ResultCode.REQUEST_METNHOD_NOT_SUPPORTED.getMsg());
    }


    /**
     * 类型不匹配异常
     *
     * @param e 异常信息
     * @param response 响应
     * @return 不匹配结果
     */
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public R<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
                                                          HttpServletResponse response) {
        log.error("类型不匹配异常",e);
        setResponseCode(response, ResultCode.PARA_TYPE_MISMATCH.getCode());
        return R.fail(ResultCode.PARA_TYPE_MISMATCH.getCode(), ResultCode.PARA_TYPE_MISMATCH.getMsg());
    }

    /**
     * url未找到异常
     *
     * @param e 异常信息
     * @param response 响应
     * @return 异常结果
     */
    @ExceptionHandler({NoResourceFoundException.class})
    public R<?> handleMethodNoResourceFoundException(NoResourceFoundException e,
                                                     HttpServletResponse response) {
        log.error("url未找到异常",e);
        setResponseCode(response, ResultCode.URL_NOT_FOUND.getCode());
        return R.fail(ResultCode.URL_NOT_FOUND.getCode(), ResultCode.URL_NOT_FOUND.getMsg());
    }

    /**
     * 业务异常
     *
     * @param e 异常信息
     * @param request 请求
     * @param response 响应
     * @return 业务异常结果
     */
    @ExceptionHandler(ServiceException.class)
    public R<?> handleServiceException(ServiceException e, HttpServletRequest request,
                                       HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生业务异常", requestURI,  e);
        setResponseCode(response,e.getCode());
        return R.fail(e.getCode(), e.getMsg());
    }

    /**
     * 参数校验异常
     * @param e 异常信息
     * @param request 请求
     * @param response 响应
     * @return 异常报文
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request,
                                                      HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生参数校验异常", requestURI, e);
        setResponseCode(response, ResultCode.INVALID_PARA.getCode());
        String message = joinMessage(e);
        return R.fail(ResultCode.INVALID_PARA.getCode(), message);
    }

    private String joinMessage(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getAllErrors();
        if (CollectionUtils.isEmpty(allErrors)) {
            return "";
        }
//        List<String> collect = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        return allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(", "));
    }

    /**
     * 参数校验异常
     * @param e 异常信息
     * @param request 请求
     * @param response 响应
     * @return 异常报文
     */
    @ExceptionHandler({ConstraintViolationException.class})
    public R<Void> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request,
                                                      HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}', 发生参数校验异常",requestURI, e);
        setResponseCode(response,ResultCode.INVALID_PARA.getCode());
        String message = e.getMessage();
        return R.fail(ResultCode.INVALID_PARA.getCode(),message);
    }


    /**
     * 拦截运行时异常
     *
     * @param e 异常信息
     * @param request 请求信息
     * @param response 响应信息
     * @return 响应结果
     */
    @ExceptionHandler(RuntimeException.class)
    public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request,
                                       HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生运行时异常.", requestURI, e);
        setResponseCode(response, ResultCode.ERROR.getCode());
        return R.fail(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg());
    }

    /**
     * 系统异常
     * @param e 异常信息
     * @param request 请求
     * @param response 响应
     * @return 响应结果
     */
    @ExceptionHandler(Exception.class)
    public R<?> handleException(Exception e, HttpServletRequest request,
                                HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生异常.", requestURI, e);
        setResponseCode(response, ResultCode.ERROR.getCode());
        return R.fail(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg());
    }
}

2.2.4 在 resources 下创建自动配置

自动配置目录 : META.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports

在这里插入图片描述
将全局异常处理器添加进自动配置目录里
在这里插入图片描述

三、在网关层添加异常处理

在这里插入图片描述
上面的统一异常处理,作用在服务层,而网关层就无能为力了,所以我们还要在网关层也安排上统一异常处理

3.1 在 gateway 工程下创建 handler 包

在这里插入图片描述

3.2 在 handler 包下创建 GatewayExceptionHandler

在这里插入图片描述
GatewayExceptionHandler:

package com.my.gateway.handler;

import com.my.commoncore.utils.JsonUtil;
import com.my.commoncore.utils.ServletUtil;
import com.my.commondomain.domain.R;
import com.my.commondomain.domain.ResultCode;
import com.my.commondomain.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.resource.NoResourceFoundException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * 网关统一异常处理
 */
@Order(-1)
@Configuration
@Slf4j
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {

    /**
     * 处理器
     *
     * @param exchange ServerWebExchange
     * @param ex 异常信息
     * @return 无
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();

        //响应已经提交到客户端,无法再对这个响应进行常规的异常处理修改了,直接返回一个包含原始异常ex的Mono.error(ex)
        if (response.isCommitted()) {
            return Mono.error(ex);
        }
        int retCode = ResultCode.ERROR.getCode();
        String retMsg = ResultCode.ERROR.getMsg();
        if (ex instanceof NoResourceFoundException) {
            retCode = ResultCode.SERVICE_NOT_FOUND.getCode();
            retMsg = ResultCode.SERVICE_NOT_FOUND.getMsg();
        } else if (ex instanceof ServiceException) {
            retCode = ((ServiceException) ex).getCode();
            retMsg = ((ServiceException) ex).getMsg();
        }

        int httpCode = Integer.parseInt(String.valueOf(retCode).substring(0,3));

        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().
                getPath(), ex.getMessage());

        // servlet 工具类
        return ServletUtil.webFluxResponseWriter(response, HttpStatus.valueOf(httpCode),retMsg, retCode);
//        return webFluxResponseWriter(response, HttpStatus.valueOf(httpCode),retMsg, retCode);
    }

    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus httpStatus, String retMsg, int retCode) {
        return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE,httpStatus,retMsg,retCode);
    }

    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String applicationJsonValue, HttpStatus httpStatus, String retMsg, int retCode) {
        response.setStatusCode(httpStatus);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,applicationJsonValue);
        R<?> result = R.fail(retCode,retMsg);
        DataBuffer wrap = response.bufferFactory().wrap(JsonUtil.toJson(result).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(wrap));
    }
}

这个 ServletUtil 工具类是后加进 common-core 里的工具类,开发就是这样的,写着写着就发现有些东西是通用的东西,可以封装一下
在这里插入图片描述

ServletUtil:

package com.my.commoncore.utils;

import com.my.commondomain.constants.CommonConstants;
import com.my.commondomain.domain.R;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * Servlet工具类
 */
public class ServletUtil {

    /**
     * 内容编码
     *
     * @param str 内容
     * @return 编码后的内容
     */
    public static String urlEncode(String str) {
        try {
            return URLEncoder.encode(str, CommonConstants.UTF8);
        } catch (UnsupportedEncodingException e) {
            return StringUtils.EMPTY;
        }
    }

    /**
     * 设置webflux模型响应
     *
     * @param response ServerHttpResponse
     * @param code     响应状态码
     * @param value    响应内容
     * @return 无
     */
    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code) {
        return webFluxResponseWriter(response, HttpStatus.OK, value, code);
    }

    /**
     * 设置webflux模型响应
     *
     * @param response ServerHttpResponse
     * @param status   http状态码
     * @param code     响应状态码
     * @param value    响应内容
     * @return 无
     */
    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) {
        //修改了
        return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
    }

    /**
     * 设置webflux模型响应
     *
     * @param response    ServerHttpResponse
     * @param contentType content-type
     * @param status      http状态码
     * @param code        响应状态码
     * @param value       响应内容
     * @return 无
     */
    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
        R<?> result = R.fail(code, value.toString());
        DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtil.toJson(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * 获取request
     *
     * @return request对象
     */
    public static HttpServletRequest getRequest() {
        try {
            return getRequestAttributes().getRequest();
        } catch (Exception e) {
            return null;
        }
    }



    /**
     * 获取request属性信息
     *  根据当前线程处理的请求获取  ServletRequestAttributes
     *  ServletRequestAttributes 包含request 和 response
     * @return request属性
     */
    public static ServletRequestAttributes getRequestAttributes() {
        try {
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            return (ServletRequestAttributes) attributes;
        } catch (Exception e) {
            return null;
        }
    }
}

四、验证

直接拿 mstemplate 模版服务来进行验证,将 common-domain 和 common-security 的依赖引入模板服务,搞一个 controller 层,拿浏览器验证一下

在这里插入图片描述
简单测试一下

TestController:

package com.my.mstemplateservice.test;

import com.my.commondomain.domain.R;
import com.my.commondomain.domain.ResultCode;
import com.my.commondomain.exception.ServiceException;
import com.my.mstemplateservice.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/test")
public class TestController {

    @GetMapping("/info")
    public void info() {
        log.info("接口调用测试");
    }

//    @GetMapping("/result")
//    public R<Void> result(int id) {
//        if(id > 0) {
//            return R.success();
//        }else {
//            return R.fail();
//        }
//    }

    @GetMapping("/result")
    public R<User> result(int id) {
        if(id <= 0) {
            return R.fail("id小于0");
        }
        User user = new User();
        user.setAge(id);
        user.setName("test");
        return R.success(user);
    }

    @GetMapping("/exception")
    public R<Void> exception(int id) {
        if (id < 0) {
            throw new ServiceException(ResultCode.ERROR_CODE);
        }
        if (id == 1) {
            throw new ServiceException("id 不能等于1");
        }
        if (id == 1000) {
            throw new ServiceException("奇奇怪怪的异常信息",344556);
        }
        return R.success();
    }
}

前面的工具类啥的也要测一下,我忘了。。。😅

通过网关调用模板服务的 controller ,检验统一异常处理的结果:

在这里插入图片描述
在这里插入图片描述


END

接下来开始组件的封装

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐