Custom annotation for business verification

In daily interface development, in order to ensure the stability and security of the interface, we generally need to deal with two kinds of verification in the interface logic:

  1. Parameter verification

  2. Business rule verification

First, let's look at parameter verification.

Parameter verification

Parameter verification is easy to understand. For example, when logging in, you need to verify whether the user name and password are empty, and when creating a user, you need to verify whether the format of e-mail and mobile phone number is accurate.

The implementation of parameter verification is also very simple. We only need to use the Bean Validation verification framework. With the verification annotation provided by it, we can easily complete parameter verification.

Common verification notes include:

@Null,@NotNull,@AssertTrue,@AssertFalse,@Min,@Max,@DecimalMin,@DecimalMax,@Negative,@NegativeOrZero,@Positive,@PositiveOrZero,@Size,@Digits,@Past,@PastOrPresent,@Future,@FutureOrPresent,@Pattern,@NotEmpty,@NotBlank,@Email

Next, let's look at business rule verification.

The background management system + user applet based on spring boot + mybatis Plus + Vue & element supports RBAC dynamic permission, multi tenant, Data permission, workflow, tripartite login, payment, SMS, mall and other functions.

Project address: https://github.com/YunaiV/ruoyi-vue-pro

Business rule verification

Business rule verification means that the interface needs to meet some specific business rules. For example, the user of the business system needs to ensure its uniqueness. The user attribute cannot conflict with other users, and it is not allowed to duplicate the user name, mobile phone number and mailbox of any existing user in the database.

The user name and number need to be verified when the user is created; When editing a user, you cannot change the information to the attribute of an existing user.

95% of programmers often choose to write in the service logic when facing this kind of business rule verification. The common code logic is as follows:

public void create(User user) {
    Account account = accountDao.queryByUserNameOrPhoneOrEmail(user.getName(),user.getPhone(),user.getEmail());
    if (account != null) {
        throw new IllegalArgumentException("User already exists, please re-enter");
    }
}

Although I introduced in my last article that using Assert to optimize the code can make it look more concise, it's a programmer's version of the story of paying back pearls by giving simple validation to Bean Validation and leaving complex validation to myself.

The most elegant implementation method should refer to the standard method of Bean Validation and complete the verification of business rules with the help of custom verification annotations.

Next, we complete the verification of business rules through user-defined annotations through the user interface case mentioned above.

Code practice

The requirements are easy to understand. When registering a new user, it should be constrained not to duplicate the key information of any existing user; When modifying your own information, you can only duplicate your own information, and you are not allowed to modify it into the information of existing users.

These constraint rules not only serve these two methods, but may be used in other entries in user resources and even in other layered code. Checking on the Bean can completely cover these usage scenarios.

Custom annotation

First, we need to create two user-defined annotations for business rule verification:

  • UniqueUser: indicates that a user is unique. Uniqueness includes: user name, mobile phone number and email address

@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {

    String message() default "User name, mobile phone number and email address cannot be duplicate with existing users";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  • NotConflictUser: indicates that a user's information is conflict free. Conflict free means that the user's sensitive information does not coincide with other users

@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
    String message() default "The user name, email address and mobile phone number are duplicate with existing users";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Implement business verification rules

To make the custom validation annotation effective, you need to implement the constraint validator interface. The first parameter of the interface is the user-defined annotation type, and the second parameter is the class of the annotated field. Because multiple parameters need to be verified, we directly pass in the user object. It should be mentioned that the implementation class of the constraint validator interface does not need to add @ Component, which has been loaded into the container when it is started.

@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, User> {

    protected Predicate<User> predicate = c -> true;

    @Resource
    protected UserRepository userRepository;

    @Override
    public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
        return userRepository == null || predicate.test(user);
    }

    /**
     * Verify whether the user is unique
     * That is, judge whether there is information of the current new user in the database, such as user name, mobile phone and email
     */
    public static class UniqueUserValidator extends UserValidation<UniqueUser>{
        @Override
        public void initialize(UniqueUser uniqueUser) {
            predicate = c -> !userRepository.existsByUserNameOrEmailOrTelphone(c.getUserName(),c.getEmail(),c.getTelphone());
        }
    }

    /**
     * Verify whether it conflicts with other users
     * Changing the user name, e-mail and phone number to one that does not duplicate the existing one or only repeats it with yourself is not a conflict
     */
    public static class NotConflictUserValidator extends UserValidation<NotConflictUser>{
        @Override
        public void initialize(NotConflictUser notConflictUser) {
            predicate = c -> {
                log.info("user detail is {}",c);
                Collection<User> collection = userRepository.findByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
                //Changing the user name, e-mail and phone number to one that does not duplicate the existing one or only repeats it with yourself is not a conflict
                return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId()));
            };
        }
    }

}

Here, the predict functional interface is used to judge the business rules.

use

@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
    @Autowired
    private UserRepository userRepository;
    

    @PostMapping
    public User createUser(@UniqueUser @Valid User user){
        User savedUser = userRepository.save(user);
        log.info("save user id is {}",savedUser.getId());
        return savedUser;
    }

    @SneakyThrows
    @PutMapping
    public User updateUser(@NotConflictUser @Valid @RequestBody User user){
        User editUser = userRepository.save(user);
        log.info("update user is {}",editUser);
        return editUser;
    }
}

It is easy to use. You only need to add custom annotations to the method. There is no need to add any business rule code to the business logic.

test

The following error occurs after calling the interface, indicating that the business rule verification is effective.

{
  "status": 400,
  "message": "User name, mobile phone number and email address cannot be duplicate with existing users",
  "data": null,
  "timestamp": 1644309081037
}

Summary

Through the above steps, the business verification is completely separated from the business logic. When verification is needed, it is automatically triggered with @ Validated annotation or manually triggered by code. These annotations can be applied to the code of any level such as controller, service layer and persistence layer according to the requirements of your project.

This method is more elegant than any business rule verification method. It is recommended to be used in the project. During development, the format verification annotation without business meaning can be placed on the Bean's class definition, and the verification with business logic can be placed outside the Bean's class definition. The difference between the two is that the annotations placed in the class definition can run automatically, while the annotations placed outside the class need to be clearly marked as in the previous code.

Project source code address: https://github.com/jianzh5/cloud-blog/

Keywords: Java Back-end

Added by Tylor_Famous on Tue, 01 Mar 2022 07:46:08 +0200