mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7mobile wallpaper 8mobile wallpaper 9mobile wallpaper 10mobile wallpaper 11mobile wallpaper 12
1132 字
3 分钟
自定义错误码 + 全局异常体系 + Result的优雅实现方案
2025-08-16

目录

一、业务需求

二、分析

三、实现方案

(一)错误码相关

1. 错误码接口

2. 抽象基础错误码

3. 错误码类型

4. 错误码具体实现

(二)异常相关

1. 抽象基础异常

2. 异常具体实现

3. 全局异常拦截器

(三)Result相关

1. Result封装

2. Result反馈类


一、业务需求#

你的团队需承担一个采用的大型新项目,现需设计一套错误码、异常以及Result方案。


二、分析#

对于错误码方案,我们一般会用枚举类或者常量类来设计,针对本项目特点让我们来分析一下:

首先这个项目是大规模的,那肯定会使用到成百甚至上千的错误码,枚举类是单例的,也就是说我们要将这成百上千的错误码都写到一个枚举类里面,先不说团队协作的易冲突性或者管理的复杂性,是编译时间就会大大增加。那有同学可能会想使用多个枚举类不就好了,这样是可以,但是枚举类是不适合常规类那一套继承体系的,它天生就是用来管理体量小的常量的。

其次,微服务架构强调。枚举类难以构建复杂的层级关系,无法很好地契合模块内业务进一步细分的需求。采用枚举类管理跨模块的错误码,其的复杂性同样会显著增加。

总结而言,枚举类适用于体量小、无需精细模块划分的项目。综合考量项目规模庞大、微服务架构对模块化和错误码管理的要求,以及错误码唯一性校验的必要性,常量类方案是本项目更适宜的选择。

而对于异常和Result的话则不作分析了,因为基本都是一个套路,这里要做的只是兼容我们的错误码方案即可。


三、实现方案#

(一)错误码相关#

1. 错误码接口#

public interface IErrorCode {
String code();
String message();
}

这里确定我们错误码的,由一个错误码和具体错误信息组成。

2. 抽象基础错误码#

public abstract class AbsErrorCode implements IErrorCode {
protected final String code;
protected final String message;
protected AbsErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
@Override
public String code() {
return code;
}
@Override
public String message() {
return message;
}
}

用于实现错误码的架构。

3. 错误码类型#

// 静态错误码实现
public class StaticErrorCode extends AbsErrorCode {
public StaticErrorCode(String code, String message) {
super(code, message);
}
}
// 动态错误码实现
public class DynamicErrorCode extends AbsErrorCode {
private final String template;
public DynamicErrorCode(String code, String template) {
super(code, template);
this.template = template;
}
@Override
public String message() {
return template;
}
public DynamicErrorCode formatMessage(Object... args) {
return new DynamicErrorCode(code,MessageFormat.format(template, args));
}
}

用于创建错误码对象,我们把错误码分为了两种,一动一静。

静态错误码就是写死的那种,直接用就可以;动态错误码中是有占位符的,根据传入的参数进行动态填充,更加灵活。

4. 错误码具体实现#

public final class UserErrorCodes {
public static final StaticErrorCode USER_ERROR = new StaticErrorCode("U000001", "请稍后再试~");
public static final DynamicErrorCode USERNAME_ALREADY_EXIST = new DynamicErrorCode("U000002", "[{0}]已存在");
public static final StaticErrorCode USER_NOT_EXIST = new StaticErrorCode("U000003", "用户不存在");
}
public final class SystemErrorCodes {
public static final StaticErrorCode SYSTEM_ERROR = new StaticErrorCode("S000001", "系统繁忙~");
}
public final class RemoteErrorCodes {
public static final StaticErrorCode REMOTE_ERROR = new StaticErrorCode("R000001", "系统繁忙~");
}

可以像我这样将错误码分为三大类供全局使用,也可以给每个模块分一个类型单独使用。

(二)异常相关#

错误码一般都是放入异常抛出,然后被全局异常给拦截。

1. 抽象基础异常#

@Getter
public abstract class AbstractException extends RuntimeException {
public final String errorCode;
public final String errorMessage;
public AbstractException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable);
this.errorCode = errorCode.code();
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.message());
}
}

提供异常对象的基础架构,跟错误码对象一致。

2. 异常具体实现#

