转自:https://juejin.cn/post/7357172505961578511
见过几千行代码的 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));
}
}
代码量直接减一半呀,这还不算上有些直接把业务逻辑写在 controller 的,看到这些我真的直接吐血
改造流程
校验方式
这个 if 校验看得我哪哪都不爽。好歹给我写一个断言吧。Assert.notNull(userVo.getUsername(), "用户名不能为空");
这不香吗?确实不香。
使用 spring 提供的@Valid
在入参时使用@Valid注解,并且在 vo 中使用校验注解,如AuthLoginReqVO
@ApiModel(value = "管理后台 - 账号密码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthLoginReqVO {
@ApiModelProperty(value = "账号", required = true, example = "user")
@NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", 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的一些详细信息:
为什么使用 @Valid 来验证参数: 在编写接口时,我们经常需要验证请求参数。通常,我们可能会写大量的 if 和 if else 代码来进行判断。但这样的代码不仅不优雅,而且如果存在大量的验证逻辑,这会使代码看起来混乱,大大降低代码可读性。为了简化这个过程,我们可以使用 @Valid 注解来帮助我们简化验证逻辑。 @Valid 注解的作用: @Valid 的主要作用是用于数据效验,可以在定义的实体中的属性上,添加不同的注解来完成不同的校验规则,而在接口类中的接收数据参数中添加 @valid 注解,这时你的实体将会开启一个校验的功能。 @Valid 的相关注解: 在实体类中不同的属性上添加不同的注解,就能实现不同数据的效验功能。 使用 @Valid 进行参数效验步骤: 整个过程如下,用户访问接口,然后进行参数效验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。 @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());
}
}
就这么多,搞定,这样就拥有了漂流优雅的 controller 了
在日常开发中,还有那些血压飙升瞬间
我拿出下图阁下如何面对
这个阁下又如何面对,我不说,你能知道这个什么吗【狗头】
看了这篇文章之后,我甚至以为这不就是我写的代码么,其实文章中介绍的Valid校验、全局异常处理,还有一些统一参数封装,统一异常体系等等,在我自己出的高并发实战项目中其实都有用到。
但是这其实没啥好吹的,只能说都是基本操作而已。
更重要的还是这个项目用到了目前市面上最主流的 SpringCloudAlibaba 的技术栈,用到的框架都是目前的最新版+稳定版。
后端主要用到了像 JDK 21、Spring 6.1、SpringCloud 2023、Nacos、Sentinel、Dubbo、Redis、XXL-JOB、RocketMQ、ShardingJDBC、Druid、MySQL、EslasticSearch、Canal、 Seata、SaToken 等中间件及技术,还用到了像 Hutool、Logback、Caffeine、Mybatis、MybatisPlus、FastJson2等常用的开源框架。
在技术方案上,主要涉及到了各种分布式、微服务、高并发、高可用等相关技术列表。(项目详细介绍)
项目给大家交付的内容包括了代码+视频+文档+答疑。
这个项目目前还在更新中,预计会在接下来的3个月左右时间完成代码的开发、文档和视频的更新。(主干功能已完成,项目可完整运行起来)
更新完之后,有效代码行数应该在3万行左右,视频和文档都在200集左右。文档总字数大概20万字左右,视频的总时长大概在2000分钟左右。
这个项目因为目前刚刚推出,还是一个首发价,当前的价格是179,这个价格不是一年的价格,是永久的。
项目详细介绍:高并发、大流量的项目实战课上线了!
购买的方式目前大家可以通过下方的二维码下单,下单后会有短信提示,然后你就可以根据提示操作,申请代码、视频、文档的权限了。
这个项目是一个微服务的技术栈,所以其实内容还是挺多的,想要学明白,还是有一定的门槛的,所以,对于0基础的人不适合!
除了0基础以外,其他人都能学,因为项目中我有很多模块,不同的模块遇到的挑战、用到的技术都不一样,你可以按照我划分的难度进行选择性学习。