学无先后,达者为师

网站首页 编程语言 正文

物联网设备数据流转之搭建后端服务框架:SpringBoot统一响应封装,全局异常拦截

作者:Heartsuit 更新时间: 2022-05-17 编程语言

背景

本篇内容相对比较简单,不涉及具体的物联网技术,是作为后端工程师的基本操作,算是对基础框架的封装工作,我们使用 SpringBootRestControllerAdviceExceptionHandler 等注解实现对客户端请求响应的统一封装以及异常信息拦截封装。

为了保证我们前端在对接服务接口时采用统一的规范,业界主流的做法便是将所有请求的结果进行封装,当然在遇到异常信息时也进行一层封装,这样可以使所有接口的响应结构是一致的,响应体的内容包括状态码与数据,当出现错误时还包含错误信息,一般地,响应体如下:

  • 正常响应
{
  "code": 200,
  "msg": "success",
  "data": "hello everyone."
}
  • 异常响应
{
  "code": 500,
  "msg": "/ by zero"
}

后续内容将在上一篇文章中 IDEA 项目的基础上,添加一个 common 包(具体的文件位置可以从下图中左侧项目结构获得)。

统一响应封装

2022-05-15-Result.jpg

通过 RestControllerAdvice 注解,实现对请求的拦截,统一封装结果为 Result

@RestControllerAdvice(basePackages = "com.heartsuit.common.controller")
public class ResultAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (o instanceof String) {
            return objectMapper.writeValueAsString(Result.success(o));
        }
        if (o instanceof Result) {
            return o;
        }
        return Result.success(o);
    }
}
  • Result.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    /** 结果状态 ,正常响应200,其他状态码都为失败*/
    private int code;
    private String msg;
    private T data;

    // Static methods
    /**
     * 成功时候的调用
     */
    public static <T> Result<T> success(T data) {
        return new Result<T>(data, CodeMsg.SUCCESS);
    }
    public static <T> Result<T> success() {
        return new Result<T>(CodeMsg.SUCCESS);
    }

    /**
     * 失败时候的调用
     */
    public static <T> Result<T> error(Integer code, String msg) {
        return new Result<T>(code, msg);
    }
    public static <T> Result<T> error(CodeMsg codeMsg) {
        return new Result<T>(codeMsg);
    }
    public static <T> Result<T> error(String msg) {
        CodeMsg codeMsg = new CodeMsg(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
        return new Result<T>(codeMsg);
    }

    // Constructor
    private Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Result(T data, CodeMsg codeMsg) {
        this.data = data;
        if (codeMsg != null) {
            this.code = codeMsg.getCode();
            this.msg = codeMsg.getMsg();
        }
    }
    private Result(CodeMsg codeMsg) {
        if (codeMsg != null) {
            this.code = codeMsg.getCode();
            this.msg = codeMsg.getMsg();
        }
    }
}
  • CodeMsg.java
@Getter
public class CodeMsg {
    private int code;
    private String msg;

    // 通用的错误码
    public static final CodeMsg SUCCESS =new CodeMsg(HttpStatus.OK.value(), "success");
    public static final CodeMsg BAD_REQUEST = new CodeMsg(HttpStatus.BAD_REQUEST.value(), "请求无效");
    public static final CodeMsg SERVER_ERROR = new CodeMsg(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务端异常");
    public static final CodeMsg NO_HANDLER_FOUND = new CodeMsg(HttpStatus.NOT_FOUND.value(), "未找到对应资源");
    public static final CodeMsg UNAUTHORIZED = new CodeMsg(HttpStatus.UNAUTHORIZED.value(), "未认证或登录状态过期");
    public static final CodeMsg FORBIDDEN = new CodeMsg(HttpStatus.FORBIDDEN.value(), "未授权");
    // 自定义错误码
    public static final CodeMsg PARAMETER_ERROR = new CodeMsg(4000, "参数不正确!");
    /*用户相关:验证码*/
    public static final CodeMsg CAPTCHA_EXPIRED = new CodeMsg(4001, "验证码不存在或已过期");
    public static final CodeMsg CAPTCHA_INVALID = new CodeMsg(4002, "验证码错误");
    /*用户相关:认证授权*/
    public static final CodeMsg BAD_CREDENTIAL = new CodeMsg(4003, "用户名或密码错误");
    public static final CodeMsg ACCOUNT_NOT_FOUND = new CodeMsg(4004, "账号不存在");
    public static final CodeMsg ACCOUNT_NOT_ACTIVATED = new CodeMsg(4005, "账号未激活");
    // 限流
    public static final CodeMsg RATE_LIMIT = new CodeMsg(4006,"达到阈值啦!");
    // 熔断
    public static final CodeMsg DEGRADE = new CodeMsg(4007,"熔断啦!");

    public static CodeMsg error(String msg){
        return new CodeMsg(HttpStatus.BAD_REQUEST.value(),msg);
    }
    public CodeMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

全局异常拦截

2022-05-15-Exception.jpg
默认拦截所有异常(也可自定义异常进行封装),同样通过 RestControllerAdvice 注解,实现对异常响应的统一封装。

  • RestExceptionHandler.java
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<String> exception(Exception e) {
        log.error("Global exception: {}", null == e.getMessage() ? e.toString() : e.getMessage(), e);
        return Result.error(CodeMsg.SERVER_ERROR.getCode(), null == e.getMessage() ? e.toString() : e.getMessage());
    }
}

用于测试的Controller

@RestController
@RequestMapping("demo")
public class HelloController {
    @GetMapping("hello")
    public String hello() {
        return "hello everyone.";
    }

    @GetMapping("error")
    public Result error() {
        int value = 8 / 0;
        return Result.success(value);
    }
}

小总结

至此,我们在基于 SpringBoot 的后端服务中完成了响应封装、异常拦截。下一篇我们的主角 TDengine 将闪亮登场: TDengine 集成 SpringBoot , MyBatisPlus 实现 ORM 与 基础的 CRUD 功能。


If you have any questions or any bugs are found, please feel free to contact me.

Your comments and suggestions are welcome!

原文链接:https://blog.csdn.net/u013810234/article/details/124787685

栏目分类
最近更新