1 基本类

1.1 接口IBizError

public interface IBizError {
	int getCode();

	String getMessage();
}

1.2 异常处理类BizException

import lombok.Data;
import java.io.Serial;

@Data
public class BizException extends RuntimeException {
    @Serial
    private static final long serialVersionUID = 6577165386030770043L;
    /** 错误代码 */
    private IBizError bizError;
    /** 扩展信息 */
    private String extendMessage;
    private Throwable cause;

    public BizException(IBizError bizError) {
        super(String.format("[code=%s, message=%s]", bizError.getCode(), bizError.getMessage()));
        this.bizError = bizError;
    }

    public BizException(IBizError bizError, String extendMessage) {
        super(String.format("[code=%s, message=%s, extendMessage=%s]", bizError.getCode(), bizError.getMessage(), extendMessage));
        this.bizError = bizError;
        this.extendMessage = extendMessage;
    }

    public BizException(IBizError bizError, Throwable cause) {
        super(String.format("[code=%s, message=%s]", bizError.getCode(), bizError.getMessage(), cause));
        this.bizError = bizError;
        this.cause = cause;
    }
}

1.3 业务异常编码基类BizError

public class BizError implements IBizError {

    public static final IBizError SUCCESS = new BizError(200, "成功");
    public static final IBizError HTTP_201 = new BizError(201, "Accepted");
    public static final IBizError INVALID_PARAM = new BizError(600001, "参数错误");
    public static final IBizError SQL_EXECUTE_EXCEPTION = new BizError(600002, "SQL执行错误");
    public static final IBizError SYSTEM__UNCATCH_ERROR = new BizError(699999, "系统内部错误,请重试");

    protected int code;
    protected String message;

    protected BizError(int code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public int getCode() {
        return this.code;
    }

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

1.4 辅助类BizAsserts

public class BizAsserts {

    public static void isNull(Object obj, IBizError bizError) {
        isNull(obj, bizError, null);
    }

    public static void isNull(Object obj, IBizError bizError, String extendMessage) {
        if (!ObjectUtil.isNull(obj)) {
            throw new BizException(bizError, extendMessage);
        }
    }

    public static void isNotNull(Object obj, IBizError bizError) {
        isNotNull(obj, bizError, null);
    }

    public static void isNotNull(Object obj, IBizError bizError, String extendMessage) {
        if (ObjectUtil.isNull(obj)) {
            throw new BizException(bizError, extendMessage);
        }
    }

    public static void isTrue(boolean expression, IBizError bizError) {
        isTrue(expression, bizError, null);
    }

    public static void isTrue(boolean expression, IBizError bizError, String extendMessage) {
        if (!expression) {
            throw new BizException(bizError, extendMessage);
        }
    }
}

1.5 工具类ObjectUtil

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;

public class ObjectUtil {
    public static boolean isNull(Object obj) {
        if (obj == null) return true;
        else if (obj instanceof CharSequence) return ((CharSequence) obj).length() == 0;
        else if (obj instanceof Collection) return ((Collection) obj).isEmpty();
        else if (obj instanceof Map) return ((Map) obj).isEmpty();
        else if (obj.getClass().isArray()) return Array.getLength(obj) == 0;
        return false;
    }
}

1.6 全局异常处理类BizExceptionHandler

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@ControllerAdvice
public class BizExceptionHandler {
    @ResponseBody
    @ExceptionHandler(BizException.class)
    public HttpApiResponse<Object> handle(HttpServletResponse httpServletResponse, BizException ex) {
        httpServletResponse.setStatus(HttpStatus.OK.value());
        return HttpApiResponse.newErrorInstance(ex.getBizError());
    }

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public HttpApiResponse handle(HttpServletResponse httpServletResponse, Exception ex) {
        httpServletResponse.setStatus(HttpStatus.OK.value());
        BizException bex = includeBizException(ex, 0);
        if (null == bex) {
            return HttpApiResponse.newErrorInstance(BizError.SYSTEM__UNCATCH_ERROR);
        }

        return HttpApiResponse.newErrorInstance(bex.getBizError());
    }

    /**
     * 有些场景下,BizException会被其他组件/代码重新封装,所以这里通过查找n层异常来判断是否包含BizException
     *
     * @param ex
     * @param level
     * @return
     */
    private BizException includeBizException(Throwable ex, int level) {
        if (null == ex || level >= 5) {
            return null;
        }

        if (ex instanceof BizException) {
            return (BizException)ex;
        }

        return includeBizException(ex.getCause(), ++level);
    }
}

2 使用

各模块可以继承BizError来声明自己的业务编码,如

public class UamBizError extends BizError {
	public static final IBizError ACCESS_LIMIT = new UamBizError(601003, "无权限访问");

	public static class User {
		public static final IBizError USER_NOT_FOUND = new UamBizError(601004, "用户不存在");
		public static final IBizError EMAIL_INVALID = new UamBizError(601005, "email格式错误");
	}

	protected UamBizError(int code, String message) {
		super(code, message);
	}
}

3 示例

throw new BizException(BizError.INVALID_PARAM); // 显式抛出业务异常
BizAsserts.isNotNull(user, BizError.User.USER_NOT_FOUND);

4 业务异常编码设计