After reading the code written by my colleagues, I started to imitate silently. . .

background

The thing is like this, I am currently participating in the construction of the XXXX project and need to interface with a third party. There are several asynchronous notifications in the interface of the other party. For the security of the interface, the parameters of the interface need to be verified.

In order to make it easier for everyone to process the return parameters of asynchronous notifications, colleague Z proposed to encapsulate the signature verification function in a unified way, and then everyone only needs to pay attention to their own business logic.


Friends who are interested in knowing the content and more related learning materials, please like and collect + comment and forward + follow me, there will be a lot of dry goods later. I have some interview questions, architecture, and design materials that can be said to be necessary for programmer interviews!
All the materials are organized into the network disk, welcome to download if necessary! Private message me to reply [111] to get it for free

Z's solution

Colleague Z chose the solution of "custom parameter parser". Next, let's take a look through the code.

custom annotation

@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.PARAMETER})public @interface RsaVerify {
   
           /**     * 是否启用验签功能,默认验签     */    boolean verifySign() default true;}

Custom method parameter resolvers

@AllArgsConstructor@Component//实现 HandlerMethodArgumentResolver 接口public class RsaVerifyArgumentResolver implements HandlerMethodArgumentResolver {
   
   
    private final SecurityService securityService;
    /**     * 此方法用来判断本次请求的接口是否需要解析参数,     * 如果需要返回 true,然后调用下面的 resolveArgument 方法,     *  如果不需要返回 false     */    @Override    public boolean supportsParameter(MethodParameter parameter) {
   
           return parameter.hasParameterAnnotation(RsaVerify.class);    }
    /**     * 真正的解析方法,将请求中的参数值解析为某种对象     * parameter 要解析的方法参数     * mavContainer 当前请求的 ModelAndViewContainer(为请求提供对模型的访问)     * webRequest 当前请求     * WebDataBinderFactory 用于创建 WebDataBinder 的工厂     */    @Override    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
   
           RsaVerify parameterAnnotation = parameter.getParameterAnnotation(RsaVerify.class);        if (!parameterAnnotation.verifySign()) {
   
               return mavContainer.getModel();        }                //对参数进行处理并验签的逻辑        ......                //返回处理后的实体类参数        return ObjectMapperFactory                .getDateTimeObjectMapper("yyyyMMddHHmmss")                .readValue(StringUtil.queryParamsToJson(sb.toString()), parameter.getParameterType());    }   }

Create a configuration class

​​​​​​​

@Configuration@AllArgsConstructorpublic class PayTenantWebConfig implements WebMvcConfigurer {
   
   
    private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver;        /**     * 将自定义的方法参数解析器加入到配置类中     */    @Override    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
   
           resolvers.add(rsaVerifyArgumentResolver);    }}

use

The method of use is very simple, you only need to introduce annotations on the parameters

​​​​​​​

@RestController@Slf4j@RequestMapping("/xxx")public class XxxCallbackController {
   
   
    /**     * @param params     * @return     */    @PostMapping("/callback")    public String callback(@RsaVerify CallbackReq params) {
   
           log.info("receive callback req={}", params);  //业务逻辑处理  .....          return "success";    }}

question

Question one

Seeing this, careful friends should have some doubts: Since custom annotations are used here, why not use facets to implement them, but use custom parameter parsers? Very Good! This is also the question raised by Ah Q. My colleague said that because the priority of jackson's deserialization action is much higher than that of the aspect, the error of deserialization failure has been reported before entering the aspect.

question two

Why is the annotation @RequestBody missing in the controller?

To answer this question, we have to understand the HandlerMethodArgumentResolverComposite class, hereinafter referred to as Composite. SpringMVC will put all parameter parsers into Composite at startup, and Composite is a collection of all parameters. When parsing parameters, a parameter parser that supports parameter parsing will be selected from the set of parameter parsers, and then the parser will be used for parameter parsing.

And because of @RequestBody, the parameter parser RequestResponseBodyMethodProcessor used has a higher priority than our custom parameter parser, so if it is shared, it will be intercepted and parsed by the former, so for normal use, we need to remove the @RequestBody annotation. ​​​​​​​​

/** * Find a registered {@link HandlerMethodArgumentResolver} that supports * the given method parameter. */@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   
       HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);    if (result == null) {
   
           for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
   
               if (resolver.supportsParameter(parameter)) {
   
                   result = resolver;                this.argumentResolverCache.put(parameter, result);                break;            }        }    }    return result;}

C colleague's solution

The solution of colleague Z above can already solve this problem, but the solution still has two shortcomings:

  • Each callback needs to create its own controller layer, and there is no unified external entrance;

  • It is necessary to add custom annotations on the method, which is more intrusive;

Therefore, after our discussion, we decided to abandon this plan, but the idea of ​​this plan is worth learning. Next let's analyze the new solution:

Define the business interface class

The business interface class contains two methods: the type of specific business processing; the specific processing method of the business.

​​​​​​​

public interface INotifyService {
   
    /**  * 处理类型  */ public String handleType(); /**  * 处理具体业务  */ Integer handle(String notifyBody);
}

Asynchronous notification unified entrance

​​​​​​​

@AllArgsConstructor@RestController@RequestMapping(value = "/notify")public class NotifyController {
   
    private IService service;
    @PostMapping(value = "/receive")    public String receive(@RequestBody String body) {
   
           //处理通知        Integer status = service.handle(body);        return "success";    }}

Do two steps in Iservice:

  • After spring starts, collect all classes of type INotifyService and put them in the map;

  • Process and transform the parameters, and verify the signature;

private ApplicationContext applicationContext;private Map<String,INotifyService> notifyServiceMap;
/** * 启动加载 */@PostConstructpublic void init(){
   
    Map<String,INotifyService> map = applicationContext.getBeansOfType(INotifyService.class); Collection<INotifyService> services = map.values(); if(CollectionUtils.isEmpty(services)){
   
     return; } notifyServiceMap = services.stream().collect(Collectors.toMap(INotifyService::handleType, x -> x));}
@Overridepublic Map<String, INotifyService> getNotifyServiceMap() {
   
    return notifyServiceMap;}
@Overridepublic Integer handle(String body) {
   
    //参数处理+验签逻辑    ......         //获取具体的业务实现类 INotifyService notifyService=notifyServiceMap.get(notifyType); Integer status=null; if(Objects.nonNull(notifyService)) {
   
     //执行具体业务  try {
   
      status=notifyService.handle(JSON.toJSONString(requestParameter));  } catch (Exception e) {
   
      e.printStackTrace();  } } //后续逻辑处理    ......         return status;}

Realization of business

​​​​​​​

@Servicepublic class NotifySignServiceImpl implements INotifyService {
   
   
    @Override    public String handleType() {
   
           return "type_sign";    }
    @Override    @Transactional    public Integer handle(String notifyBody) {
   
           //具体的业务处理        ......

summary

  • This solution provides a unified asynchronous notification entry, separating public parameter processing and signature verification logic from business logic.

  • Utilize the feature of java dynamically loading classes, the implementation classes are collected by type.

  • Using the characteristics of java polymorphism, different business logics are processed through different implementation classes.

Seeing this, I believe that everyone has a certain understanding of these two implementations, and you can try to apply them in future projects and experience them!

Guess you like

Origin blog.csdn.net/m0_69424697/article/details/125105928