一、背景
之前针对参数校验提供了一个通用方案(见文末),但是新增一个校验代价比较大,需要修改多个类。
给出一个改进方案,新增校验时只需新增一个校验类即可,并且校验可以指定分组。
这样同样的一个组件,可以实现多个分组,指定分组条件可以让不同的方法只走指定分组的校验。
二、代码
2.1 项目结构
2.2 实体
package com.chujianyun.entity.param; import lombok.Data; @Data public class UserParam { /** * 用户ID */ private Long userId; /** * 等级 */ private Integer level; }
枚举
package com.chujianyun.entity.enums; /** * 校验分组枚举 */ public enum UserValidateGroupEnum { // 所有条件都校验 ALL, // 某种条件才校验 SOME }
结果类
package com.chujianyun.entity.dto; import lombok.Data; import java.util.List; @Data public class UserDTO { private String message; public UserDTO() { } public UserDTO(String message) { this.message = message; } }
2.3 校验抽象类
package com.chujianyun.component; import lombok.Data; import java.util.Set; @Data public abstract class Validator<P> { /** * 校验分组,枚举 */ private Set<Enum> groups; /** * 验证参数 */ abstract void validate(P param); }
2.4 具体校验类
等级校验
package com.chujianyun.component; import com.chujianyun.entity.param.UserParam; import com.chujianyun.exception.BusinessException; import org.apache.commons.lang3.RandomUtils; import org.springframework.stereotype.Component; @Component public class UserLevelValidator extends Validator<UserParam> { @Override public void validate(UserParam param) { System.out.println("验证等级"); if (param == null) { throw new BusinessException(""); } // 根据userId查询等级 boolean isBiggerThan50 = RandomUtils.nextBoolean(); if (!isBiggerThan50) { throw new BusinessException("低于50级"); } } }
性别校验
package com.chujianyun.component; import com.chujianyun.entity.param.UserParam; import com.chujianyun.exception.BusinessException; import org.apache.commons.lang3.RandomUtils; import org.springframework.stereotype.Component; @Component public class UserSexValidator extends Validator<UserParam> { @Override void validate(UserParam param) { System.out.println("验证性别"); if (param == null) { throw new BusinessException(""); } // 模拟服务,根据userId查询性别 boolean isFemale = RandomUtils.nextBoolean(); if (!isFemale) { throw new BusinessException("仅限女性玩家哦!"); } } }
某个特殊类型校验
package com.chujianyun.component; import com.chujianyun.entity.enums.UserValidateGroupEnum; import com.chujianyun.entity.param.UserParam; import com.chujianyun.exception.BusinessException; import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.RandomUtils; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashSet; import java.util.stream.Collectors; import java.util.stream.Stream; /** * 特殊情况才校验 */ @Component public class UserSomeValidator extends Validator<UserParam> { @PostConstruct public void init() { setGroups(Stream.of(UserValidateGroupEnum.SOME).collect(Collectors.toSet())); } @Override void validate(UserParam param) { System.out.println("仅检测Some类型的"); if (param == null) { throw new BusinessException(""); } // 模拟服务,根据userId某个条件 boolean isSome = RandomUtils.nextBoolean(); if (!isSome) { throw new BusinessException("某种条件不满足哦!"); } } }
2.5 校验链
package com.chujianyun.component; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.ResolvableType; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; @Component public class ValidatorChain implements ApplicationContextAware { private Map<Class, List<Validator>> validatorMap = new HashMap<>(); /** * 参数校验 */ public <P> void checkParam(P param) { checkParam(param, validator -> true); } /** * 符合某种条件才参数校验 */ public <P> void checkParam(P param, Predicate<Validator> predicate) { List<Validator> validators = getValidators(param.getClass()); if (CollectionUtils.isNotEmpty(validators)) { validators.stream() .filter(predicate) .forEach(validator -> validator.validate(param)); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, Validator> beansOfType = applicationContext.getBeansOfType(Validator.class); this.validatorMap = beansOfType.values().stream().collect(Collectors.groupingBy(validator -> getParamType(validator.getClass()))); } /** * 查找相关的所有校验器 */ private List<Validator> getValidators(Class clazz) { return validatorMap.get(clazz); } /** * 解析泛型待校验参数类型 */ private Class getParamType(Class clazz) { ResolvableType resolvableType = ResolvableType.forClass(clazz); return resolvableType.getSuperType().getGeneric(0).resolve(); } }
2.6 服务层
package com.chujianyun.service; import com.chujianyun.entity.dto.UserDTO; import com.chujianyun.entity.param.UserParam; public interface UserService { UserDTO checkUser(UserParam userParam); UserDTO checkUserSome(UserParam userParam); }
服务实现
package com.chujianyun.service.impl; import com.chujianyun.component.ValidatorChain; import com.chujianyun.entity.dto.UserDTO; import com.chujianyun.entity.enums.UserValidateGroupEnum; import com.chujianyun.entity.param.UserParam; import com.chujianyun.service.UserService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class UserServiceImpl implements UserService { @Resource private ValidatorChain validatorChain; @Override public UserDTO checkUser(UserParam userParam) { // 参数校验 validatorChain.checkParam(userParam); // 业务逻辑 return new UserDTO("测试"); } @Override public UserDTO checkUserSome(UserParam userParam) { // 参数校验(只校验类型为Some的) validatorChain .checkParam(userParam, param -> param.getGroups() .contains(UserValidateGroupEnum.SOME)); // 业务逻辑 return new UserDTO("测试"); } }
还可以设置除了分组之外的其他条件,满足条件才会校验。
2.7 控制层
package com.chujianyun.web; import com.chujianyun.entity.dto.UserDTO; import com.chujianyun.entity.param.UserParam; import com.chujianyun.service.UserService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/user") public class HelloController { @Resource private UserService userService; @PostMapping("/check") public ResponseEntity<UserDTO> checkUser(UserParam userParam) { return new ResponseEntity<>(userService.checkUser(userParam), HttpStatus.OK); } @PostMapping("/checkSome") public ResponseEntity<UserDTO> checkUserSome(UserParam userParam) { return new ResponseEntity<>(userService.checkUserSome(userParam), HttpStatus.OK); } }
2.8 异常处理
package com.chujianyun.web; import com.chujianyun.exception.BusinessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(value = Exception.class) public ResponseEntity handle(Exception e) { if (e instanceof BusinessException) { return new ResponseEntity<>("[业务异常]" + e.getMessage(), HttpStatus.BAD_REQUEST); } return new ResponseEntity<>("[系统异常]" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); } }
三、测试
直接执行check校验所有的校验器(下面只给出其中一种情况的截图)
checkSome则只会校验指定的条件
四、总结
设计模式里有一个开闭原则即“对拓展开放,对修改关闭”,本例就是一个实践。
不过还有很多可以改进的地方,大家可以考虑使用接口来实现,欢迎大家一起完善,欢迎提PR。
大家在平时开发时,要多尝试将设计模式使用到项目中,来提高代码的可维护性,灵活性。
源码地址:https://github.com/chujianyun/checkparam
之前的一个通用方案:https://blog.csdn.net/w605283073/article/details/95680896
点击查看更多内容
2人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