From 54c8ae54520524544eb5d17650949c10fb4c243a Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sat, 22 Jun 2019 16:50:31 +0800 Subject: [PATCH] =?UTF-8?q?#1079=20=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9=E8=BD=AC=E5=8F=91=E5=AE=A2=E6=9C=8D=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8A=9F=E8=83=BD=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../miniapp/message/WxMaMessageHandler.java | 17 ++++-- .../message/WxMaMessageInterceptor.java | 10 ++- .../miniapp/message/WxMaMessageMatcher.java | 7 ++- .../wx/miniapp/message/WxMaMessageRouter.java | 35 ++++------- .../message/WxMaMessageRouterRule.java | 42 +++++++------ .../wx/miniapp/message/WxMaXmlOutMessage.java | 60 ++++++++++++++++++ .../wx/miniapp/demo/WxMaDemoServer.java | 61 ++++++++++++------- .../wx/miniapp/demo/WxMaPortalServlet.java | 37 ++++++----- 8 files changed, 179 insertions(+), 90 deletions(-) create mode 100644 weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java index f60edc1d..9fdd9569 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageHandler.java @@ -8,13 +8,22 @@ import me.chanjar.weixin.common.session.WxSessionManager; import java.util.Map; /** - * 处理小程序推送消息的处理器接口 + * 处理小程序推送消息的处理器接口. * * @author Binary Wang */ public interface WxMaMessageHandler { - - void handle(WxMaMessage message, Map context, - WxMaService service, WxSessionManager sessionManager) throws WxErrorException; + /** + * 处理消息. + * + * @param message 输入消息 + * @param context 上下文 + * @param service 服务类 + * @param sessionManager session管理器 + * @return 输出消息 + * @throws WxErrorException 异常 + */ + WxMaXmlOutMessage handle(WxMaMessage message, Map context, + WxMaService service, WxSessionManager sessionManager) throws WxErrorException; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageInterceptor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageInterceptor.java index ef0d500a..31600a23 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageInterceptor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageInterceptor.java @@ -8,17 +8,21 @@ import me.chanjar.weixin.common.session.WxSessionManager; import java.util.Map; /** - * 微信消息拦截器,可以用来做验证 + * 微信消息拦截器,可以用来做验证. * * @author Binary Wang */ public interface WxMaMessageInterceptor { /** - * 拦截微信消息 + * 拦截微信消息. * - * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @param wxMessage . + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @param wxMaService . + * @param sessionManager . * @return true代表OK,false代表不OK + * @throws WxErrorException . */ boolean intercept(WxMaMessage wxMessage, Map context, diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageMatcher.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageMatcher.java index 9d4e54f8..5283713c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageMatcher.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageMatcher.java @@ -3,14 +3,17 @@ package cn.binarywang.wx.miniapp.message; import cn.binarywang.wx.miniapp.bean.WxMaMessage; /** - * 消息匹配器,用在消息路由的时候 + * 消息匹配器,用在消息路由的时候. * * @author Binary Wang */ public interface WxMaMessageMatcher { /** - * 消息是否匹配某种模式 + * 消息是否匹配某种模式. + * + * @param message 消息 + * @return 是否匹配 */ boolean match(WxMaMessage message); diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java index be3af708..bda2e6c8 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java @@ -32,8 +32,6 @@ public class WxMaMessageRouter { private ExecutorService executorService; - private WxMessageDuplicateChecker messageDuplicateChecker; - private WxSessionManager sessionManager; private WxErrorExceptionHandler exceptionHandler; @@ -43,7 +41,6 @@ public class WxMaMessageRouter { ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxMaMessageRouter-pool-%d").build(); this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), namedThreadFactory); - this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); this.sessionManager = new StandardSessionManager(); this.exceptionHandler = new LogExceptionHandler(); } @@ -58,16 +55,6 @@ public class WxMaMessageRouter { this.executorService = executorService; } - /** - *
-   * 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker}
-   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker}
-   * 
- */ - public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { - this.messageDuplicateChecker = messageDuplicateChecker; - } - /** *
    * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
@@ -93,16 +80,16 @@ public class WxMaMessageRouter {
   }
 
   /**
-   * 开始一个新的Route规则
+   * 开始一个新的Route规则.
    */
   public WxMaMessageRouterRule rule() {
     return new WxMaMessageRouterRule(this);
   }
 
   /**
-   * 处理微信消息
+   * 处理微信消息.
    */
