优秀的编程知识分享平台

网站首页 > 技术文章 正文

你写的controller优雅吗?(controller层要写什么)

nanyue 2024-09-03 16:18:24 技术文章 7 ℃

前言:

我们知道java后端开发遵循的是三层架构的设计模式,即控制层、业务逻辑层和数据层,分别对应的是controller、service和mapper。那么三层架构的设计优点我们都知道是各层之间进行解耦,增强了代码的可读性和易维护性。

但是,我们在很多项目中是否见过这些场景呢:

  • 见过几千行代码的 controller吗?我见过。
  • 见过全是 try catch 的 controller 吗,我见过。
  • 见过全是字段校验的 controller 吗,我见过。
  • 见过全是业务代码的 controller 吗?不好意思,我们公司很多业务写在 controller 的。

这些是不是让我们血压飙升呀。

不优雅的 controller


@RestController
@RequestMapping("/user/test")
public class UserController {

    private static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserService userService;

    @Autowired
    private AuthService authService;

    @PostMapping
    public CommonResult userRegistration(@RequestBody UserVo userVo) {
        if (StringUtils.isBlank(userVo.getUsername())){
            return CommonResult.error("用户名不能为空");
        }
        if (StringUtils.isBlank(userVo.getPassword())){
            return CommonResult.error("密码不能为空");
        }
        logger.info("注册用户:{}" , userVo.getUsername());
        try {
            userService.registerUser(userVo.getUsername());
            return CommonResult.ok();
        }catch (Exception e){
            logger.error("注册用户失败:{}", userVo.getUsername(), e);
            return CommonResult.error("注册失败");
        }
    }

    @PostMapping("/login")
    @PermitAll
    @ApiOperation("使用账号密码登录")
    public CommonResult<AuthLoginRespVO> login(@RequestBody AuthLoginReqVO reqVO) {
        if (StringUtils.isBlank(reqVO.getUsername())){
            return CommonResult.error("用户名不能为空");
        }
        if (StringUtils.isBlank(reqVO.getPassword())){
            return CommonResult.error("密码不能为空");
        }
        try {
            return success(authService.login(reqVO));
        }catch (Exception e){
            logger.error("注册用户失败:{}", reqVO.getUsername(), e);
            return CommonResult.error("注册失败");
        }
    }

}

优雅的controller

@RestController
@RequestMapping("/user/test")
public class UserController1 {

    private static Logger logger = LoggerFactory.getLogger(UserController1.class);

    @Autowired
    private UserService userService;

    @Autowired
    private AuthService authService;

    @PostMapping("/userRegistration")
    public CommonResult userRegistration(@RequestBody @Valid UserVo userVo) {
        userService.registerUser(userVo.getUsername());
        return CommonResult.ok();
    }

    @PostMapping("/login")
    @PermitAll
    @ApiOperation("使用账号密码登录")
    public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
        return success(authService.login(reqVO));
    }

}

改造流程

校验方式

这个 if 校验看得我哪哪都不爽。好歹给我写一个断言吧。Assert.notNull(userVo.getUsername(), "用户名不能为空");这不香吗?确实不太香。

那就赶快使用 spring 提供的@Valid。

在入参时使用@Valid注解,并且在 Dto中使用校验注解,如AuthLoginReqDto

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthLoginReqDto {

    @ApiModelProperty(value = "账号", required = true, example = "user")
    @NotEmpty(message = "登录账号不能为空")
    @Length(min = 4, max = 16, message = "账号长度为 4-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+#34;, message = "账号格式为数字以及字母")
    private String username;

    @ApiModelProperty(value = "密码", required = true, example = "password")
    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;

}

@Valid注解

在SpringBoot中,@Valid是一个非常有用的注解,主要用于数据校验。以下是关于@Valid的一些详细信息:

  1. 为什么使用 @Valid 来验证参数:在编写接口时,我们经常需要验证请求参数。通常,我们可能会写大量的 if 和 if else 代码来进行判断。但这样的代码不仅不优雅,而且如果存在大量的验证逻辑,这会使代码看起来混乱,大大降低代码可读性。为了简化这个过程,我们可以使用 @Valid 注解来帮助我们简化验证逻辑。
  2. @Valid 注解的作用:@Valid 的主要作用是用于数据校验,可以在定义的实体中的属性上,添加不同的注解来完成不同的校验规则,而在接口类中的接收数据参数中添加 @valid 注解,这时你的实体将会开启一个校验的功能。
  3. @Valid 的相关注解:在实体类中不同的属性上添加不同的注解,就能实现不同数据的效验功能。
  4. 使用 @Valid 进行参数校验步骤:整个过程如下,用户访问接口,然后进行参数校验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行校验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。
  5. @Validated与@Valid的区别:@Validated 是 @Valid 的变体。通过声明实体中属性的 groups ,再搭配使用 @Validated ,就能决定哪些属性需要校验,哪些不需要校验。

全局异常处理

这个全局异常处理,可以根据自己的异常,自定义异常处理,并设置一个兜底的异常处理。


@ResponseBody
@RestControllerAdvice
public class ExceptionHandlerAdvice {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
        logger.error("[handleValidationExceptions]", ex);
        StringBuilder sb = new StringBuilder();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((org.springframework.validation.FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            sb.append(fieldName).append(":").append(errorMessage).append(";");
        });
        return CommonResult.error(sb.toString());
    }

    /**
     * 处理系统异常,兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> defaultExceptionHandler(Throwable ex) {
        logger.error("[defaultExceptionHandler]", ex);
        // 返回 ERROR CommonResult
        return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
    }

}

总结

作为一个开发者,我们不仅要有严谨的逻辑思维尽量减少代码中的bug,而且我们也要有意识的让自己写的代码显得很优雅。这样也是方便自己或其他人定位问题、代码复用等。

最近发表
标签列表