SpringCloudAlibaba:从0搭建一套快速开发框架-05 公共模块(common)编写与优化:提升开发效率实践

序言:上篇主要介绍了OpenFeign的集成使用,本篇主要优化完善公共模块。

公共模块介绍

本片主要针对于我个人创建的公共模块(shine-common)展开。

shine-common 模块的总体功能可以概括为提供系统中的基础通用功能和工具,旨在为其他模块和服务提供一些重复使用的逻辑、工具方法以及规范化的处理方式。具体功能如下:

  1. 基础常量与配置支持
    • 提供项目中需要的常量定义和全局配置项。这样可以避免硬编码常量,统一管理常量和配置信息,提升代码的可维护性。
  2. 请求上下文管理
    • 提供请求上下文的管理功能,帮助系统在处理请求时获取、传递和修改当前请求相关的信息。这对于跨层级或跨服务的调用非常重要,确保请求链路的上下文能够在系统中保持一致。
  3. 标准化的异常处理
    • 提供统一的异常类和全局异常处理机制,确保系统在发生异常时能够有一致的响应格式。异常类涵盖系统级别、业务级别和安全相关的异常,并且通过全局异常处理器来统一管理和返回错误信息。.
  4. 枚举与状态管理
    提供系统中的枚举类型。枚举有助于规范化一些固定值的使用,避免在代码中出现“魔法数字”,提高可读性和可维护性。
  5. 数据封装与响应格式统一
    提供标准的数据响应封装机制,使得所有接口返回的数据格式一致。
  6. 工具类
    提供各种常用的工具类,如文件处理工具、数据转换工具、时间处理工具等,减少在项目中重复编写相同功能代码的需求。比如,Excel 工具类可以提供数据导入导出的功能。

总体来说,shine-common 模块的设计目标是将项目中常用的功能和逻辑提取成通用模块,避免代码重复,提高系统的可扩展性、可维护性和开发效率。

代码

包创建

创建以下包:
com.shine.common.constant:存放常量,避免重复定义。
com.shine.common.context:上下文处理。
com.shine.common.enums:统一枚举接口。
com.shine.common.exception:统一异常基类。
com.shine.common.handler:全局处理器。
com.shine.common.response:统一接口返回数据格式。
com.shine.common.status:通用状态码以及错误信息。
com.shine.common.utils:存放公共工具类,方便开发。

类创建

通用常量

com.shine.common.constant下创建接口:BaseConstant,内容如下

public interface BaseConstant {

    String TOKEN_HEADER_KEY = "Authorization";

}

为了存放常量,尽量避免重复声明。

上下文

com.shine.common.context下创建类:ShineRequestContext,内容如下

public class ShineRequestContext {

    /**
     * 获取当前的请求
     *
     * @return
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }

    /**
     * 获取请求Token
     *
     * @return
     */
    public static String getToken() {
        HttpServletRequest request = getRequest();
        String token = request.getHeader(BaseConstant.TOKEN_HEADER_KEY);
        if (StringUtils.isBlank(token)) {
            return null;
        }
        if (token.startsWith("Bearer ")) {
            return token.replace("Bearer ", "");
        }
        return "";
    }

}

getRequest() 方法:
- 该方法用于获取当前的 HTTP 请求对象(HttpServletRequest)。
- 它通过 RequestContextHolder.getRequestAttributes() 获取当前线程绑定的请求属性。RequestContextHolder 是 Spring 提供的一个类,负责获取当前请求的上下文。在 Spring Web 环境中,每个请求的上下文都会绑定到当前线程中,允许通过它获取请求的相关信息。
- ServletRequestAttributes 是 RequestContextHolder.getRequestAttributes() 方法返回的对象,它包含了与当前请求相关的所有信息。
- getRequest() 方法最终返回当前请求对象。

getToken() 方法:
- 该方法用于从当前请求的头部获取 token。
- 首先,它调用 getRequest() 方法获取当前的 HTTP 请求。
- 然后,通过 request.getHeader(TOKEN_HEADER_KEY) 从请求头中获取 token,TOKEN_HEADER_KEY 是 “Authorization”,即获取 Authorization 头字段的值。
- 如果获取到的 token 是空值或为空字符串,方法返回 null。
- 如果 token 以 "Bearer " 开头,表示这是一个 Bearer 类型的 token。getToken() 方法会去除 "Bearer " 前缀并返回实际的 token。
- 如果 token 不是 Bearer 类型,则返回空字符串 “”。

这个类的主要功能是简化从 HTTP 请求中获取请求对象和 token 的操作。getRequest() 用于获取当前的请求对象,getToken() 用于从请求头中提取出 token,尤其是处理 Bearer 类型的 token,用于认证和授权机制中。(后面我们会单独实现一个支持oauth授权的单独模块,所以这个类主要功能就是提供下游服务获取到token)