public class UserException extends AbstractException {
public UserException (IErrorCode errorCode) {
this(null, null, errorCode);
}
public UserException (String message) {
this(message, null, UserErrorCodes.USER_ERROR);
}
public UserException (String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public UserException (String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}
@Override
public String toString() {
return "ClientException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
public class ServiceException extends AbstractException {
public ServiceException(String message) {
this(message, null, SystemErrorCodes.SERVICE_ERROR);
}
public ServiceException(IErrorCode errorCode) {
this(null, errorCode);
}
public ServiceException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public ServiceException(String message, Throwable throwable, IErrorCode errorCode) {
super(Optional.ofNullable(message).orElse(errorCode.message()), throwable, errorCode);
}
@Override
public String toString() {
return "ServiceException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}
public class RemoteException extends AbstractException {
public RemoteException(String message) {
this(message, null, RemoteErrorCodes.REMOTE_ERROR);
}
public RemoteException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}
public RemoteException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}
@Override
public String toString() {
return "RemoteException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}

每个异常要提供三种构造方法,便于应对各种情况。

3. 全局异常拦截器#

@Slf4j
@RestControllerAdvice(basePackages = "com.Yilena.Global")
public class GlobalExceptionHandler {
// 拦截参数验证异常
@SneakyThrows
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
// 获取绑定结果,用于后续处理验证错误信息
BindingResult bindingResult = ex.getBindingResult();
// 从绑定结果中获取第一个字段错误信息,以便后续处理
FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());
// 使用Optional处理可能为null的错误信息,以避免空指针异常
// 如果存在错误信息,则获取默认的错误消息;否则使用空字符串
String exceptionStr = Optional.ofNullable(firstFieldError)
.map(FieldError::getDefaultMessage)
.orElse(StrUtil.EMPTY);
// 记录错误日志,包括请求方法、请求URL和错误信息
log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
// 返回包含错误代码和错误信息的响应结果
return Results.failure(UserErrorCodes.CLIENT_ERROR.code(), exceptionStr);
}
// 拦截应用内抛出的异常
@ExceptionHandler(value = {AbstractException.class})
public Result abstractException(HttpServletRequest request, AbstractException ex) {
// 检查异常的原因是否为空
if (ex.getCause() != null) {
// 如果原因不为空,记录包含原因的错误日志,并返回失败结果
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex, ex.getCause());
return Results.failure(ex);
}
// 创建一个StringBuilder来构建异常的堆栈跟踪信息
StringBuilder stackTraceBuilder = new StringBuilder();
// 添加异常的类名和错误消息到StringBuilder中
stackTraceBuilder.append(ex.getClass().getName()).append(": ").append(ex.getErrorMessage()).append("\n");
// 获取异常的堆栈跟踪元素
StackTraceElement[] stackTrace = ex.getStackTrace();
// 遍历堆栈跟踪的前五个元素(或全部,如果少于五个),并添加到StringBuilder中
for (int i = 0; i < Math.min(5, stackTrace.length); i++) {
stackTraceBuilder.append("\tat ").append(stackTrace[i]).append("\n");
}
// 记录包含异常堆栈跟踪信息的详细错误日志,并返回失败结果
log.error("[{}] {} [ex] {} \n\n{}", request.getMethod(), request.getRequestURL().toString(), ex, stackTraceBuilder);
return Results.failure(ex);
}
// 拦截自定义以外的异常
@ExceptionHandler(value = Throwable.class)
public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
return Results.failure();
}
// 获取请求的url
private String getUrl(HttpServletRequest request) {
if (StrUtil.isEmpty(request.getQueryString())) {
return request.getRequestURL().toString();
}
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
}

着重需要注意拦截后对报错信息的处理,传参异常就得返回其错误的字段信息;自定义异常就得返回其报错原因,至于自定义以外的异常就简单处理即可,因为一般核心异常我们都会使用自定义异常对象进行处理。

(三)Result相关#

1. Result封装#

@Data
@Accessors(chain = true)
public class Result<T> implements Serializable {
@Serial
private static final long serialVersionUID = 5679018624309023727L;
// 正确返回码
public static final String SUCCESS_CODE = "0";
//返回码
private String code;
// 返回消息
private String message;
// 响应数据
private T data;
// 请求ID
private String requestId;
// 判断是否成功
public boolean isSuccess() {
return SUCCESS_CODE.equals(code);
}
}

serialVersionUID是用来保证序列化和过程中的版本一致性的,如果不显式声明的话JVM会根据类的结构自动生成一个版本号出来,但是一旦类的结构发生变化,其生成的版本号也会变化,序列化和反序列化过程中发现前后版本号不一致就会抛出异常。

所以建议给所有实现Serializable接口的类都显式声明版本号。

2. Result反馈类#

public final class Results {
/**
* 创建一个表示成功的 Result 对象,不包含任何数据
*/
public static Result<Void> success() {
return new Result<Void>()
.setCode(Result.SUCCESS_CODE);
}
/**
* 创建一个表示成功的 Result 对象,包含指定的数据
*/
public static <T> Result<T> success(T data) {
return new Result<T>()
.setCode(Result.SUCCESS_CODE)
.setData(data);
}
/**
* 创建一个表示失败的 Result 对象,使用默认的错误代码和消息
*/
public static Result<Void> failure() {
return new Result<Void>()
.setCode(SystemErrorCodes.SERVICE_ERROR.code())
.setMessage(SystemErrorCodes.SERVICE_ERROR.message());
}
/**
* 根据给定的异常创建一个表示失败的 Result 对象,如果异常的错误代码或消息为空,则使用默认的错误代码和消息
*/
static Result<Void> failure(AbstractException abstractException) {
String errorCode = Optional.ofNullable(abstractException.getErrorCode())
.orElse(SystemErrorCodes.SERVICE_ERROR.code());
String errorMessage = Optional.ofNullable(abstractException.getErrorMessage())
.orElse(SystemErrorCodes.SERVICE_ERROR.message());
return new Result<Void>()
.setCode(errorCode)
.setMessage(errorMessage);
}
/**
* 创建一个表示失败的 Result 对象,使用指定的错误代码和消息
*/
static Result<Void> failure(String errorCode, String errorMessage) {
return new Result<Void>()
.setCode(errorCode)
.setMessage(errorMessage);
}
}

码文不易,留个赞再走吧


原文链接: 自定义错误码 + 全局异常体系 + Result的优雅实现方案 作者: Yilena

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

自定义错误码 + 全局异常体系 + Result的优雅实现方案
https://blog.csdn.net/2401_88959292/article/details/150211691?spm=1001.2014.3001.5501
作者
Yilena
发布于
2025-08-16
许可协议
CC BY 4.0

部分信息可能已经过时

相关文章 智能推荐
1
基于MySQL + Redis + JWT的双令牌认证方案的实现
业务拆解 本文针对单令牌认证机制存在的安全隐患与用户体验痛点,设计并实现了一套基于MySQL、Redis与JWT的双令牌(访问令牌+刷新令牌)认证方案。文章详细分析了双令牌机制的优势,通过分离认证与状态维护职责,结合Redis黑名单与分布式锁限流,有效提升了系统安全性。同时,提供了完整的Java代码实现,包括JWT工具类、Redis令牌管理服务、全局过滤器及业务层Controller逻辑,展示了如何优雅地处理令牌刷新、单点互斥登录及异常响应,为构建安全可靠的Web应用认证体系提供了实践参考。
2
一个简单高效的秒杀方案的实现
业务拆解 本文详细设计并实现了一个简单高效的电商秒杀方案,核心目标是保证“一人一单”且“防止超卖”。方案通过新增秒杀商品表与订单表并进行水平分表来优化数据库结构;利用Redis预热库存数据、Lua脚本保证扣减原子性,结合Set结构实现一人一单校验,从而有效应对高并发。同时,引入RabbitMQ异步处理订单生成,并配置生产者确认与幂等性检查机制以防消息丢失或重复消费。文章还提供了完整的Java代码实现,包括Redis与MQ配置、雪花算法ID生成、拦截器校验及定时任务同步库存,为高并发秒杀场景提供了实用的工程参考。
3
点餐场景下:分析实现十万级用户日志的店铺推荐方案
业务拆解 本文针对点餐小程序十万级用户进店日志场景,设计并实现了一套高效的个性化店铺推荐方案。通过冷热数据分离策略,利用Elasticsearch存储近30天热数据,结合MySQL与Redis优化查询性能。文章详细阐述了基于访问频次、分区及分类偏好的加权评分排序算法,并规划了每日定时执行的离线分析任务以平衡系统开销。最后,提供了完整的实体类定义、ES数据迁移定时任务及多线程并发日志分析的Java代码实现,为海量日志分析与推荐系统落地提供了实践参考。
4
一文讲透 RAG:从定义、架构、底层原理到主流方案对比
技术笔记 本文深度剖析了检索增强生成(RAG)技术的全貌,从大模型面临的知识静态、幻觉及私有数据缺失等痛点出发,阐明了RAG作为工程化解法的核心价值。文章详细拆解了RAG的标准架构,包括离线索引(文档解析、Chunking、Embedding)与在线问答(检索、重排、上下文组装、生成)的关键模块,并揭示了其基于参数化与非参数化知识结合的底层原理。此外,针对召回不准、上下文污染等常见问题提供了优化思路,对比了Naive RAG、Modular RAG、GraphRAG及Agentic RAG等主流方案的优劣与适用场景,并特别强调了企业级应用中权限控制与知识治理的重要性,为构建生产级RAG系统提供了全面指南。
5
如何应对海量Key带来的redis内存占用问题?
业务拆解 本文针对海量Key导致的Redis内存占用过高问题,深入分析了内存碎片化与元数据开销的根源,并提出了五种切实可行的解决方案。从基础的合并小Key(利用Hash结构)与临时添加TTL救急,到架构层面的Redis集群模式与Redis on Flash(内存+SSD)降本方案,再到结合MySQL的冷热数据分离策略(全量/冷数据存DB,热数据存Redis)。文章通过详实的流程图与优劣对比,帮助开发者在不同预算与业务场景下,科学应对Redis内存瓶颈。

目录

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00