拒绝if/else 数据校验及转换

转载自【优雅代码】10-拒绝if/else数据校验及转换

1|0背景

避免if、else,只用注解完成校验及格式化

2|0注解边界值

2|1官方注解

给接口添加@Validated注解(其可以使用分组更为优秀)

@PostMapping("testFront") public ResponseVO<FrontRepVO> testFront(@Validated @RequestBody FrontReqVO frontReqVO) { return new ResponseVO<>(); }

使用javax.validation.constraints下的注解,该注解在非controller中

public class FrontReqVO { @NotNull private String name; @Size(min = 1) private String name2; // 校验去除空格后不可为空 @TrimNotNull(groups = {Default.class, Insert.class}) // 序列化,去除空格 @JsonSerialize(using = TrimNotNullJson.class) // 反序列化,去除空格 @JsonDeserialize(using = TrimNotNullDeJson.class) private String name3; @Pattern(regexp = "^[Y|N]$") private String yesOrNo; @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date date; private ErrorCodeEnum errorCodeEnum; private Boolean boo; }

让方法间互相调用也生效

需要在方法上加@Validated,属性上加@Valid

@Service @Validated public class FrontTestService { public ResponseVO<FrontRepVO> testFront3Valid(@Valid FrontReqVO frontReqVO) { return new ResponseVO<>(); } }

2|2自定义注解

有些时候自定义注解不能完全实现自己的功能,这时候就需要自定义注解,比如TrimNotNull,该注解作为用为此字段不可为null,trim后不可为空

添加注解,和官方原注解复制过来即可,关键改动@Constraint中要写该注解的实现类

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(TrimNotNull.List.class) @Documented @Constraint( validatedBy = {TrimNotNullImpl.class} ) public @interface TrimNotNull { String message() default "不能为null或空"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface List { TrimNotNull[] value(); } }

自定义注解实现类

public class TrimNotNullImpl implements ConstraintValidator<TrimNotNull, String> { @Override public void initialize(TrimNotNull constraintAnnotation) { } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return StringUtils.isNoneBlank(s); } }

3|0统一规范错误

3|1错误码

阿里将00000定义为成功,A开头的错误为用户端错误,B开头的错误为服务器错误,C开头的错误为远程调用类错误,根据错误码可以初步定为是怎样的问题,源代码过长就不贴了,该部分参考阿里手册即可

public enum ErrorCodeEnum { SUCCESS("00000", "成功", "Success"), CLIENT_ERROR("A0001", "用户端错误", "Client error"), USER_REGISTRATION_ERROR("A0100", "用户注册错误", "User registration error"); public static final Map<String, ErrorCodeEnum> MAPS = Stream.of(ErrorCodeEnum.values()).collect(Collectors.toMap(ErrorCodeEnum::getCode, s -> s)); }

3|2全局异常

为避免将各种错误返回给用户,所以需要对其进行统一封装

  • 校验类封装
public class FieldValidError { private String field; private String msg; }
  • 全局异常
@RestControllerAdvice @Controller public class GlobalExceptionHandler implements ErrorController { /** * 404 * * @author seal 876651109@qq.com * @date 2020/6/4 1:29 AM */ @RequestMapping(value = "/error") @ResponseBody public ResponseVO<String> error(HttpServletRequest request, HttpServletResponse response) { return ResponseVO.<String>builder().code(ErrorCodeEnum.ADDRESS_NOT_IN_SERVICE.getCode()).msg(ErrorCodeEnum.ADDRESS_NOT_IN_SERVICE.getZhCn()).build(); } /** * 其它未定义错误 * * @author seal 876651109@qq.com * @date 2020/6/4 12:57 AM */ @ExceptionHandler(Exception.class) public ResponseVO<String> handleException(Exception e) { log.warn("handleException:" + e.getMessage(), e); return ResponseVO.<String>builder().code(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR.getCode()).msg(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR.getZhCn()).build(); } /** * 请求方式异常例如get/post * * @author seal 876651109@qq.com * @date 2020/6/4 12:58 AM */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseVO<String> HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { return ResponseVO.<String>builder().code(ErrorCodeEnum.USER_API_REQUEST_VERSION_MISMATCH.getCode()).msg(ErrorCodeEnum.USER_API_REQUEST_VERSION_MISMATCH.getZhCn()).build(); } /** * 重写校验异常,按照统一格式返回 * * @param e * @return {@link ResponseVO< List< FieldValidError>>} * @author 876651109@qq.com * @date 2021/5/14 5:06 下午 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseVO<List<FieldValidError>> MethodArgumentNotValidException(MethodArgumentNotValidException e) { ResponseVO<List<FieldValidError>> vo = ResponseVO.<List<FieldValidError>>builder().build(); List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); List<FieldValidError> list = new ArrayList<>(fieldErrors.size()); vo.setData(list); for (FieldError error : fieldErrors) { FieldValidError build = FieldValidError.builder().field(error.getField()).msg(error.getDefaultMessage()).build(); switch (error.getCode()) { case "Size": build.setMsg(String.format("必须在%s到%s之间", error.getArguments()[2], error.getArguments()[1])); break; default: break; } list.add(build); } return vo; } @Override public String getErrorPath() { return null; } }

4|0扩展转换

在日常开发中难免会遇到string转各种奇怪的格式,或预处理,或后置处理,则对其进行扩展非常有必要

  1. json格式
  • 反序列化,外部数据转对象时
  • 写法一
/** * 实现去空格反序列化 * * @author 876651109@qq.com * @date 2021/5/14 1:46 下午 */ public class TrimNotNullDeJson extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { return StringUtils.trim(p.getText()); } }
  • 写法二