通用枚举

com.shine.common.enums下创建接口:IEnum,内容如下

/**
 * 通用枚举接口
 */
public interface IEnum<T> {

	/**
     * 获取枚举值
     * 
     * @return
     */
    T getCode();

    /**
     * 获取枚举说明
     * 
     * @return
     */
    String getName();

}

这个接口 IEnum 是一个通用的枚举接口,定义了获取枚举值和枚举说明的方法。它旨在提供一个统一的接口,使得不同的枚举类都可以遵循相同的规范,方便系统中的代码处理各种枚举类型。

1.泛型 T:
IEnum 接口是一个泛型接口,T 表示枚举值的类型。不同的枚举类可以根据自己的需求定义枚举值的类型,例如可以是 Integer、String 或其他类型。
2. getCode() 方法:
该方法用于获取枚举值,返回类型为 T。T 可以是任何类型,例如 Integer 或 String,它代表枚举常量的实际值。
这个方法通常用来获取枚举常量的代码值或标识符。比如在应用中,可能需要根据这个值来进行匹配或判断。
3. getName() 方法:
该方法用于获取枚举的说明或名称,返回类型为 String。
这个方法通常用来获取枚举常量的文字描述,提供对枚举值的解释。
为什么使用这个接口?
统一规范:
如果项目中有多个枚举类,IEnum 接口提供了一种标准化的方式来定义枚举类,使得所有枚举类都遵循相同的结构和方法,便于代码的一致性和扩展。
类型安全:
泛型 T 确保了枚举值的类型安全。在实际使用中,getCode() 方法可以返回任意类型的值,不会出现类型转换错误。
方便使用:
通过 getCode() 获取枚举的实际值,getName() 获取枚举的描述或名称。这样,在使用枚举时,可以非常方便地访问它的值和对应的名称。

主要原因还是我设计的异常枚举和表枚举他们对应的code值不统一,异常枚举code是六位数字,但是数字不可以以0开头,所以就定义为字符串了,存的内容还是6位数字的字符串,表枚举的code全都是数字类型Integer的。

通用异常

com.shine.common.exception下创建通用异常接口:IExceptionBaseExceptionSecurityException,内容如下:

通用异常接口

public interface IException<T> {

    /**
     * 获取错误的枚举
     *
     * @return 通用异常枚举
     */
    IEnum<T> getEnum();

    /**
     * 获取异常信息
     *
     * @return
     */
    String getMessage();

    /**
     * 格式化异常信息:编码+内容
     * 
     * @return
     */
    default String formatMessage() {
        return String.format("[%s] %s", getEnum().getCode(), getMessage());
    }

}

业务异常,总异常

public class BaseException extends RuntimeException implements IException<String> {

    private final IEnum<String> iEnum;

    public BaseException(IEnum<String> iEnum) {
        super(iEnum.getName());
        this.iEnum = iEnum;
    }

    public BaseException(IEnum<String> iEnum, String message) {
        super(message);
        this.iEnum = iEnum;
    }

    /**
     * 获取错误的枚举
     *
     * @return 通用异常枚举
     */
    @Override
    public IEnum<String> getEnum() {
        return iEnum;
    }

    @Override
    public String getMessage() {
        return super.getMessage();
    }

}

关于安全的异常

public class SecurityException extends RuntimeException implements IException<String> {

    private final IEnum<String> iEnum;

    public SecurityException(IEnum<String> iEnum) {
        super(iEnum.getName());
        this.iEnum = iEnum;
    }

    public SecurityException(IEnum<String> iEnum, String message) {
        super(message);
        this.iEnum = iEnum;
    }

    /**
     * 获取错误的枚举
     *
     * @return 通用异常枚举
     */
    @Override
    public IEnum<String> getEnum() {
        return iEnum;
    }

    @Override
    public String getMessage() {
        return super.getMessage();
    }

}

主要解释一下接口为什么这么定义,其实也是为了兼容IEnum的泛型,每一个异常类都需要一个实现了IEnum的实现类来表示状态码。严格来说一个异常类只能用一个与之对应的枚举。

响应码

com.shine.common.status下创建通用响应码ResponseStatus,内容如下:

public enum ResponseStatus implements IEnum<String> {

    /**
     * 请求成功
     */
    SUCCESS("000000", "成功"),

    /**
     * 参数错误
     */
    PARAMS_ERROR("100000", "你的参数不对哦~"),

    /**
     * 权限不足
     */
    UNAUTHORIZED("200000", "你没有权限访问哦~"),

    /**
     * 远程调用错误
     */
    FEIGN_ERROR("300000", "远程调用失败~"),

