瞧瞧人家用SpringBoot写的后端API接口,那叫一个优雅!

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

19aabff8bde84ad8ebc7efecbf9726ba.png


日常工作中,我们开发接口时,一般都会涉及到参数校验、异常处理、封装结果返回 等处理。如果每个后端开发在参数校验、异常处理等都是各写各的,没有统一处理的话,代码就不优雅,也不容易维护。所以,作为一名合格的后端开发工程师,我们需要统一校验参数,统一异常处理、统一结果返回 ,让代码更加规范、可读性更强、更容易维护。

  • 使用注解,优雅进行参数校验

  • 统一结果返回

  • 统一异常处理

  • 唠叨几句

1. 使用注解,统一参数校验

假设小田螺 实现一个注册用户的功能,在controller 层,他会先进行校验参数,如下:

@RestController
@RequestMapping
public class UserController {

    @RequestMapping("addUser")
    public String addUser(UserParam userParam) {

        if (StringUtils.isEmpty(userParam.getUserName())) {
            return "用户名不能为空";
        }
        if (StringUtils.isEmpty(userParam.getPhone())) {
            return "手机号不能为空";
        }
        if (userParam.getPhone().length() > 11) {
            return "手机号不能超过11";
        }
        if (StringUtils.isEmpty(userParam.getEmail())) {
            return "邮箱不能为空";
        }

        //省略其他参数校验

        //todo 插入用户信息表
        return "SUCCESS";
    }

}

以上代码有什么问题嘛?其实没什么问题,就是校验有点辣眼睛 。正常的添加用户业务还没写,参数校验就一大堆啦。假设后来,小田螺又接了一个需求:编辑用户信息。实现编辑用户信息前,也是先校验信息,如下:

@RequestMapping("editUser")
public String editUser(UserParam userParam) {

    if (StringUtils.isEmpty(userParam.getUserName())) {
        return "用户名不能为空";
    }
    if (StringUtils.isEmpty(userParam.getPhone())) {
        return "手机号不能为空";
    }
    if (userParam.getPhone().length() > 11) {
        return "手机号不能超过11";
    }
    
    if (StringUtils.isEmpty(userParam.getEmail())) {
        return "邮箱不能为空";
    }

    //省略其他参数校验

    //todo 编辑用户信息表
    return "SUCCESS";
}

我们可以使用注解的方式,来进行参数校验,这样代码更加简洁,也方便统一管理。实际上, spring boot有个validation的组件,我们可以拿来即用。引入这个包即可:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

引入包后,参数校验就非常简洁啦,如下:

public class UserParam {

    @NotNull(message = "用户名不能为空")
    private String userName;

    @NotNull(message = "手机号不能为空")
    @Max(value = 11)
    private String phone;

    @NotNull(message = "邮箱不能为空")
    private String email;

然后在UserParam参数对象中,加入@Validated注解哈,把错误信息接收到BindingResult对象,代码如下:

@RequestMapping("addUser")
    public String addUser(@Validated UserParam userParam, BindingResult result) {
        
        List<FieldError> fieldErrors = result.getFieldErrors();
        if (!fieldErrors.isEmpty()) {
            return fieldErrors.get(0).getDefaultMessage();
        }

        //todo 插入用户信息表
        return "SUCCESS";
    }

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

2. 接口统一响应对象返回

如果你在你们项目代码中,看到controller 层报文返回结果,有这样的:

@RequestMapping("/hello")
public String getStr(){
  return "hello,捡田螺的小男孩";
}

//返回
hello,捡田螺的小男孩

也有这样的:

@RequestMapping("queryUser")
public UserVo queryUser(String userId) {
    return new UserVo("666", "捡田螺的小男孩");
}
//返回:
{"userId":"666","name":"捡田螺的小男孩"}

显然,如果接口返回结果不统一,前端处理就不方便,我们代码也不好维护。再比如小田螺 喜欢用Result处理结果,大田螺 喜欢用Response处理结果,可以想象一下,这些代码有多乱。

所以作为后端开发,我们项目的响应结果,需要统一标准的返回格式 。一般一个标准的响应报文对象,都有哪些属性呢?

  • code :响应状态码

  • message :响应结果描述

  • data:返回的数据

响应状态码一般用枚举表示哈:

public enum CodeEnum {

    /**操作成功**/
    SUCCESS("0000","操作成功"),
    /**操作失败**/
    ERROR("9999","操作失败"),;

    /**
     * 自定义状态码
     **/
    private String code;
    /**自定义描述**/
    private String message;

