<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          看了同事寫(xiě)的代碼,大家竟然都開(kāi)始默默模仿了。。。

          共 9817字,需瀏覽 20分鐘

           ·

          2022-07-16 19:45

          背景

          事情是這樣的,目前我正在參與 XXXX 項(xiàng)目的搭建,需要與第三方對(duì)接接口。在對(duì)方的接口中存在幾個(gè)異步通知,為了接口的安全性,需要對(duì)接口的參數(shù)進(jìn)行驗(yàn)簽處理。

          為了方便大家對(duì)異步通知返回參數(shù)的處理,Z 同事提出要將該驗(yàn)簽功能進(jìn)行統(tǒng)一封裝,到時(shí)候大家只需要關(guān)注自己的業(yè)務(wù)邏輯即可。

          Z同事的解決方案

          Z 同事選擇的是“自定義參數(shù)解析器”的解決方案,接下來(lái)我們通過(guò)代碼來(lái)了解一下。

          自定義注解

          @Documented
          @Retention(RetentionPolicy.RUNTIME)
          @Target({ElementType.PARAMETER})
          public @interface RsaVerify {
              
              /**
               * 是否啟用驗(yàn)簽功能,默認(rèn)驗(yàn)簽
               */

              boolean verifySign() default true;
          }

          自定義方法參數(shù)解析器

          @AllArgsConstructor
          @Component
          //實(shí)現(xiàn) HandlerMethodArgumentResolver 接口
          public class RsaVerifyArgumentResolver implements HandlerMethodArgumentResolver {

              private final SecurityService securityService;

              /**
               * 此方法用來(lái)判斷本次請(qǐng)求的接口是否需要解析參數(shù),
               * 如果需要返回 true,然后調(diào)用下面的 resolveArgument 方法,
               *  如果不需要返回 false
               */

              @Override
              public boolean supportsParameter(MethodParameter parameter) {
                  return parameter.hasParameterAnnotation(RsaVerify.class);
              }

              /**
               * 真正的解析方法,將請(qǐng)求中的參數(shù)值解析為某種對(duì)象
               * parameter 要解析的方法參數(shù)
               * mavContainer 當(dāng)前請(qǐng)求的 ModelAndViewContainer(為請(qǐng)求提供對(duì)模型的訪問(wèn))
               * webRequest 當(dāng)前請(qǐng)求
               * WebDataBinderFactory 用于創(chuàng)建 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();
                  }
                  
                  //對(duì)參數(shù)進(jìn)行處理并驗(yàn)簽的邏輯
                  ......
                  
                  //返回處理后的實(shí)體類(lèi)參數(shù)
                  return ObjectMapperFactory
                          .getDateTimeObjectMapper("yyyyMMddHHmmss")
                          .readValue(StringUtil.queryParamsToJson(sb.toString()), parameter.getParameterType());
              }
             
          }

          創(chuàng)建配置類(lèi)

          @Configuration
          @AllArgsConstructor
          public class PayTenantWebConfig implements WebMvcConfigurer {

              private final RsaVerifyArgumentResolver rsaVerifyArgumentResolver;
              
              /**
               * 將自定義的方法參數(shù)解析器加入到配置類(lèi)中
               */

              @Override
              public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                  resolvers.add(rsaVerifyArgumentResolver);
              }
          }

          使用

          使用方法非常簡(jiǎn)單,只需要在參數(shù)上引入注解就可以了

          @RestController
          @Slf4j
          @RequestMapping("/xxx")
          public class XxxCallbackController {

              /**
               * @param params
               * @return
               */

              @PostMapping("/callback")
              public String callback(@RsaVerify CallbackReq params) {
                  log.info("receive callback req={}", params);
            //業(yè)務(wù)邏輯處理
            .....
            
                  return "success";
              }
          }

          問(wèn)題

          問(wèn)題一

          看到這,細(xì)心的朋友應(yīng)該會(huì)有所疑問(wèn):既然這邊用到了自定義的注解,為什么不用切面來(lái)實(shí)現(xiàn),而是使用自定義的參數(shù)解析器呢?Very Good!這也是阿Q提出的疑問(wèn),同事說(shuō)是因?yàn)?jackson 的反序列化動(dòng)作優(yōu)先級(jí)遠(yuǎn)高于切面的優(yōu)先級(jí),所以還沒(méi)進(jìn)入切面就已經(jīng)報(bào)反序列化失敗的錯(cuò)誤了。

          問(wèn)題二

          為什么在 controller 中注解 @RequestBody 不見(jiàn)了?

          要回答這個(gè)問(wèn)題,我們就得了解下HandlerMethodArgumentResolverComposite這個(gè)類(lèi)了,以下簡(jiǎn)稱(chēng)CompositeSpringMVC 在啟動(dòng)時(shí)會(huì)將所有的參數(shù)解析器放到 Composite 中,Composite 是所有參數(shù)的一個(gè)集合。當(dāng)對(duì)參數(shù)進(jìn)行解析時(shí)就會(huì)從該參數(shù)解析器集合中選擇一個(gè)支持對(duì) parameter 解析的參數(shù)解析器,然后使用該解析器進(jìn)行參數(shù)解析。

          又因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">@RequestBody所以使用的參數(shù)解析器RequestResponseBodyMethodProcessor優(yōu)先級(jí)高于我們自定義的參數(shù)解析器,所以如果共用會(huì)被前者攔截解析,所以為了正常使用,我們需要將@RequestBody 注解去掉。

          /**
           * Find a registered {@link HandlerMethodArgumentResolver} that supports
           * the given method parameter.
           */

          @Nullable
          private 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同事的解決方案

          上邊 Z 同事的方案已經(jīng)可以解決該問(wèn)題了,但是該方案還有兩個(gè)不足之處:

          • 需要每一個(gè)回調(diào)都去創(chuàng)建自己的 controller 層,沒(méi)有一個(gè)對(duì)外的統(tǒng)一入口;
          • 需要在方法上添加自定義注解,侵入性比較強(qiáng);

          因此經(jīng)過(guò)我們的商議,決定摒棄該方案,但是該方案的思想值得我們學(xué)習(xí)。接下來(lái)讓我們分析一下新的解決方案:

          定義業(yè)務(wù)接口類(lèi)

          業(yè)務(wù)接口類(lèi)包含兩個(gè)方法:具體業(yè)務(wù)處理的類(lèi)型;業(yè)務(wù)的具體處理方法。

          public interface INotifyService {
           /**
            * 處理類(lèi)型
            */

           public String handleType();
           /**
            * 處理具體業(yè)務(wù)
            */

           Integer handle(String notifyBody);

          }

          異步通知統(tǒng)一入口

          @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";
              }
          }

          在 Iservice 中做兩個(gè)步驟:

          • 在 spring 啟動(dòng)之后,收集所有的類(lèi)型為 INotifyService的類(lèi)并放入map中;
          • 將參數(shù)進(jìn)行處理轉(zhuǎn)化,并驗(yàn)簽處理;
          private ApplicationContext applicationContext;
          private Map<String,INotifyService> notifyServiceMap;

          /**
           * 啟動(dòng)加載
           */

          @PostConstruct
          public 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));
          }

          @Override
          public Map<String, INotifyService> getNotifyServiceMap() {
           return notifyServiceMap;
          }

          @Override
          public Integer handle(String body) {
           //參數(shù)處理+驗(yàn)簽邏輯
              ......
                  
           //獲取具體的業(yè)務(wù)實(shí)現(xiàn)類(lèi)
           INotifyService notifyService=notifyServiceMap.get(notifyType);
           Integer status=null;
           if(Objects.nonNull(notifyService)) {
            //執(zhí)行具體業(yè)務(wù)
            try {
             status=notifyService.handle(JSON.toJSONString(requestParameter));
            } catch (Exception e) {
             e.printStackTrace();
            }
           }
           //后續(xù)邏輯處理
              ......
                  
           return status;
          }

          業(yè)務(wù)具體實(shí)現(xiàn)

          @Service
          public class NotifySignServiceImpl implements INotifyService {

              @Override
              public String handleType() {
                  return "type_sign";
              }

              @Override
              @Transactional
              public Integer handle(String notifyBody) {
                  //具體的業(yè)務(wù)處理
                  ......
              }

          小結(jié)

          • 此方案提供統(tǒng)一的異步通知入口,把公共的參數(shù)處理和驗(yàn)簽邏輯與業(yè)務(wù)邏輯剝離。
          • 利用 java 動(dòng)態(tài)加載類(lèi)的特性,將實(shí)現(xiàn)類(lèi)通過(guò)類(lèi)型進(jìn)行收集。
          • 利用 java 多態(tài)的特性,通過(guò)不同的實(shí)現(xiàn)類(lèi)來(lái)處理不同的業(yè)務(wù)邏輯。

          看到這,相信大家已經(jīng)對(duì)這兩種實(shí)現(xiàn)方案有了一定的理解,大家可以試著在以后的項(xiàng)目中應(yīng)用一下,體驗(yàn)一把!

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享  最新整理全集,找項(xiàng)目不累啦 07版

          堪稱(chēng)神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門(mén)到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


          歡迎添加程序汪個(gè)人微信 itwang009  進(jìn)粉絲群或圍觀朋友圈


          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美国产A片 | 999欠欠欠兔费精品产 | 无码人妻一区二区三区在线神菜美 | 久久大鸡巴再线观看 | 北条麻妃在线观看 |