-  public void route(final WxMaMessage wxMessage, final Map context) {
+  private WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map context) {
     final List matchRules = new ArrayList<>();
     // 收集匹配的规则
     for (final WxMaMessageRouterRule rule : this.rules) {
@@ -115,10 +102,11 @@ public class WxMaMessageRouter {
     }
 
     if (matchRules.size() == 0) {
-      return;
+      return null;
     }
 
     final List> futures = new ArrayList<>();
+    WxMaXmlOutMessage result = null;
     for (final WxMaMessageRouterRule rule : matchRules) {
       // 返回最后一个非异步的rule的执行结果
       if (rule.isAsync()) {
@@ -131,7 +119,7 @@ public class WxMaMessageRouter {
           })
         );
       } else {
-        rule.service(wxMessage, context, this.wxMaService, this.sessionManager, this.exceptionHandler);
+        result = rule.service(wxMessage, context, this.wxMaService, this.sessionManager, this.exceptionHandler);
         // 在同步操作结束,session访问结束
         this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
         sessionEndAccess(wxMessage);
@@ -155,18 +143,17 @@ public class WxMaMessageRouter {
         }
       });
     }
-
+    return result;
   }
 
-  public void route(final WxMaMessage wxMessage) {
-    this.route(wxMessage, new HashMap(2));
+  public WxMaXmlOutMessage route(final WxMaMessage wxMessage) {
+   return this.route(wxMessage, new HashMap(2));
   }
 
   /**
-   * 对session的访问结束
+   * 对session的访问结束.
    */
-  protected void sessionEndAccess(WxMaMessage wxMessage) {
-
+  private void sessionEndAccess(WxMaMessage wxMessage) {
     InternalSession session = ((InternalSessionManager) this.sessionManager).findSession(wxMessage.getFromUser());
     if (session != null) {
       session.endAccess();
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
index 2e4906eb..5ea3fb8a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java
@@ -16,7 +16,6 @@ import java.util.regex.Pattern;
  * @author Binary Wang
  */
 public class WxMaMessageRouterRule {
-
   private final WxMaMessageRouter routerBuilder;
 
   private boolean async = true;
@@ -46,7 +45,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 设置是否异步执行,默认是true
+   * 设置是否异步执行,默认是true.
    */
   public WxMaMessageRouterRule async(boolean async) {
     this.async = async;
@@ -54,7 +53,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果msgType等于某值
+   * 如果msgType等于某值.
    */
   public WxMaMessageRouterRule msgType(String msgType) {
     this.msgType = msgType;
@@ -62,7 +61,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果event等于某值
+   * 如果event等于某值.
    */
   public WxMaMessageRouterRule event(String event) {
     this.event = event;
@@ -70,7 +69,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果eventKey等于某值
+   * 如果eventKey等于某值.
    */
   public WxMaMessageRouterRule eventKey(String eventKey) {
     this.eventKey = eventKey;
@@ -78,7 +77,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果content等于某值
+   * 如果content等于某值.
    */
   public WxMaMessageRouterRule content(String content) {
     this.content = content;
@@ -86,7 +85,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果content匹配该正则表达式
+   * 如果content匹配该正则表达式.
    */
   public WxMaMessageRouterRule rContent(String regex) {
     this.rContent = regex;
@@ -94,7 +93,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果fromUser等于某值
+   * 如果fromUser等于某值.
    */
   public WxMaMessageRouterRule fromUser(String fromUser) {
     this.fromUser = fromUser;
@@ -102,7 +101,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候
+   * 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候.
    */
   public WxMaMessageRouterRule matcher(WxMaMessageMatcher matcher) {
     this.matcher = matcher;
@@ -110,14 +109,14 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 设置微信消息拦截器
+   * 设置微信消息拦截器.
    */
   public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor) {
     return interceptor(interceptor, (WxMaMessageInterceptor[]) null);
   }
 
   /**
-   * 设置微信消息拦截器
+   * 设置微信消息拦截器.
    */
   public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor, WxMaMessageInterceptor... otherInterceptors) {
     this.interceptors.add(interceptor);
@@ -130,14 +129,14 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 设置微信消息处理器
+   * 设置微信消息处理器.
    */
   public WxMaMessageRouterRule handler(WxMaMessageHandler handler) {
     return handler(handler, (WxMaMessageHandler[]) null);
   }
 
   /**
-   * 设置微信消息处理器
+   * 设置微信消息处理器.
    */
   public WxMaMessageRouterRule handler(WxMaMessageHandler handler, WxMaMessageHandler... otherHandlers) {
     this.handlers.add(handler);
@@ -150,7 +149,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
+   * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则.
    */
   public WxMaMessageRouter end() {
     this.routerBuilder.getRules().add(this);
@@ -158,7 +157,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 规则结束,但是消息还会进入其他规则
+   * 规则结束,但是消息还会进入其他规则.
    */
   public WxMaMessageRouter next() {
     this.reEnter = true;
@@ -166,7 +165,7 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 将微信自定义的事件修正为不区分大小写,
+   * 将微信自定义的事件修正为不区分大小写.
    * 比如框架定义的事件常量为click,但微信传递过来的却是CLICK
    */
   protected boolean test(WxMaMessage wxMessage) {
@@ -188,9 +187,9 @@ public class WxMaMessageRouterRule {
   }
 
   /**
-   * 处理微信推送过来的消息
+   * 处理微信推送过来的消息.
    */
-  protected void service(WxMaMessage wxMessage,
+  protected WxMaXmlOutMessage service(WxMaMessage wxMessage,
                          Map context,
                          WxMaService wxMaService,
                          WxSessionManager sessionManager,
@@ -199,11 +198,12 @@ public class WxMaMessageRouterRule {
       context = new HashMap<>(16);
     }
 
+    WxMaXmlOutMessage outMessage = null;
     try {
       // 如果拦截器不通过
       for (WxMaMessageInterceptor interceptor : this.interceptors) {
         if (!interceptor.intercept(wxMessage, context, wxMaService, sessionManager)) {
-          return;
+          return null;
         }
       }
 
@@ -213,11 +213,13 @@ public class WxMaMessageRouterRule {
         if (handler == null) {
           continue;
         }
-        handler.handle(wxMessage, context, wxMaService, sessionManager);
+        outMessage = handler.handle(wxMessage, context, wxMaService, sessionManager);
       }
     } catch (WxErrorException e) {
       exceptionHandler.handle(e);
     }
+
+    return outMessage;
   }
 
   public WxMaMessageRouter getRouterBuilder() {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java
new file mode 100644
index 00000000..7211e053
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaXmlOutMessage.java
@@ -0,0 +1,60 @@
+package cn.binarywang.wx.miniapp.message;
+
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
+import cn.binarywang.wx.miniapp.util.xml.XStreamTransformer;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
+
+import java.io.Serializable;
+
+/**
+ * 微信小程序输出给微信服务器的消息.
+ *
+ * @author Binary Wang
+ * @date 2019-06-22
+ */
+@Data
+@XStreamAlias("xml")
+@Accessors(chain = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class WxMaXmlOutMessage implements Serializable {
+  private static final long serialVersionUID = 4241135225946919153L;
+
+  @XStreamAlias("ToUserName")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  protected String toUserName;
+
+  @XStreamAlias("FromUserName")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  protected String fromUserName;
+
+  @XStreamAlias("CreateTime")
+  protected Long createTime;
+
+  @XStreamAlias("MsgType")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  protected String msgType;
+
+  @SuppressWarnings("unchecked")
+  public String toXml() {
+    return XStreamTransformer.toXml((Class) this.getClass(), this);
+  }
+
+  /**
+   * 转换成加密的xml格式.
+   */
+  public String toEncryptedXml(WxMaConfig config) {
+    String plainXml = toXml();
+    WxMaCryptUtils pc = new WxMaCryptUtils(config);
+    return pc.encrypt(plainXml);
+  }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
index fe886f4b..89df2444 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaDemoServer.java
@@ -1,15 +1,5 @@
 package cn.binarywang.wx.miniapp.demo;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
 import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
@@ -20,11 +10,23 @@ import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
 import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
+import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
 import cn.binarywang.wx.miniapp.test.TestConfig;
 import com.google.common.collect.Lists;
+import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.session.WxSessionManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * @author Binary Wang
@@ -33,29 +35,31 @@ public class WxMaDemoServer {
 
   private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() {
     @Override
-    public void handle(WxMaMessage wxMessage, Map context,
-                       WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+    public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
+                                    WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
       System.out.println("收到消息:" + wxMessage.toString());
       service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
         .toUser(wxMessage.getFromUser()).build());
+      return null;
     }
   };
 
   private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() {
     @Override
-    public void handle(WxMaMessage wxMessage, Map context,
-                       WxMaService service, WxSessionManager sessionManager)
+    public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
+                                    WxMaService service, WxSessionManager sessionManager)
       throws WxErrorException {
       service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
         .toUser(wxMessage.getFromUser()).build());
+      return null;
     }
 
   };
 
   private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() {
     @Override
-    public void handle(WxMaMessage wxMessage, Map context,
-                       WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+    public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
+                                    WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
       try {
         WxMediaUploadResult uploadResult = service.getMediaService()
           .uploadMedia(WxMaConstants.MediaType.IMAGE, "png",
@@ -69,13 +73,14 @@ public class WxMaDemoServer {
       } catch (WxErrorException e) {
         e.printStackTrace();
       }
+      return null;
     }
   };
 
   private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() {
     @Override
-    public void handle(WxMaMessage wxMessage, Map context,
-                       WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
+    public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
+                                    WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
       try {
         final File file = service.getQrcodeService().createQrcode("123", 430);
         WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia(WxMaConstants.MediaType.IMAGE, file);
@@ -88,13 +93,14 @@ public class WxMaDemoServer {
       } catch (WxErrorException e) {
         e.printStackTrace();
       }
+      return null;
     }
   };
 
   private static final WxMaMessageHandler templateMsgHandler = new WxMaMessageHandler() {
     @Override
-    public void handle(WxMaMessage wxMessage, Map context,
-                       WxMaService service, WxSessionManager sessionManager)
+    public WxMaXmlOutMessage handle(WxMaMessage wxMessage, Map context,
+                                    WxMaService service, WxSessionManager sessionManager)
       throws WxErrorException {
       service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.builder()
         .templateId(templateId).data(Lists.newArrayList(
@@ -102,10 +108,22 @@ public class WxMaDemoServer {
         .toUser(wxMessage.getFromUser())
         .formId("自己替换可用的formid")
         .build());
+      return null;
     }
 
   };
 
+  private static final WxMaMessageHandler customerServiceMessageHandler = new WxMaMessageHandler() {
+    @Override
+    public WxMaXmlOutMessage handle(WxMaMessage message, Map context, WxMaService service, WxSessionManager sessionManager) {
+      return new WxMaXmlOutMessage()
+        .setMsgType(WxConsts.XmlMsgType.TRANSFER_CUSTOMER_SERVICE)
+        .setFromUserName(message.getToUser())
+        .setCreateTime(Calendar.getInstance().getTimeInMillis() / 1000)
+        .setToUserName(message.getFromUser());
+    }
+  };
+
   private static WxMaConfig config;
   private static WxMaService service;
   private static WxMaMessageRouter router;
@@ -142,7 +160,8 @@ public class WxMaDemoServer {
         .rule().async(false).content("模板").handler(templateMsgHandler).end()
         .rule().async(false).content("文本").handler(textHandler).end()
         .rule().async(false).content("图片").handler(picHandler).end()
-        .rule().async(false).content("二维码").handler(qrcodeHandler).end();
+        .rule().async(false).content("二维码").handler(qrcodeHandler).end()
+        .rule().async(false).content("转发客服").handler(customerServiceMessageHandler).end();
     } catch (IOException e) {
       e.printStackTrace();
     }
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
index 5cadb689..c209082d 100644
--- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/demo/WxMaPortalServlet.java
@@ -5,6 +5,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaMessage;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
+import cn.binarywang.wx.miniapp.message.WxMaXmlOutMessage;
+import lombok.AllArgsConstructor;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 
@@ -18,19 +20,13 @@ import java.util.Objects;
 /**
  * @author Binary Wang
  */
+@AllArgsConstructor
 public class WxMaPortalServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
 
-  private WxMaConfig wxMaConfig;
-  private WxMaService wxMaService;
-  private WxMaMessageRouter wxMaMessageRouter;
-
-  WxMaPortalServlet(WxMaConfig wxMaConfig, WxMaService wxMaService,
-                    WxMaMessageRouter wxMaMessageRouter) {
-    this.wxMaConfig = wxMaConfig;
-    this.wxMaService = wxMaService;
-    this.wxMaMessageRouter = wxMaMessageRouter;
-  }
+  private WxMaConfig config;
+  private WxMaService service;
+  private WxMaMessageRouter messageRouter;
 
   @Override
   protected void service(HttpServletRequest request, HttpServletResponse response)
@@ -42,7 +38,7 @@ public class WxMaPortalServlet extends HttpServlet {
     String nonce = request.getParameter("nonce");
     String timestamp = request.getParameter("timestamp");
 
-    if (!this.wxMaService.checkSignature(timestamp, nonce, signature)) {
+    if (!this.service.checkSignature(timestamp, nonce, signature)) {
       // 消息签名不正确,说明不是公众平台发过来的消息
       response.getWriter().println("非法请求");
       return;
@@ -56,7 +52,7 @@ public class WxMaPortalServlet extends HttpServlet {
     }
 
     String encryptType = request.getParameter("encrypt_type");
-    final boolean isJson = Objects.equals(this.wxMaConfig.getMsgDataFormat(), WxMaConstants.MsgDataFormat.JSON);
+    final boolean isJson = Objects.equals(this.config.getMsgDataFormat(), WxMaConstants.MsgDataFormat.JSON);
     if (StringUtils.isBlank(encryptType)) {
       // 明文传输的消息
       WxMaMessage inMessage;
@@ -66,7 +62,12 @@ public class WxMaPortalServlet extends HttpServlet {
         inMessage = WxMaMessage.fromXml(request.getInputStream());
       }
 
-      this.wxMaMessageRouter.route(inMessage);
+      final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
+      if (outMessage != null) {
+        response.getWriter().write(outMessage.toXml());
+        return;
+      }
+
       response.getWriter().write("success");
       return;
     }
@@ -76,12 +77,16 @@ public class WxMaPortalServlet extends HttpServlet {
       String msgSignature = request.getParameter("msg_signature");
       WxMaMessage inMessage;
       if (isJson) {
-        inMessage = WxMaMessage.fromEncryptedJson(request.getInputStream(), this.wxMaConfig);
+        inMessage = WxMaMessage.fromEncryptedJson(request.getInputStream(), this.config);
       } else {//xml
-        inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.wxMaConfig, timestamp, nonce, msgSignature);
+        inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.config, timestamp, nonce, msgSignature);
       }
 
-      this.wxMaMessageRouter.route(inMessage);
+      final WxMaXmlOutMessage outMessage = this.messageRouter.route(inMessage);
+      if (outMessage != null) {
+        response.getWriter().write(outMessage.toEncryptedXml(this.config));
+        return;
+      }
       response.getWriter().write("success");
       return;
     }