    CodeEnum(String code, String message){
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

因为返回的数据类型不是确定的,我们可以使用泛型,如下:

/**
 * @author 捡田螺的小男孩
 * @param <T>
 */
public class BaseResponse<T> {

    /**
     * 响应状态码(0000表示成功,9999表示失败
     */
    private String code;

    /**
     * 响应结果描述
     */
    private String message;

    /**
     * 返回的数据
     */
    private T data;

    /**
     * 成功返回
     * @param data
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> success(T data) {
        BaseResponse<T> response= new BaseResponse<>();
        response.setCode(CodeEnum.SUCCESS.getCode());
        response.setMessage(CodeEnum.SUCCESS.getMessage());
        response.setData(data);
        return response;
    }

    /**
     *  失败返回
     * @param code
     * @param message
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> fail(String code, String message) {
        BaseResponse<T> response = new BaseResponse<>();
        response.setCode(code);
        response.setMessage(message);
        return response;
    }
    
    public void setCode(String code) {
        this.code = code;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setData(T data) {
        this.data = data;
    }
}

有了统一的响应体,我们就可以优化一下controller 层的代码啦:

@RequestMapping("/hello")
public BaseResponse<String> getStr(){
    return BaseResponse.success("hello,捡田螺的小男孩");
}
//output
{"code":"0000","message":"操作成功","data":"hello,捡田螺的小男孩"}

@RequestMapping("queryUser")
public BaseResponse<UserVo> queryUser(String userId) {
    return BaseResponse.success(new UserVo("666", "捡田螺的小男孩"));
}
//output
{"code":"0000","message":"操作成功","data":{"userId":"666","name":"捡田螺的小男孩"}}

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

3. 统一异常处理

日常开发中,我们一般都是自定义统一的异常类,如下:

public class BizException extends RuntimeException {

    private String retCode;

    private String retMessage;

    public BizException() {
        super();
    }

    public BizException(String retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public String getRetCode() {
        return retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }
}

在controller 层,很可能会有类似代码:

@RequestMapping("/query")
public BaseResponse<UserVo> queryUserInfo(UserParam userParam) {
     try {
        return BaseResponse.success(userService.queryUserInfo(userParam));
    } catch (BizException e) {
        //doSomething
    } catch (Exception e) {
        //doSomething
    }
    return BaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());
}

这块代码,没什么问题哈,但是如果try...catch太多,不是很优雅。

可以借助注解@RestControllerAdvice,让代码更优雅。@RestControllerAdvice是一个应用于Controller层的切面注解,它一般配合@ExceptionHandler注解一起使用,作为项目的全局异常处理。我们来看下demo代码哈。

还是原来的UserController,和一个会抛出异常的userService的方法,如下:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/query")
    public BaseResponse<UserVo> queryUserInfo1(UserParam userParam) {
        return BaseResponse.success(userService.queryUserInfo(userParam));
    }
}

@Service
public class UserServiceImpl implements UserService {
    //抛出异常
    @Override
    public UserVo queryUserInfo(UserParam userParam) throws BizException {
        throw new BizException("6666", "测试异常类");
    }
}

我们再定义一个全局异常处理器,用@RestControllerAdvice注解,如下:

@RestControllerAdvice(annotations = RestController.class)
public class ControllerExceptionHandler {
}

我们有想要拦截的异常类型,比如想拦截BizException类型,就新增一个方法,使用@ExceptionHandler注解修饰,如下:

@RestControllerAdvice(annotations = RestController.class)
public class ControllerExceptionHandler {

    @ExceptionHandler(BizException.class)
    @ResponseBody
    public BaseResponse<Void> handler(BizException e) {
        System.out.println("进入业务异常"+e.getRetCode()+e.getRetMessage());
        return BaseResponse.fail(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMessage());
    }
}

唠叨几句

本文大家学到了哪些知识呢?

  1. 为了写出更优雅、更简洁、更容易维护的代码,我们需要统一参数校验、统一响应对象返回、统一异常处理

  2. 参数校验更简洁,可以使用注解实现。

  3. 如何统一响应对象返回,一般要包括状态码、描述信息、返回数据。

  4. Controller层如何统一全局异常处理?@RestControllerAdvice+@ExceptionHandler

  5. 进阶篇?大家可以自己实现自定义注解哈,也建议去看看@RestControllerAdvice实现原理,它其实就是一个切面注解,看下它的源码即可。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

8f3b6855643d9b133464b86ad6adbbe1.png

已在知识星球更新源码解析如下:

0abb89f41be8593f1dbc15cb22bc5e6d.png

c6477d9389feb71d29eb9533833381c7.png

7e05468b7dc5f36815f273782814f3d1.png

2dfa6fba5d76badf9f0d44d4e35446c5.png

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值