    /**
     * 服务器未知异常
     */
    ERROR("900000", "服务器出错啦,请稍后重试~"),

    ;

    private final String code;

    private final String name;

    ResponseStatus(String code, String name) {
        this.code = code;
        this.name = name;
    }


    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getName() {
        return name;
    }

}

这个类是通用的业务异常基类,后续如果有具体的更精确的错误,会沿用对应响应码。

统一响应格式

com.shine.common.response下创建通用响应码类:Result,内容如下:

@Data
@NoArgsConstructor
public class Result<T> {

    private String code;

    private String message;

    private T data;

    private Boolean success;

    public Result(String code, String message, T data, Boolean success) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.success = success;
    }

    public static <T> Result<T> exec(String code, String message, T data, Boolean success) {
        return new Result<>(code, message, data, success);
    }

    public static <T> Result<T> exec(String code, String message, Boolean success) {
        return exec(code, message, null, success);
    }

    public static <T> Result<T> exec(IEnum<String> status, T data, Boolean success) {
        return exec(status.getCode(), status.getName(), data, success);
    }

    public static <T> Result<T> success(T data) {
        return exec(ResponseStatus.SUCCESS, data, true);
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(ResponseStatus status, T data) {
        return exec(status, data, false);
    }

    public static <T> Result<T> error() {
        return exec(ResponseStatus.ERROR, null, false);
    }

    public static <T> Result<T> error(IEnum<String> status) {
        return exec(status, null, false);
    }

    public static <T> Result<T> error(String code, String message) {
        return exec(code, message, false);
    }

}

这个 Result 类是一个通用的响应类,用于统一后端服务返回给前端的数据结构。它封装了响应的状态码、消息、数据以及操作是否成功的标志,通过这种方式,前端可以通过标准化的格式来处理所有的响应。

类的主要作用

  • 统一响应格式:无论是成功还是失败的响应,都会以统一的结构返回。前端只需要解析一个固定格式的 JSON 响应即可,无需为不同的接口编写不同的处理逻辑。
  • 携带必要信息:响应不仅仅包含业务数据,还会带上状态码和描述信息,帮助前端了解请求的处理结果,并做出相应的操作。
  • 灵活的数据泛型支持:类使用了泛型 T,可以根据不同的业务场景返回不同类型的数据。

类的主要字段
code:表示响应的状态码,通常用来指示请求的处理结果(例如成功、失败、参数错误等)。
message:对响应状态的描述信息,可以提供更多的上下文信息供前端参考。
data:实际的业务数据,类型为泛型 T,可以灵活地适应不同的返回数据结构。
success:布尔值,表示操作是否成功。

统一异常处理

