zoukankan      html  css  js  c++  java
  • 微信公众平台接入之简单任务分发器

      微信公众号现在影响力有目共睹,所以接入其功能也是很正常的。

      现在的应用中,有很多是基于spring的框架来做的。针对自行开发的系统,我们可以通过任意的自定义 url 来进行业务功能的映射。然而大家知道,微信的回调地址永远只有一个,但是其内部的内容则是多样的。针对不同的内容,咱们做出的响应自然也是不一样的。咱们可以通过n个if...else 区分出需要处理的业务。但是这样的代码会很不清晰,维护起来将是噩梦。所以,咱们有必要根据内容来做一个业务功能的分发,使代码能够更清晰。

      只需要一个简单的 mapping 就可以了。

    比如,我们可以配置一个微信回调地址: http://a.bc.com/xxx/wxcallback

    我们需要响应两种类型的请求,一个是 get 请求,进行服务验证,一个是 post 请求,进行业务事件通知!demo 如下:

        @RequestMapping(value = "/{wxServiceType}/wxcallback", method = RequestMethod.GET, produces = "text/html")
        @ResponseBody
        public String gatewayGet(@ModelAttribute WxTokenVerifyReqModel verifyReqModel, @PathVariable(value = "wxServiceType") String wxServiceType) {
            log.info("请求token:{}", verifyReqModel);
            try {
                String reply = serverValidateService.validServerToken(verifyReqModel, WxGatewayServiceKeyEnum.xxx);
                log.info("成功返回:{}", reply);
                return reply;
            } catch (WeixinSystemException e) {
                log.warn("验证失败, code:{}, msg:{}", e.errCode, e.message);
                return "fail";
            } catch (Exception e) {
                log.error("微信校验接口异常error=", e);
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 公众号主要消息请求入口
         *
         * @param req  请求, xml 格式
         * @param resp 响应
         * @return 按格式返回
         */
        @RequestMapping(value = "/{wxServiceType}/wxcallback", method = RequestMethod.POST, produces = "application/xml; charset=utf-8")
        @ResponseBody
        public String gatewayPost(HttpServletRequest req, HttpServletResponse resp, @PathVariable(value = "wxServiceType") String wxServiceType) throws IOException {
    
            try {
                // 交给内部分发器处理,返回处理结果
                Object retMessage = wxMessageHandleDispatcher.handle(req, wxServiceType);
                if (null != retMessage) {
                    return retMessage.toString();
                }
            }
            catch (DocumentException | IOException e) {
                log.error("参数解析异常", e);
            } catch (Exception e) {
                log.error("其他异常,", e);
            }
            return "success";
        }

      第一个token验证,与springmvc的普通模式一样,直接通过 ServletModelAttributeMethodProcessor 参数解析器给解析了。

      所以,咱们只需处理 xml 的正文请求即可!即如下分发:

    Object retMessage = wxMessageHandleDispatcher.handle(req, wxServiceType);

    分发入口类:
    @Component
    public class WxMessageHandleDispatcher {
    
        @Resource
        private WxMessageHandlerMappingBean wxMessageHandlerMappingBean;
    
        /**
         * 请求编号,可用于统计当日访问量
         */
        private AtomicInteger requestSeqNum = new AtomicInteger(0);
    
        /**
         * 服务标识
         */
        private static final ThreadLocal<WxGatewayServiceKeyEnum> gatewayServiceKeyHolder = new ThreadLocal<>();
    
        /**
         * 处理微信消息响应入口
         *
         * @param request xml
         * @return 返回结果
         * @throws RuntimeException
         * @apiNote {@link WxMessageBaseReqModel}
         */
        public Object handle(HttpServletRequest request, WxGatewayServiceKeyEnum serviceKey)
                throws DocumentException, IOException, RuntimeException {
            Long startTime = System.currentTimeMillis();
            // 处理参数
            Map<String, String> parameters = extractRequestParams(request);
    
            // 初始化基础环境
            prepareGatewayServiceHandle(serviceKey);
    
            // 转换为 uri
            String handleUri = exchangeHandleUri(parameters, serviceKey);
            log.info("【微信消息处理】enter {} method, params: {}, serviceKey:{}, seqNum:{}", handleUri, JSONObject.toJSONString(parameters), serviceKey, requestSeqNum.get());
    
            Object retMessage = null;
    
            try {
                // 调用 handleMethod
                retMessage = invokeHandleMethod(parameters, handleUri);
            }
            // 业务异常,看情况捕获
            catch (WeixinSystemException e) {
                log.warn("@{} 发生业务异常:code:{}, msg:{}", handleUri, e.errCode, e.message);
                throw e;
            }
            // 其他异常
            catch (Exception e) {
                log.error("处理方法" + handleUri + " 发生异常", e);
            }
            // 最终打印
            finally {
                log.info("exit {} method, params: {}, result:{}, serviceKey:{}, seqNum:{}, cost:{}ms",
                        handleUri, JSONObject.toJSONString(parameters), retMessage, serviceKey,
                        requestSeqNum.get(), (System.currentTimeMillis() - startTime));
                finishGatewayServiceHandle();
            }
    
            return retMessage;
        }
    
        /**
         * 解析请求参数
         *
         * @param request 原始请求
         * @return k-v
         * @throws IOException
         * @throws DocumentException
         */
        private Map<String, String> extractRequestParams(HttpServletRequest request) throws IOException, DocumentException {
            Map<String, String> parameters = WechatMessageUtil.xmlToMap(request);
            String requestIp = NetworkUtil.getIpAddress(request);
            parameters.put("requestIp", requestIp);
            return parameters;
        }
    
        /**
         * 初始化 serviceKey, 供全局调用
         *
         * @param serviceKey gateway 传入 服务标识
         */
        private void prepareGatewayServiceHandle(WxGatewayServiceKeyEnum serviceKey) {
            requestSeqNum.incrementAndGet();
            gatewayServiceKeyHolder.set(serviceKey);
        }
    
        /**
         * 转换需要处理的 uri 资源请求
         *
         * @param parameters 原始参数
         * @param serviceKey serviceKey gateway 传入 服务标识
         * @return 如 /xxx/text
         */
        private String exchangeHandleUri(Map<String, String> parameters, WxGatewayServiceKeyEnum serviceKey) {
            String msgType = parameters.get("MsgType");
            String eventType = parameters.get("Event");
            String handleUri = serviceKey.getAlias() + "/" + msgType;
            if(eventType != null) {
                handleUri = handleUri + "/" + eventType;
            }
            return handleUri;
        }
    
        /**
         * 调用处理方法
         *
         * @param parameters 参数请求
         * @param handleUri uri
         * @return 处理结果
         * @throws NoSuchMethodException
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        private Object invokeHandleMethod(Map<String, String> parameters, String handleUri)
                throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            Object retMessage = "";     // 回复null会有bug
    
            // 获取handleMethod
            WxMessageRequestMappingInfo methodConfig = wxMessageHandlerMappingBean.getHandlerConfig(handleUri);
    
            if(methodConfig != null) {
                WxMessageHandleService handleService = (WxMessageHandleService) SpringContextsUtil.getBean(methodConfig.getHandlerClz());
                WxMessageBaseReqModel paramsToHandle = (WxMessageBaseReqModel) JSONObject.parseObject(JSONObject.toJSONString(parameters), methodConfig.getParamClz());
                if(StringUtils.isBlank(methodConfig.getMethodName())) {
                    retMessage = handleService.handle(paramsToHandle);
                }
                else {
                    String methodName = methodConfig.getMethodName();
                    retMessage = MethodUtils.invokeExactMethod(handleService, methodName, paramsToHandle);
                }
            }
            else {
                log.info("【微信消息处理】no handler found for {}", handleUri);
            }
            return retMessage;
        }
    
        /**
         * 获取gateway 进来的 serviceKey
         *
         * @return 自拥有的 serviceKey 服务标识
         */
        public WxGatewayServiceKeyEnum getGatewayServiceKey() {
            return gatewayServiceKeyHolder.get();
        }
    
        /**
         * 操作完成后,重置 serviceKey
         */
        private void finishGatewayServiceHandle() {
            gatewayServiceKeyHolder.remove();
        }
    }
     
    如上分发类,主要做一主体的操作,如参数解析,uri 重新获取,实际业务方法的调用等;
    所以,关键点还是在于怎么调用业务方法?
    首先,看一下业务方法配置的获取:
     WxMessageHandlerMethodEnum methodConfig = wxMessageHandlerMappingBean.getHandlerConfig(handleUri);
    @Component
    @Slf4j
    public class WxMessageHandlerMappingBean implements InitializingBean, BeanNameAware,
            ApplicationContextAware {
    
        /**
         * 直接匹配的mapping
         */
        private final Map<String, WxMessageRequestMappingInfo> directHandlerMappings = new ConcurrentHashMap<>();
    
        /**
         * 使用正则匹配的mapping
         */
        private final Map<String, WxMessageRequestMappingInfo> patternHandlerMappings = new HashMap<>();
    
        // spring 式的 mapping
        private final Map<WxMessageRequestMappingInfo, HandlerMethod> springPatternMethodMappings = new ConcurrentHashMap<>();
    
        private transient String beanName;
    
        private transient ApplicationContext applicationContext;
    
        public void setBeanName(String name) {
            this.beanName = name;
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }
        /**
         * 获取处理方法配置,主要为处理类
         *     先精确匹配,不行再用正则匹配一次
         *
         * @param messageHandleUri uri,如 xxx/text
         * @return 处理类,一般需继承 {@link com.mobanker.weixin.dae.service.WxMessageHandleService}
         */
        public final WxMessageRequestMappingInfo getHandlerConfig(String messageHandleUri) {
            // 直接匹配
            WxMessageRequestMappingInfo handlerMethodConfig = directHandlerMappings.get(messageHandleUri);
            if(handlerMethodConfig != null) {
                return handlerMethodConfig;
            }
            // 正则匹配
            handlerMethodConfig = getPatternHandlerMapping(messageHandleUri);
            if(handlerMethodConfig != null) {
                return handlerMethodConfig;
            }
            return null;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            // 扫描处理方法
            initHandlerMethods();
        }
    
        /**
         * 正则路由注册
         *
         * @param methodConfig 路由配置
         */
        private void registerPatternHandlerMapping(WxMessageRequestMappingInfo methodConfig) {
            Pattern normalUriPattern = Pattern.compile("^[a-zA-Z0-9/\-_:\$]+$");
            if(!normalUriPattern.matcher(methodConfig.getLookup()).matches()) {
                String patternedUri = methodConfig.getLookup().replace("*", ".*");
                patternHandlerMappings.put(patternedUri, methodConfig);
            }
        }
    
        /**
         * 正则路由匹配
         *
         * @param messageHandleUri 如 xxx/event/(VIEW|CLICK)
         * @return 匹配到的方法或者 null
         */
        private WxMessageRequestMappingInfo getPatternHandlerMapping(String messageHandleUri) {
            for (Map.Entry<String, WxMessageRequestMappingInfo> config1 : patternHandlerMappings.entrySet()) {
                Pattern uriPattern = Pattern.compile(config1.getKey());
                if(uriPattern.matcher(messageHandleUri).matches()) {
                    return config1.getValue();
                }
            }
            return null;
        }
        protected ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        /**
         * Scan beans in the ApplicationContext, detect and register handler methods.
         * @see #isWxMessageHandler(Class)
         * @see #getMappingForMethod(Method, Class)
         */
        protected void initHandlerMethods() {
            if (log.isDebugEnabled()) {
                log.debug("Looking for request mappings in application context: " + getApplicationContext());
            }
    
            String[] beanNames =
                    getApplicationContext().getBeanNamesForType(Object.class);
    
            for (String beanName : beanNames) {
                if (isWxMessageHandler(getApplicationContext().getType(beanName))){
                    detectHandlerMethods(beanName);
                }
            }
    
        }
    
        protected boolean isWxMessageHandler(Class<?> beanType) {
            return ((AnnotationUtils.findAnnotation(beanType, WxMessageHandler.class) != null));
        }
    
        /**
         * Look for handler methods in a handler.
         * @param handler the bean name of a handler or a handler instance
         */
        protected void detectHandlerMethods(final Object handler) {
            Class<?> handlerType =
                    (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
    
            // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
            final Map<Method, WxMessageRequestMappingInfo> mappings = new IdentityHashMap<>();
            final Class<?> userType = ClassUtils.getUserClass(handlerType);
    
            Set<Method> methods = MethodIntrospector.selectMethods(userType, new ReflectionUtils.MethodFilter() {
                @Override
                public boolean matches(Method method) {
                    WxMessageRequestMappingInfo mapping = getMappingForMethod(method, userType);
                    if (mapping != null) {
                        mappings.put(method, mapping);
                        return true;
                    }
                    else {
                        return false;
                    }
                }
            });
    
            for (Method method : methods) {
                registerHandlerMethod(handler, method, mappings.get(method));
            }
        }
    
        /**
         * Uses method and type-level @{@link RequestMapping} annotations to create
         * the RequestMappingInfo.
         * @return the created RequestMappingInfo, or {@code null} if the method
         * does not have a {@code @RequestMapping} annotation.
         */
        protected WxMessageRequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
            WxMessageRequestMappingInfo info = null;
            WxMessageRequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, WxMessageRequestMapping.class);
            if (methodAnnotation != null) {
                info = createRequestMappingInfo(methodAnnotation, handlerType, method);
            }
            return info;
        }
    
        /**
         * Created a RequestMappingInfo from a RequestMapping annotation.
         */
        protected WxMessageRequestMappingInfo createRequestMappingInfo(WxMessageRequestMapping annotation,
                                                                       Class<?> handlerType, Method method) {
            String uri = annotation.value();
            return new WxMessageRequestMappingInfo(
                    uri,
                    handlerType,
                    method.getName(),
                    method.getParameterTypes()[0],
                    "auto gen"
            );
        }
    
    
        /**
         * Register a handler method and its unique mapping.
         * @param handler the bean name of the handler or the handler instance
         * @param method the method to register
         * @param mapping the mapping conditions associated with the handler method
         * @throws IllegalStateException if another method was already registered
         * under the same mapping
         */
        protected void registerHandlerMethod(Object handler, Method method, WxMessageRequestMappingInfo mapping) {
            // old gen
            this.directHandlerMappings.put(mapping.getLookup(), mapping);
            registerPatternHandlerMapping(mapping);
    
            // new gen
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            springPatternMethodMappings.put(mapping, handlerMethod);
    
            log.info("Mapped WxMessageHandler {}", mapping);
        }
    
    
        protected HandlerMethod createHandlerMethod(Object handler, Method method) {
            HandlerMethod handlerMethod;
            if (handler instanceof String) {
                String beanName = (String) handler;
                handlerMethod = new HandlerMethod(beanName,
                        getApplicationContext().getAutowireCapableBeanFactory(), method);
            }
            else {
                handlerMethod = new HandlerMethod(handler, method);
            }
            return handlerMethod;
        }
    
    }

    handler 的注解配置如下:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface WxMessageHandler {
    
    
        /**
         * 路由映射 url
         *
         * @return 如: xxx/text
         */
        String value() default "";
    
    }

    路由配置 requestMapping 如下:

    public class WxMessageRequestMappingInfo {
    
        /**
         * 消息处理路由
         */
        private String lookup;
    
        /**
         * 处理类,调用期 handle 方法*/
        private Class<?> handlerClz;
    
        /**
         * 处理方法名称
         */
        private String methodName;
    
        /**
         * 方法参数
         */
        private Class<?> paramClz;
    
        /**
         * 备注
         */
        private String remark;
    
    
        public WxMessageRequestMappingInfo(String lookup, @NotNull Class<?> handlerClz, String methodName,
                                           Class<?> paramClz, String remark) {
            this.lookup = lookup;
            this.handlerClz = handlerClz;
            this.methodName = methodName;
            this.paramClz = paramClz;
            this.remark = remark;
        }
    
        public String getLookup() {
            return lookup;
        }
    
        public Class<?> getHandlerClz() {
            return handlerClz;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public Class<?> getParamClz() {
            return paramClz;
        }
    
        public String getRemark() {
            return remark;
        }
    
        @Override
        public String toString() {
            return ""{[" + lookup + "]}" onto @" + handlerClz.getName() + "#" + methodName + " : " + remark;
        }
    }

    使用时,只需在类上添加注解 @WxMessageHandler 即可

    @WxMessageHandler

    在方法上加上 路由注解即可: 

        @WxMessageRequestMapping(value = "xxx/event/subscribe")

    如:

    @WxMessageHandler
    public class SxkEventPushServiceImpl implements WxEventPushHandleService {
    
        @WxMessageRequestMapping(value = "xxx/event/subscribe")
        @Override
        public Object subscribe(WxEventPushSubscribeReqModel reqModel) {
            System.out.println("hello, welcome.")
            return "hello, welcome.";
        }
    }

      方法配置做两件事:

      1. 在启动时,将hander 添加的 mappings 中;

      2. 在使用时,从mappings 中获取 handler 信息;

    其实现原理与 spring 的 handlerMappings 类似!

      这里的参数解析,只处理了第一个参数,假设方法只能使用一个参数!

      找到处理方法后,就可以进行反射调用了!

     
  • 相关阅读:
    Python实例 包机制
    Python实例 类和继承
    python实例 文件处理
    python实例 异常处理
    配置 Apache+php多端口多站点(转载)
    C#中Delegate和Event以及它们的区别(转载)
    LINQ to SQL更新数据库操作(转载)
    创业公司如何实施敏捷开发(转)
    利用ps橡皮擦工具快速抠图
    XP win2003系统 微软雅黑字体的使用方法
  • 原文地址:https://www.cnblogs.com/yougewe/p/10228959.html
Copyright © 2011-2022 走看看