/** * 在该注解下,反序列化时会调用该类的这个方法,注意方法要static * * @author 876651109@qq.com * @date 2021/5/14 1:46 下午 */ @JsonCreator public static ErrorCodeEnum get(String value) { if (StringUtils.isBlank(value)) { return null; } ErrorCodeEnum errorCodeEnum = MAPS.get(value); if (errorCodeEnum == null) { return ErrorCodeEnum.valueOf(value); } else { return errorCodeEnum; } }

序列化,对象转string时

/** * 自定义序列化 * * @author 876651109@qq.com * @date 2021/5/14 1:46 下午 */ public class TrimNotNullJson extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(StringUtils.trim(value)); } }
  1. 表单form/data格式或get请求格式
  • 实现Converter,将code转成enum
/** * 表单form/data格式或get请求实现Convert * 将stringCode直接转成enum * * @author seal 876651109@qq.com * @date 2022/1/2 12:57 */ public class StringCodeToEnumConvert implements Converter<String, ErrorCodeEnum> { @Override public ErrorCodeEnum convert(String source) { return ErrorCodeEnum.MAPS.get(source); } }
  • 注册
@Configuration public class SpringMvcConfig extends WebMvcConfigurationSupport { @Override protected void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToCodeConvert()); } /** * 非必要,用于开放静态资源拦截 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("doc.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } }

5|0aop

这部分功能旨在减少后端各种不规范的tryCath日志,可以将其相对统一规范,同时提供了打印入参、返回值、方法耗时等。核心功能为在方法出错时可以打印入参方便定为错误。

  1. 创建注解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { /** * 运行时长 */ boolean totalConsume() default true; /** * 请求参数 */ boolean parameter() default false; /** * 返回值 */ boolean result() default false; /** * 异常时打印请求参数 */ boolean exception() default true; }
  1. 增加aop配置
@Aspect @Component @Order(1000) @Slf4j public class LogInfoAspect { /** * 通过@Pointcut注解声明频繁使用的切点表达式 */ @Pointcut("execution(* com.example.demo..*.*(..))") public void AspectController() { } @Pointcut("execution(* com.example.demo..*.*(..))") public void AspectController2() { } /** * 先执行、先退出 * * @author seal 876651109@qq.com * @date 2020/6/3 2:34 PM */ @Around("AspectController() || AspectController2()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { log.debug("环绕前"); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); pjp.getTarget(); LogAnnotation logAnnotationClass = pjp.getTarget().getClass().getAnnotation(LogAnnotation.class); LogAnnotation logAnnotationMethod = methodSignature.getMethod().getAnnotation(LogAnnotation.class); if (logAnnotationClass == null && logAnnotationMethod == null) { return pjp.proceed(); } LogAnnotation logAnnotation = ObjectUtils.defaultIfNull(logAnnotationMethod, logAnnotationClass); StopWatch sw = new StopWatch(); String className = pjp.getTarget().getClass().getName(); String methodName = methodSignature.getName(); if (logAnnotation.parameter()) { log.info("{}:{}:parameter:{}", className, methodName, pjp.getArgs()); } sw.start(); Object result = null; try { result = pjp.proceed(); } catch (Throwable e) { if (logAnnotation.exception()) { log.warn(e.getMessage(), e); log.info("{}:{}:parameter:{}", className, methodName, pjp.getArgs()); } throw e; } if (logAnnotation.result()) { log.info("{}:{}:result:{}", className, methodName, result); } sw.stop(); if (logAnnotation.totalConsume()) { log.info("{}:{}:totalConsume:{}s", className, methodName, sw.getTotalTimeSeconds()); } log.debug("环绕后"); return result; } }

6|0压缩返回值

在yml加入

server: compression: enabled: true mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain

7|0对接文档

  1. swagger-ui
  • 官网
    这是一个相对完善的在线api生成器,在编写java的时候直接通过api注解可以辅助生成中文注释,当然没有的话它也会生成一份全部接口的文档,同时支持在线调试,功能十分强大。但对于持久化等显然不够友好,swagger-ui-layer则在此基础上补充了这一点,支持导出,更好看的ui。关于这一块的内容不过多赘述,东西比较简单,网上资料也比较多。
  • 引入jar包
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-ui</artifactId> <version>3.0.2</version> </dependency>
  • 加入配置
@Configuration @EnableSwagger2 public class SwaggerConfig { //http://localhost:8081/swagger-ui.html //http://localhost:8081/doc.html @Value("${swagger.enable:true}") private boolean enable; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("微服务接口调用文档") .enable(enable) .pathMapping("/") .select() .apis(RequestHandlerSelectors.basePackage("com.example.demo")) .paths(PathSelectors.any()) .build().apiInfo(new ApiInfoBuilder() .title("SpringBoot整合Swagger") .description("SpringBoot整合Swagger,详细信息......") .version("9.0") .license("The Apache License") .licenseUrl("http://www.baidu.com") .build()); } }

swagger

  1. yapi
  • github

    与swagger不同的是,这个有权限控制,这对于整个大团队维护一份文档则显得尤为重要,其支持多种格式的导入,包括上面提到的swagger。


__EOF__

本文作者咫尺是梦
本文链接https://www.cnblogs.com/satire/p/15825805.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   satire  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示