com.shine.common.handler下创建全局异常处理类:GlobalExceptionHandler,内容如下:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理安全相关异常
     * 
     * @param e
     * @return
     */
    @ExceptionHandler(value = SecurityException.class)
    @org.springframework.web.bind.annotation.ResponseStatus(code = HttpStatus.UNAUTHORIZED, value = HttpStatus.UNAUTHORIZED)
    public Result<?> authExceptionHandler(SecurityException e) {
        log.error("权限处理异常:[{}]|[{}]|[{}]", e.getEnum().getCode(), e.getEnum().getName(), e.getMessage());
        e.printStackTrace();
        return Result.error(e.getEnum().getCode(), e.getMessage());
    }

    /**
     * 统一业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = BaseException.class)
    public Result<?> baseExceptionHandler(BaseException e) {
        log.error("业务处理异常:[{}]|[{}]|[{}]", e.getEnum().getCode(), e.getEnum().getName(), e.getMessage());
        e.printStackTrace();
        return Result.error(e.getEnum().getCode(), e.getMessage());
    }

    /**
     * 统一处理运行时异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = RuntimeException.class)
    public Result<?> runtimeExceptionHandler(RuntimeException e) {
        log.error("运行异常:{}", e.getMessage());
        e.printStackTrace();
        return Result.error(ResponseStatus.ERROR, e.getMessage());
    }

}

当我们开发一个系统时,异常处理是非常重要的一部分。它不仅帮助我们捕获程序中出现的错误,还能保证系统的稳定性和用户的良好体验。这个类会在遇到不同类型的异常时,统一处理并返回清晰的错误信息。

在这段代码中,我们实现了一个 全局异常处理器,它的主要功能是捕获应用中的不同异常并根据异常类型做出相应处理。这个处理器使用了 @RestControllerAdvice 和 @ExceptionHandler 注解,这些是 Spring 提供的非常强大的异常处理工具。下面,我将逐一介绍这段代码的实现及其背后的思想。

  1. 类级别注解 @RestControllerAdvice

    • 首先,@RestControllerAdvice 注解标记了这个类是一个全局的异常处理器,Spring 会自动将这个类注册为全局异常处理器。它是 @ControllerAdvice 和 @ResponseBody 的组合,意味着我们可以统一处理控制器中的异常,并直接返回 JSON 格式的错误信息。
  2. @Slf4j 注解

    • 这个注解来自 Lombok,它为类生成了一个 log 对象,方便我们在代码中记录日志。日志记录对于后期的错误排查和追踪非常重要,尤其是在处理异常时,详细的日志信息能够帮助我们快速定位问题。
  3. 异常处理方法

    • 我们的全局异常处理器包含了三个方法,每个方法都对应一种特定类型的异常。
    • 处理安全异常 (SecurityException):
      • @ExceptionHandler(value = SecurityException.class) 表示当抛出 SecurityException 异常时,Spring 会调用这个方法进行处理。通常这种异常与权限相关,例如用户未授权访问某些资源时抛出。
      • 我们使用 @ResponseStatus 来设置 HTTP 响应的状态码为 401 Unauthorized,这个状态码是权限错误的标准响应。
      • 在方法内部,我们记录了异常的详细日志,记录了异常的代码、名称和消息,并通过 e.printStackTrace() 打印堆栈信息,便于开发人员调试。
      • 最后,我们使用 Result.error() 方法返回标准化的错误信息,确保前端能够统一处理错误。
    • 处理业务异常 (BaseException):
      • BaseException 是我们定义的业务异常类,代表业务处理过程中发生的错误。通过 @ExceptionHandler(value = BaseException.class) 注解,当抛出 BaseException 时会调用这个方法。
      • 与安全异常的处理类似,我们记录了异常的详细日志,并返回了统一格式的错误信息。
    • 处理运行时异常 (RuntimeException):
      • 最后,我们还处理了 RuntimeException,它是 Java 中最常见的运行时异常。通常这类异常不会被显式声明,而是在代码执行时发生错误。我们通过 @ExceptionHandler(value = RuntimeException.class) 来捕获这类异常。
      • 在这个处理器中,我们也记录了异常日志并返回了一个标准化的错误响应。
  4. 返回统一的错误响应

    • 在每个异常处理方法中,我们都调用了 Result.error() 方法来返回一个标准化的错误响应。这样做的好处是,所有的错误响应格式一致,前端在处理这些响应时可以统一解析和展示错误信息。
    • Result.error() 方法接受错误码和错误消息,确保我们能够清晰地表达出错误的类型和原因。

最后我们在搞一个好玩的

shine-common模块的resource目录下创建一个配置文件:application.properties,内容如下

spring-cloud.version=(v2022.0.3)
spring-cloud-alibaba.version=(v2022.0.0.0)

主要是定义依赖版本的配置,只是显示的配置,真正的版本在父工程的POM内

resource下面继续创建一个文件banner.txt,内容如下(当然你也可以自定义)

${AnsiColor.MAGENTA}

   _____   _    _   _____   _   _   ______             _____   _         ____    _    _   _____     ${AnsiColor.BRIGHT_RED}
  / ____| | |  | | |_   _| | \ | | |  ____|           / ____| | |       / __ \  | |  | | |  __ \    ${AnsiColor.YELLOW}
 | (___   | |__| |   | |   |  \| | | |__     ______  | |      | |      | |  | | | |  | | | |  | |   ${AnsiColor.BRIGHT_GREEN}
  \___ \  |  __  |   | |   | . ` | |  __|   |______| | |      | |      | |  | | | |  | | | |  | |   ${AnsiColor.BRIGHT_CYAN}
  ____) | | |  | |  _| |_  | |\  | | |____           | |____  | |____  | |__| | | |__| | | |__| |   ${AnsiColor.BLUE}
 |_____/  |_|  |_| |_____| |_| \_| |______|           \_____| |______|  \____/   \____/  |_____/    ${AnsiColor.BRIGHT_MAGENTA}

${application.formatted-version}
spring-boot:${spring-boot.formatted-version}
spring-cloud: ${spring-cloud.version}
spring-cloud-alibaba: ${spring-cloud-alibaba.version}

这样启动我们的system服务,控制台就会出现如下页面:

SpringCloudAlibaba:从0搭建一套快速开发框架-05 公共模块(common)编写与优化:提升开发效率实践_第1张图片

给大家几个网站,可以去试试,没什么用但是很好玩。

文字风格

http://www.network-science.de/ascii/
http://patorjk.com/software/taag/
https://www.bootschool.net/ascii

图片风格(图转文字)

https://www.degraeve.com/img2txt.php
https://www.bootschool.net/ascii-art/animals

我们下期见~

你可能感兴趣的:(SpringCloud,2022,java,spring,cloud,spring,boot,微服务)