java - 服务层验证(SpringBoot)
问题描述
我有一个 DTO,它在控制器层使用 BeanValidation (javax.validation) 和自定义验证器 (org.springframework.validation.Validator) 进行验证。这样我可以检查提供的输入是否有效,然后将 DTO 转换为实体并将其转发到服务层。
@Data
public class UserDTO {
@NotBlank
@Size(max = 25)
private String name;
@NotNull
private Date birthday;
@NotNull
private Date startDate;
private Date endDate;
private Long count;
}
public class UserDTOValidator implements Validator {
private static final String START_DATE= "startDate";
private static final String END_DATE= "endDate";
private static final String COUNT= "count";
@Override
public boolean supports(Class<?> clazz) {
return UserDTO.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
UserDTO vm = (UserDTO) target;
if (vm.getEndDate() != null) {
if (vm.getStartDate().after(vm.getEndDate())) {
errors.rejectValue(START_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
if (vm.getEndDate().equals(vm.getStartDate()) || vm.getEndDate().before(vm.getStartDate())) {
errors.rejectValue(END_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
}
if (vm.getCount() < 1) {
errors.rejectValue(COUNT, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
.....
}
}
public class UserController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserDTOValidator());
}
@PostMapping()
public ResponseEntity<UserDTO> create(@RequestBody @Valid UserDTO userDTO) {
.....
}
.....
}
然后是业务逻辑验证。例如:@Entity 用户的 startDate 必须在某个事件发生之后,并且如果最后创建的用户的生日是在夏季,则计数必须大于某个 X,否则,该实体应被用户服务丢弃。
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private SomeEventService someEventService ;
@Override
public User create(User entity) {
String error = this.validateUser(entity);
if (StringUtils.isNotBlank(error)) {
throw new ValidationException(error);
}
return this.userRepository.save(entity);
}
....
private String validateUser(User entity) {
SomeEvent someEvent = this.someEventService.get(entity.getName());
if (entity.getStartDate().before(someEvent.getDate())) {
return "startDate";
}
User lastUser = this.userRepository.findLast();
....
}
}
但是我觉得这不是处理业务逻辑验证的最佳方法。我应该怎么办?ConstraintValidator/HibernateValidator/JPA 事件监听器?他们可以在@Entity 类级别工作,还是我必须为每个不同的字段检查创建 X 个?你们如何在实际的生产应用程序中做到这一点?
解决方案
在我的建议中,
- 通过@Valid 使用经典的字段级别验证
样本
void myservicemethod(@Valid UserDTO user)
- 对于实体级别的自定义业务级别验证,请在 DTO 本身中创建 validate 方法
样本
class UserDTO {
//fields and getter setter
void validate() throws ValidationException {
//your entity level business logic
}
}
此策略将有助于保持实体特定的验证逻辑将保留在实体内
- 如果您仍然有需要其他服务调用的验证逻辑,则使用自定义 ConstraintValidator 创建自定义验证注释(例如stackoverflow 上的问题)。在这种情况下,我的偏好是从这个自定义验证器调用 UserDTO.validate() 来代替从服务调用
这将有助于使您的验证逻辑与服务层分离,并且还具有可移植性和模块化
推荐阅读
- angular - 更新异步管道中使用的 observable 的一部分
- laravel - 如何从自定义 Laravel UserProvider 返回身份验证错误?
- ruby-on-rails - Rails:创建新记录时更新连接表自定义属性
- spring-boot - Ouath2 + jwt 和弹簧靴
- c# - 模糊匹配两个 csv 文件的最快方法
- jenkins - 如何解决 Jenkins 上的 Gruntfile.js“意外令牌”错误?
- binding - hyperjaxb 绑定自定义排除包
- python-3.x - Python - 安排关闭程序
- sql - SQL 搜索缺失的记录,然后插入值
- vba - 访问 365 vba 问题,将特定 Outlook 365 电子邮件消息移动到子文件夹