对于前后端分离的项目,后端对于前端发送的数据进行验证是必要的,但这不表示前端的验证就没必要,比如一些数据的格式问题可以在前端完成验证,快速返回结果给用户,提升用户体验。

这里以一个简单的用户注册服务举例,代码部分主要展示逻辑和框架。

主要思路:前端发送用户注册提交的表单信息,后端接收后在service层进行格式验证,如果格式不符合要求,则抛出相应异常,最后统一在controller层进行异常的拦截处理,返回特定的json信息。

1.接收前端数据

controller层:

  • @ResponseBody注解把前端发送的信息保存在UserDO类中,因为项目结构简单,这里直接拿DO类来接收了。
  • 返回的ResponseBean是一个封装类,用于统一返回给前端。
  • 调用的service层的userService.addUser实际上是会抛出异常的,但这里不需要用try-catch去捕获,到最后我们再统一拦截处理。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@RequestMapping("/user")
public class UserController {
    UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 处理用户注册
     *
     * @param userDO 直接用UserDO类接收前端注册信息,角色信息通过后端生成,默认都是user
     * @return 如果用户信息格式正确,返回成功的ResponseBean,如果格式不正确,返回包含错误信息的ResponseBean
     */
    @PostMapping("/register")
    public ResponseBean submitRegister(@RequestBody UserDO userDO) {
        userService.addUser(userDO);
        return ResponseBean.ok("注册成功");
    }
}

2.Service层格式验证

service层:

  • DataValidateUtils是自定义的数据格式验证类,比如手机号码格式是否正确等,具体根据需求实现即可。
  • InvalidParameterException是java自带的Runtime异常子类,可以根据需要自己写异常类。自己实现的话通常也是继承Runtime异常,先写一个项目级异常类,再用继承方式扩展开各种细分的子类。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Service
public class UserService implements UserDetailsService{
    UserMapper userMapper;

    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 添加User,需要做数据格式验证
     * @param userDO
     * @return
     */
    public int addUser(UserDO userDO) {
        if(!DataValidateUtils.ValidateUserDO(userDO)) throw new InvalidParameterException("前端发送的用户信息不符合格式");
        return userMapper.addUser(userDO);
    }
}

3.统一处理controller层异常

异常处理:

  • @RestControllerAdvice是spring提供的功能,实现了类似aop切面的功能。这个注解相当于@ControllerAdvice+@ResponseBody
  • @ExceptionHandler(InvalidParameterException.class)标注于方法上,用来对应特定的异常类,返回特定的结果。
  • 这里的ResponseBean和前面处理成功时返回的是同一个类,只是在这里用error返回,表示服务端虽然接收到了前端的数据,但是数据有问题,把问题描述返回给前端,让前端自行处理。
1
2
3
4
5
6
7
8
@RestControllerAdvice
public class GlobalExceptionAdvice {

    @ExceptionHandler(InvalidParameterException.class)
    public ResponseBean invalidParameter(){
        return ResponseBean.error("数据不合法");
    }
}

4.总结

文中用了一个很简单的例子来演示前后端分离项目后端如何进行数据验证,及验证失败时如何处理。这种处理方式有几个优点:

  • 返回结果的格式统一,不管是否验证成功,都是用同一个ResponseBean来表示结果,实际上如果验证失败时,error表示还可以添加更详细的错误代码,前后端用统一错误代码来表示对应的错误。
  • 对原代码改动较小,controller层没有改变。
  • 集中处理异常。相比于散落各处的try-catch语句,@RestControllerAdvice提供了一个统一处理异常的功能,代码可读性更好,便于维护。

最后,@RestControllerAdvice是一个很方便的注解,如果没有这个注解,使用aop虽然也可以实现类似的功能,但要麻烦很多,先定义一个针对全局controllerPointCut,然后在around里面用try-catch包裹xx.proceed(),最后在catch那里按照特定的顺序捕获各种特定异常。。。只能感慨spring帮我们做了很多。