序言:上篇主要介绍了OpenFeign的集成使用,本篇主要优化完善公共模块。
本片主要针对于我个人创建的公共模块(shine-common)展开。
shine-common
模块的总体功能可以概括为提供系统中的基础通用功能和工具,旨在为其他模块和服务提供一些重复使用的逻辑、工具方法以及规范化的处理方式。具体功能如下:
总体来说,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
下创建通用异常接口:IException
、BaseException
、SecurityException
,内容如下:
通用异常接口
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 类是一个通用的响应类,用于统一后端服务返回给前端的数据结构。它封装了响应的状态码、消息、数据以及操作是否成功的标志,通过这种方式,前端可以通过标准化的格式来处理所有的响应。
类的主要作用:
类的主要字段:
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 提供的非常强大的异常处理工具。下面,我将逐一介绍这段代码的实现及其背后的思想。
类级别注解 @RestControllerAdvice:
@Slf4j 注解:
异常处理方法:
返回统一的错误响应:
最后我们在搞一个好玩的
在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服务,控制台就会出现如下页面:
给大家几个网站,可以去试试,没什么用但是很好玩。
文字风格
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
我们下期见~