Переглянути джерело

添加企业微信相关配置

M墨鱼—_mo 5 роки тому
батько
коміт
2047deda96
27 змінених файлів з 1255 додано та 2 видалено
  1. 35 1
      common/pom.xml
  2. 57 0
      common/src/main/java/com/jpsoft/smart/config/WxCpConfig.java
  3. 16 0
      common/src/main/java/com/jpsoft/smart/cpbuilder/AbstractBuilder.java
  4. 22 0
      common/src/main/java/com/jpsoft/smart/cpbuilder/TextBuilder.java
  5. 13 0
      common/src/main/java/com/jpsoft/smart/cphandler/AbstractHandler.java
  6. 30 0
      common/src/main/java/com/jpsoft/smart/cphandler/ContactChangeHandler.java
  7. 29 0
      common/src/main/java/com/jpsoft/smart/cphandler/EnterAgentHandler.java
  8. 43 0
      common/src/main/java/com/jpsoft/smart/cphandler/LocationHandler.java
  9. 24 0
      common/src/main/java/com/jpsoft/smart/cphandler/LogHandler.java
  10. 35 0
      common/src/main/java/com/jpsoft/smart/cphandler/MenuHandler.java
  11. 38 0
      common/src/main/java/com/jpsoft/smart/cphandler/MsgHandler.java
  12. 24 0
      common/src/main/java/com/jpsoft/smart/cphandler/NullHandler.java
  13. 8 0
      common/src/main/java/com/jpsoft/smart/cphandler/ScanHandler.java
  14. 62 0
      common/src/main/java/com/jpsoft/smart/cphandler/SubscribeHandler.java
  15. 27 0
      common/src/main/java/com/jpsoft/smart/cphandler/UnsubscribeHandler.java
  16. 4 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/LApiUtil.java
  17. 60 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/AesException.java
  18. 26 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/ByteGroup.java
  19. 67 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/PKCS7Encoder.java
  20. 61 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/SHA1.java
  21. 291 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/WXBizMsgCrypt.java
  22. 72 0
      common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/XMLParse.java
  23. 1 0
      web/src/main/java/com/jpsoft/smart/config/WebMvcConfig.java
  24. 129 0
      web/src/main/java/com/jpsoft/smart/config/WxCpConfiguration.java
  25. 56 0
      web/src/main/java/com/jpsoft/smart/modules/wechat/controller/WxCpController.java
  26. 11 0
      web/src/main/resources/application-production.yml
  27. 14 1
      web/src/main/resources/application-test.yml

+ 35 - 1
common/pom.xml

@@ -22,11 +22,25 @@
         <poi.version>4.1.0</poi.version>
     </properties>
     <dependencies>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.9</version>
+        </dependency>
+
+
         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi</artifactId>
             <version>${poi.version}</version>
             <scope>compile</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>commons-codec</artifactId>
+                    <groupId>commons-codec</groupId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.apache.poi</groupId>
@@ -44,7 +58,13 @@
             <groupId>org.apache.poi</groupId>
             <artifactId>poi-ooxml-schemas</artifactId>
             <version>${poi.version}</version>
-            <scope>compile</scope>
+            <!--  <scope>compile</scope>-->
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-codec</groupId>
+                    <artifactId>commons-codec</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -63,6 +83,12 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-codec</groupId>
+                    <artifactId>commons-codec</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>io.netty</groupId>
@@ -161,6 +187,14 @@
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-cp</artifactId>
+            <version>3.7.0</version>
+        </dependency>
+
+
     </dependencies>
 
 </project>

+ 57 - 0
common/src/main/java/com/jpsoft/smart/config/WxCpConfig.java

@@ -0,0 +1,57 @@
+package com.jpsoft.smart.config;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author 墨鱼_mo
+ * @date 2020-4-1 11:47
+ */
+@Component
+@ConfigurationProperties(prefix = "wx.cp")
+@Data
+public class WxCpConfig {
+
+    /**
+     * 设置微信企业号的corpId
+     */
+    private String corpId;
+
+    private List<AppConfig> appConfigs;
+
+
+    @Getter
+    @Setter
+    public static class AppConfig {
+        /**
+         * 设置微信企业应用的AgentId
+         */
+        private Integer agentId;
+
+        /**
+         * 设置微信企业应用的Secret
+         */
+        private String secret;
+
+        /**
+         * 设置微信企业号的token
+         */
+        private String token;
+
+        /**
+         * 设置微信企业号的EncodingAESKey
+         */
+        private String aesKey;
+
+    }
+
+
+
+
+
+}

+ 16 - 0
common/src/main/java/com/jpsoft/smart/cpbuilder/AbstractBuilder.java

@@ -0,0 +1,16 @@
+package com.jpsoft.smart.cpbuilder;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *  @author Binary Wang(https://github.com/binarywang)
+ */
+public abstract class AbstractBuilder {
+  protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+  public abstract WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service);
+}

+ 22 - 0
common/src/main/java/com/jpsoft/smart/cpbuilder/TextBuilder.java

@@ -0,0 +1,22 @@
+package com.jpsoft.smart.cpbuilder;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
+
+/**
+ *  @author Binary Wang(https://github.com/binarywang)
+ */
+public class TextBuilder extends AbstractBuilder {
+
+  @Override
+  public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage,
+                                 WxCpService service) {
+    WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content(content)
+        .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
+        .build();
+    return m;
+  }
+
+}

+ 13 - 0
common/src/main/java/com/jpsoft/smart/cphandler/AbstractHandler.java

@@ -0,0 +1,13 @@
+package com.jpsoft.smart.cphandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import me.chanjar.weixin.cp.message.WxCpMessageHandler;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+public abstract class AbstractHandler implements WxCpMessageHandler {
+  protected Logger logger = LoggerFactory.getLogger(getClass());
+}

+ 30 - 0
common/src/main/java/com/jpsoft/smart/cphandler/ContactChangeHandler.java

@@ -0,0 +1,30 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import com.jpsoft.smart.cpbuilder.TextBuilder;
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * 通讯录变更事件处理器.
+ *
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class ContactChangeHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+    String content = "收到通讯录变更事件,内容:" + wxMessage;
+    this.logger.info(content);
+
+    return new TextBuilder().build(content, wxMessage, cpService);
+  }
+
+}

+ 29 - 0
common/src/main/java/com/jpsoft/smart/cphandler/EnterAgentHandler.java

@@ -0,0 +1,29 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * <pre>
+ *
+ * Created by Binary Wang on 2018/8/27.
+ * </pre>
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ */
+@Slf4j
+public class EnterAgentHandler extends AbstractHandler {
+    private static final int TEST_AGENT = 1000002;
+
+    @Override
+    public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
+        // do something
+        return null;
+    }
+}

+ 43 - 0
common/src/main/java/com/jpsoft/smart/cphandler/LocationHandler.java

@@ -0,0 +1,43 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import com.jpsoft.smart.cpbuilder.TextBuilder;
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class LocationHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+    if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) {
+      //TODO 接收处理用户发送的地理位置消息
+      try {
+        String content = "感谢反馈,您的的地理位置已收到!";
+        return new TextBuilder().build(content, wxMessage, null);
+      } catch (Exception e) {
+        this.logger.error("位置消息接收处理失败", e);
+        return null;
+      }
+    }
+
+    //上报地理位置事件
+    this.logger.info("\n上报地理位置,纬度 : {}\n经度 : {}\n精度 : {}",
+        wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision()));
+
+    //TODO  可以将用户地理位置信息保存到本地数据库,以便以后使用
+
+    return null;
+  }
+
+}

+ 24 - 0
common/src/main/java/com/jpsoft/smart/cphandler/LogHandler.java

@@ -0,0 +1,24 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ *  @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class LogHandler extends AbstractHandler {
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+    this.logger.info("\n接收到请求消息,内容:{}", wxMessage);
+    return null;
+  }
+
+}

+ 35 - 0
common/src/main/java/com/jpsoft/smart/cphandler/MenuHandler.java

@@ -0,0 +1,35 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class MenuHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+
+    String msg = String.format("type:%s, event:%s, key:%s",
+        wxMessage.getMsgType(), wxMessage.getEvent(),
+        wxMessage.getEventKey());
+    if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
+      return null;
+    }
+
+    return WxCpXmlOutMessage.TEXT().content(msg)
+        .fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
+        .build();
+  }
+
+}

+ 38 - 0
common/src/main/java/com/jpsoft/smart/cphandler/MsgHandler.java

@@ -0,0 +1,38 @@
+package com.jpsoft.smart.cphandler;
+
+import com.jpsoft.smart.cpbuilder.TextBuilder;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class MsgHandler extends AbstractHandler {
+
+    @Override
+    public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                    WxSessionManager sessionManager) {
+        final String msgType = wxMessage.getMsgType();
+        if (msgType == null) {
+            // 如果msgType没有,就自己根据具体报文内容做处理
+        }
+
+        if (!msgType.equals(WxConsts.XmlMsgType.EVENT)) {
+            //TODO 可以选择将消息保存到本地
+        }
+
+        //TODO 组装回复消息
+        String content = "收到信息内容:" + wxMessage;
+
+        return new TextBuilder().build(content, wxMessage, cpService);
+
+    }
+
+}

+ 24 - 0
common/src/main/java/com/jpsoft/smart/cphandler/NullHandler.java

@@ -0,0 +1,24 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class NullHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+    return null;
+  }
+
+}

+ 8 - 0
common/src/main/java/com/jpsoft/smart/cphandler/ScanHandler.java

@@ -0,0 +1,8 @@
+package com.jpsoft.smart.cphandler;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+public abstract class ScanHandler extends AbstractHandler {
+
+}

+ 62 - 0
common/src/main/java/com/jpsoft/smart/cphandler/SubscribeHandler.java

@@ -0,0 +1,62 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import com.jpsoft.smart.cpbuilder.TextBuilder;
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpUser;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class SubscribeHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) throws WxErrorException {
+
+    this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUserName());
+
+    // 获取微信用户基本信息
+    WxCpUser userWxInfo = cpService.getUserService().getById(wxMessage.getFromUserName());
+
+    if (userWxInfo != null) {
+      // TODO 可以添加关注用户到本地
+    }
+
+    WxCpXmlOutMessage responseResult = null;
+    try {
+      responseResult = handleSpecial(wxMessage);
+    } catch (Exception e) {
+      this.logger.error(e.getMessage(), e);
+    }
+
+    if (responseResult != null) {
+      return responseResult;
+    }
+
+    try {
+      return new TextBuilder().build("感谢关注", wxMessage, cpService);
+    } catch (Exception e) {
+      this.logger.error(e.getMessage(), e);
+    }
+
+    return null;
+  }
+
+  /**
+   * 处理特殊请求,比如如果是扫码进来的,可以做相应处理
+   */
+  private WxCpXmlOutMessage handleSpecial(WxCpXmlMessage wxMessage) {
+    //TODO
+    return null;
+  }
+
+}

+ 27 - 0
common/src/main/java/com/jpsoft/smart/cphandler/UnsubscribeHandler.java

@@ -0,0 +1,27 @@
+package com.jpsoft.smart.cphandler;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+
+/**
+ * @author Binary Wang(https://github.com/binarywang)
+ */
+@Component
+public class UnsubscribeHandler extends AbstractHandler {
+
+  @Override
+  public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
+                                  WxSessionManager sessionManager) {
+    String openId = wxMessage.getFromUserName();
+    this.logger.info("取消关注用户 OPENID: " + openId);
+    // TODO 可以更新本地数据库为取消关注状态
+    return null;
+  }
+
+}

+ 4 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/LApiUtil.java

@@ -118,6 +118,10 @@ public class LApiUtil {
         else if (matchStatus == 3){
             matchMsg = "对比成功,不在布控时间";
         }
+        else if (matchStatus == 7){
+            matchMsg = "刷脸开门模式下,刷脸成功";
+        }
+
         else if (matchStatus == 10){
             matchMsg = "对比成功,人脸属性异常";
         }

+ 60 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/AesException.java

@@ -0,0 +1,60 @@
+package com.jpsoft.smart.modules.common.utils.enterprise;
+
+/**
+ * @author 墨鱼_mo
+ * @date 2020-4-1 11:52
+ */
+public class AesException extends Exception{
+    public final static int OK = 0;
+    public final static int ValidateSignatureError = -40001;
+    public final static int ParseXmlError = -40002;
+    public final static int ComputeSignatureError = -40003;
+    public final static int IllegalAesKey = -40004;
+    public final static int ValidateCorpidError = -40005;
+    public final static int EncryptAESError = -40006;
+    public final static int DecryptAESError = -40007;
+    public final static int IllegalBuffer = -40008;
+    //public final static int EncodeBase64Error = -40009;
+    //public final static int DecodeBase64Error = -40010;
+    //public final static int GenReturnXmlError = -40011;
+
+    private int code;
+
+    private static String getMessage(int code) {
+        switch (code) {
+            case ValidateSignatureError:
+                return "签名验证错误";
+            case ParseXmlError:
+                return "xml解析失败";
+            case ComputeSignatureError:
+                return "sha加密生成签名失败";
+            case IllegalAesKey:
+                return "SymmetricKey非法";
+            case ValidateCorpidError:
+                return "corpid校验失败";
+            case EncryptAESError:
+                return "aes加密失败";
+            case DecryptAESError:
+                return "aes解密失败";
+            case IllegalBuffer:
+                return "解密后得到的buffer非法";
+//		case EncodeBase64Error:
+//			return "base64加密错误";
+//		case DecodeBase64Error:
+//			return "base64解密错误";
+//		case GenReturnXmlError:
+//			return "xml生成失败";
+            default:
+                return null; // cannot be
+        }
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    AesException(int code) {
+        super(getMessage(code));
+        this.code = code;
+    }
+}

+ 26 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/ByteGroup.java

@@ -0,0 +1,26 @@
+package com.jpsoft.smart.modules.common.utils.enterprise;
+
+import java.util.ArrayList;
+
+class ByteGroup {
+	ArrayList<Byte> byteContainer = new ArrayList<Byte>();
+
+	public byte[] toBytes() {
+		byte[] bytes = new byte[byteContainer.size()];
+		for (int i = 0; i < byteContainer.size(); i++) {
+			bytes[i] = byteContainer.get(i);
+		}
+		return bytes;
+	}
+
+	public ByteGroup addBytes(byte[] bytes) {
+		for (byte b : bytes) {
+			byteContainer.add(b);
+		}
+		return this;
+	}
+
+	public int size() {
+		return byteContainer.size();
+	}
+}

+ 67 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/PKCS7Encoder.java

@@ -0,0 +1,67 @@
+/**
+ * 对公众平台发送给公众账号的消息加解密示例代码.
+ *
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+package com.jpsoft.smart.modules.common.utils.enterprise;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder {
+	static Charset CHARSET = Charset.forName("utf-8");
+	static int BLOCK_SIZE = 32;
+
+	/**
+	 * 获得对明文进行补位填充的字节.
+	 *
+	 * @param count 需要进行填充补位操作的明文字节个数
+	 * @return 补齐用的字节数组
+	 */
+	static byte[] encode(int count) {
+		// 计算需要填充的位数
+		int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
+		if (amountToPad == 0) {
+			amountToPad = BLOCK_SIZE;
+		}
+		// 获得补位所用的字符
+		char padChr = chr(amountToPad);
+		String tmp = new String();
+		for (int index = 0; index < amountToPad; index++) {
+			tmp += padChr;
+		}
+		return tmp.getBytes(CHARSET);
+	}
+
+	/**
+	 * 删除解密后明文的补位字符
+	 *
+	 * @param decrypted 解密后的明文
+	 * @return 删除补位字符后的明文
+	 */
+	static byte[] decode(byte[] decrypted) {
+		int pad = (int) decrypted[decrypted.length - 1];
+		if (pad < 1 || pad > 32) {
+			pad = 0;
+		}
+		return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
+	}
+
+	/**
+	 * 将数字转化成ASCII码对应的字符,用于对明文进行补码
+	 *
+	 * @param a 需要转化的数字
+	 * @return 转化得到的字符
+	 */
+	static char chr(int a) {
+		byte target = (byte) (a & 0xFF);
+		return (char) target;
+	}
+
+}

+ 61 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/SHA1.java

@@ -0,0 +1,61 @@
+/**
+ * 对公众平台发送给公众账号的消息加解密示例代码.
+ *
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+package com.jpsoft.smart.modules.common.utils.enterprise;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+/**
+ * SHA1 class
+ *
+ * 计算公众平台的消息签名接口.
+ */
+class SHA1 {
+
+	/**
+	 * 用SHA1算法生成安全签名
+	 * @param token 票据
+	 * @param timestamp 时间戳
+	 * @param nonce 随机字符串
+	 * @param encrypt 密文
+	 * @return 安全签名
+	 * @throws AesException
+	 */
+	public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException
+			  {
+		try {
+			String[] array = new String[] { token, timestamp, nonce, encrypt };
+			StringBuffer sb = new StringBuffer();
+			// 字符串排序
+			Arrays.sort(array);
+			for (int i = 0; i < 4; i++) {
+				sb.append(array[i]);
+			}
+			String str = sb.toString();
+			// SHA1签名生成
+			MessageDigest md = MessageDigest.getInstance("SHA-1");
+			md.update(str.getBytes());
+			byte[] digest = md.digest();
+
+			StringBuffer hexstr = new StringBuffer();
+			String shaHex = "";
+			for (int i = 0; i < digest.length; i++) {
+				shaHex = Integer.toHexString(digest[i] & 0xFF);
+				if (shaHex.length() < 2) {
+					hexstr.append(0);
+				}
+				hexstr.append(shaHex);
+			}
+			return hexstr.toString();
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new AesException(AesException.ComputeSignatureError);
+		}
+	}
+}

+ 291 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/WXBizMsgCrypt.java

@@ -0,0 +1,291 @@
+package com.jpsoft.smart.modules.common.utils.enterprise; /**
+ * 对公众平台发送给公众账号的消息加解密示例代码.
+ *
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * 针对org.apache.commons.codec.binary.Base64,
+ * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
+ * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
+ */
+
+
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
+ * <ol>
+ * 	<li>第三方回复加密消息给公众平台</li>
+ * 	<li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
+ * </ol>
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
+ * <ol>
+ * 	<li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
+ *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
+ * 	<li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
+ * 	<li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
+ * 	<li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
+ * </ol>
+ */
+public class WXBizMsgCrypt {
+    static Charset CHARSET = Charset.forName("utf-8");
+    Base64 base64 = new Base64();
+    byte[] aesKey;
+    String token;
+    String corpId;
+
+    /**
+     * 构造函数
+     * @param token 公众平台上,开发者设置的token
+     * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
+     * @param corpId 企业的corpid
+     *
+     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
+     */
+    public WXBizMsgCrypt(String token, String encodingAesKey, String corpId) throws AesException {
+        if (encodingAesKey.length() != 43) {
+            throw new AesException(AesException.IllegalAesKey);
+        }
+
+        this.token = token;
+        this.corpId = corpId;
+        aesKey = Base64.decodeBase64(encodingAesKey);
+    }
+
+    // 生成4个字节的网络字节序
+    byte[] getNetworkBytesOrder(int sourceNumber) {
+        byte[] orderBytes = new byte[4];
+        orderBytes[3] = (byte) (sourceNumber & 0xFF);
+        orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
+        orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
+        orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
+        return orderBytes;
+    }
+
+    // 还原4个字节的网络字节序
+    int recoverNetworkBytesOrder(byte[] orderBytes) {
+        int sourceNumber = 0;
+        for (int i = 0; i < 4; i++) {
+            sourceNumber <<= 8;
+            sourceNumber |= orderBytes[i] & 0xff;
+        }
+        return sourceNumber;
+    }
+
+    // 随机生成16位字符串
+    String getRandomStr() {
+        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < 16; i++) {
+            int number = random.nextInt(base.length());
+            sb.append(base.charAt(number));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 对明文进行加密.
+     *
+     * @param text 需要加密的明文
+     * @return 加密后base64编码的字符串
+     * @throws AesException aes加密失败
+     */
+    String encrypt(String randomStr, String text) throws AesException {
+        ByteGroup byteCollector = new ByteGroup();
+        byte[] randomStrBytes = randomStr.getBytes(CHARSET);
+        byte[] textBytes = text.getBytes(CHARSET);
+        byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
+        byte[] corpidBytes = corpId.getBytes(CHARSET);
+
+        // randomStr + networkBytesOrder + text + corpid
+        byteCollector.addBytes(randomStrBytes);
+        byteCollector.addBytes(networkBytesOrder);
+        byteCollector.addBytes(textBytes);
+        byteCollector.addBytes(corpidBytes);
+
+        // ... + pad: 使用自定义的填充方式对明文进行补位填充
+        byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
+        byteCollector.addBytes(padBytes);
+
+        // 获得最终的字节流, 未加密
+        byte[] unencrypted = byteCollector.toBytes();
+
+        try {
+            // 设置加密模式为AES的CBC模式
+            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
+            IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+
+            // 加密
+            byte[] encrypted = cipher.doFinal(unencrypted);
+
+            // 使用BASE64对加密后的字符串进行编码
+            String base64Encrypted = base64.encodeToString(encrypted);
+
+            return base64Encrypted;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new AesException(AesException.EncryptAESError);
+        }
+    }
+
+    /**
+     * 对密文进行解密.
+     *
+     * @param text 需要解密的密文
+     * @return 解密得到的明文
+     * @throws AesException aes解密失败
+     */
+    String decrypt(String text) throws AesException {
+        byte[] original;
+        try {
+            // 设置解密模式为AES的CBC模式
+            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+            SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
+            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
+            cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
+
+            // 使用BASE64对密文进行解码
+            byte[] encrypted = Base64.decodeBase64(text);
+
+            // 解密
+            original = cipher.doFinal(encrypted);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new AesException(AesException.DecryptAESError);
+        }
+
+        String xmlContent, from_corpid;
+        try {
+            // 去除补位字符
+            byte[] bytes = PKCS7Encoder.decode(original);
+
+            // 分离16位随机字符串,网络字节序和corpId
+            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
+
+            int xmlLength = recoverNetworkBytesOrder(networkOrder);
+
+            xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
+            from_corpid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
+                    CHARSET);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new AesException(AesException.IllegalBuffer);
+        }
+
+        // corpid不相同的情况
+        if (!from_corpid.equals(corpId)) {
+            throw new AesException(AesException.ValidateCorpidError);
+        }
+        return xmlContent;
+
+    }
+
+    /**
+     * 将公众平台回复用户的消息加密打包.
+     * <ol>
+     * 	<li>对要发送的消息进行AES-CBC加密</li>
+     * 	<li>生成安全签名</li>
+     * 	<li>将消息密文和安全签名打包成xml格式</li>
+     * </ol>
+     *
+     * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
+     * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
+     * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
+     *
+     * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
+     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
+     */
+    public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
+        // 加密
+        String encrypt = encrypt(getRandomStr(), replyMsg);
+
+        // 生成安全签名
+        if (timeStamp == "") {
+            timeStamp = Long.toString(System.currentTimeMillis());
+        }
+
+        String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt);
+
+        // System.out.println("发送给平台的签名是: " + signature[1].toString());
+        // 生成发送的xml
+        String result = XMLParse.generate(encrypt, signature, timeStamp, nonce);
+        return result;
+    }
+
+    /**
+     * 检验消息的真实性,并且获取解密后的明文.
+     * <ol>
+     * 	<li>利用收到的密文生成安全签名,进行签名验证</li>
+     * 	<li>若验证通过,则提取xml中的加密消息</li>
+     * 	<li>对消息进行解密</li>
+     * </ol>
+     *
+     * @param msgSignature 签名串,对应URL参数的msg_signature
+     * @param timeStamp 时间戳,对应URL参数的timestamp
+     * @param nonce 随机串,对应URL参数的nonce
+     * @param postData 密文,对应POST请求的数据
+     *
+     * @return 解密后的原文
+     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
+     */
+    public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
+            throws AesException {
+
+        // 密钥,公众账号的app secret
+        // 提取密文
+        Object[] encrypt = XMLParse.extract(postData);
+
+        // 验证安全签名
+        String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());
+
+        // 和URL中的签名比较是否相等
+        // System.out.println("第三方收到URL中的签名:" + msg_sign);
+        // System.out.println("第三方校验签名:" + signature);
+        if (!signature.equals(msgSignature)) {
+            throw new AesException(AesException.ValidateSignatureError);
+        }
+
+        // 解密
+        String result = decrypt(encrypt[1].toString());
+        return result;
+    }
+
+    /**
+     * 验证URL
+     * @param msgSignature 签名串,对应URL参数的msg_signature
+     * @param timeStamp 时间戳,对应URL参数的timestamp
+     * @param nonce 随机串,对应URL参数的nonce
+     * @param echoStr 随机串,对应URL参数的echostr
+     *
+     * @return 解密之后的echostr
+     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
+     */
+    public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr)
+            throws AesException {
+        String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);
+
+        if (!signature.equals(msgSignature)) {
+            throw new AesException(AesException.ValidateSignatureError);
+        }
+
+        String result = decrypt(echoStr);
+        return result;
+    }
+
+}

+ 72 - 0
common/src/main/java/com/jpsoft/smart/modules/common/utils/enterprise/XMLParse.java

@@ -0,0 +1,72 @@
+/**
+ * 对公众平台发送给公众账号的消息加解密示例代码.
+ *
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+package com.jpsoft.smart.modules.common.utils.enterprise;
+
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+/**
+ * XMLParse class
+ *
+ * 提供提取消息格式中的密文及生成回复消息格式的接口.
+ */
+class XMLParse {
+
+	/**
+	 * 提取出xml数据包中的加密消息
+	 * @param xmltext 待提取的xml字符串
+	 * @return 提取出的加密消息字符串
+	 * @throws AesException
+	 */
+	public static Object[] extract(String xmltext) throws AesException     {
+		Object[] result = new Object[3];
+		try {
+			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+			DocumentBuilder db = dbf.newDocumentBuilder();
+			StringReader sr = new StringReader(xmltext);
+			InputSource is = new InputSource(sr);
+			Document document = db.parse(is);
+
+			Element root = document.getDocumentElement();
+			NodeList nodelist1 = root.getElementsByTagName("Encrypt");
+			NodeList nodelist2 = root.getElementsByTagName("ToUserName");
+			result[0] = 0;
+			result[1] = nodelist1.item(0).getTextContent();
+			result[2] = nodelist2.item(0).getTextContent();
+			return result;
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new AesException(AesException.ParseXmlError);
+		}
+	}
+
+	/**
+	 * 生成xml消息
+	 * @param encrypt 加密后的消息密文
+	 * @param signature 安全签名
+	 * @param timestamp 时间戳
+	 * @param nonce 随机字符串
+	 * @return 生成的xml字符串
+	 */
+	public static String generate(String encrypt, String signature, String timestamp, String nonce) {
+
+		String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
+				+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
+				+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>";
+		return String.format(format, encrypt, signature, timestamp, nonce);
+
+	}
+}

+ 1 - 0
web/src/main/java/com/jpsoft/smart/config/WebMvcConfig.java

@@ -61,6 +61,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
 				.excludePathPatterns("/base/employeeInfo/mobile/**")
 //				.excludePathPatterns("/base/companyInfo/list")
 				.excludePathPatterns("/wechat/**")
+				.excludePathPatterns("/wxCp/**")
 				.excludePathPatterns("/rabbit/**")
 				.excludePathPatterns("/mobile/personInfoApi/findByNameAndPhone")
 				.excludePathPatterns("/mobile/personInfoApi/getVerifyCode")

+ 129 - 0
web/src/main/java/com/jpsoft/smart/config/WxCpConfiguration.java

@@ -0,0 +1,129 @@
+package com.jpsoft.smart.config;
+
+import com.google.common.collect.Maps;
+import com.jpsoft.smart.cphandler.*;
+import lombok.val;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import me.chanjar.weixin.cp.constant.WxCpConsts;
+import me.chanjar.weixin.cp.message.WxCpMessageRouter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author 墨鱼_mo
+ * @date 2020-4-2 9:29
+ */
+@Configuration
+@EnableConfigurationProperties(WxCpConfig.class)
+public class WxCpConfiguration {
+
+    private LogHandler logHandler;
+    private NullHandler nullHandler;
+    private LocationHandler locationHandler;
+    private MenuHandler menuHandler;
+    private MsgHandler msgHandler;
+    private UnsubscribeHandler unsubscribeHandler;
+    private SubscribeHandler subscribeHandler;
+
+    private WxCpConfig wxCpConfig;
+
+    private static Map<Integer, WxCpMessageRouter> routers = Maps.newHashMap();
+    private static Map<Integer, WxCpService> cpServices = Maps.newHashMap();
+
+    @Autowired
+    public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler,
+                             MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler,
+                             SubscribeHandler subscribeHandler, WxCpConfig wxCpConfig) {
+        this.logHandler = logHandler;
+        this.nullHandler = nullHandler;
+        this.locationHandler = locationHandler;
+        this.menuHandler = menuHandler;
+        this.msgHandler = msgHandler;
+        this.unsubscribeHandler = unsubscribeHandler;
+        this.subscribeHandler = subscribeHandler;
+        this.wxCpConfig = wxCpConfig;
+    }
+
+
+    public static Map<Integer, WxCpMessageRouter> getRouters() {
+        return routers;
+    }
+
+    public static WxCpService getCpService(Integer agentId) {
+        return cpServices.get(agentId);
+    }
+
+    @PostConstruct
+    public void initServices() {
+        cpServices = this.wxCpConfig.getAppConfigs().stream().map(a -> {
+            val configStorage = new WxCpDefaultConfigImpl();
+            configStorage.setCorpId(this.wxCpConfig.getCorpId());
+            configStorage.setAgentId(a.getAgentId());
+            configStorage.setCorpSecret(a.getSecret());
+            configStorage.setToken(a.getToken());
+            configStorage.setAesKey(a.getAesKey());
+            val service = new WxCpServiceImpl();
+            service.setWxCpConfigStorage(configStorage);
+            routers.put(a.getAgentId(), this.newRouter(service));
+            return service;
+        }).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a));
+    }
+
+    private WxCpMessageRouter newRouter(WxCpService wxCpService) {
+        final val newRouter = new WxCpMessageRouter(wxCpService);
+
+        // 记录所有事件的日志 (异步执行)
+        newRouter.rule().handler(this.logHandler).next();
+
+        // 自定义菜单事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end();
+
+        // 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end();
+
+        // 关注事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler)
+                .end();
+
+        // 取消关注事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.UNSUBSCRIBE)
+                .handler(this.unsubscribeHandler).end();
+
+        // 上报地理位置事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.LOCATION).handler(this.locationHandler)
+                .end();
+
+        // 接收地理位置消息
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
+                .handler(this.locationHandler).end();
+
+        // 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.SCAN).handler(this.nullHandler).end();
+
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end();
+
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end();
+
+        // 默认
+        newRouter.rule().async(false).handler(this.msgHandler).end();
+
+        return newRouter;
+    }
+
+}

+ 56 - 0
web/src/main/java/com/jpsoft/smart/modules/wechat/controller/WxCpController.java

@@ -0,0 +1,56 @@
+package com.jpsoft.smart.modules.wechat.controller;
+
+import com.jpsoft.smart.config.WxCpConfig;
+import com.jpsoft.smart.config.WxCpConfiguration;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author 墨鱼_mo
+ * @date 2020-4-1 10:56
+ */
+@Slf4j
+@RequestMapping("/wxCp/{agentId}")
+@RestController
+public class WxCpController {
+
+
+    @Autowired
+    private WxCpConfig wxCpConfig;
+
+    @ApiOperation(value = "验证服务器地址的有效性")
+    @GetMapping("/get")
+    @ResponseBody
+    public String index(@PathVariable Integer agentId,
+                        @RequestParam(name = "msg_signature", required = false) String signature,
+                        @RequestParam(name = "timestamp", required = false) String timestamp,
+                        @RequestParam(name = "nonce", required = false) String nonce,
+                        @RequestParam(name = "echostr", required = false) String echostr) {
+
+
+        log.info("\n接收到来自微信服务器的认证消息:signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]",
+                signature, timestamp, nonce, echostr);
+
+        if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
+            throw new IllegalArgumentException("请求参数非法,请核实!");
+        }
+
+        final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId);
+        if (wxCpService == null) {
+            throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId));
+        }
+
+        if (wxCpService.checkSignature(signature, timestamp, nonce, echostr)) {
+            return new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()).decrypt(echostr);
+        }
+
+        return "非法请求";
+
+
+    }
+}

+ 11 - 0
web/src/main/resources/application-production.yml

@@ -59,6 +59,17 @@ wx:
     refreshOAuth2TokenUrl: "https://api.weixin.qq.com/sns/oauth2/refresh_token"
     createQrCodeUrl: "https://api.weixin.qq.com/cgi-bin/qrcode/create"
     showQrCodeUrl: "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+  cp:
+    corpId: wwe5fb36b045f42c42
+    appConfigs:
+      - agentId: 1000001
+        secret: jWDwhE92FPB7it7ciO0PzJbu2c0mErMYUNtsiYR3sEo
+        token: M9Kq
+        aesKey: K9j9sJJ6QhNZeCprXoFCU5NYm7jOlyyIXfY0Dewbfnm
+      - agentId: 1000002
+        secret: 1111
+        token: 111
+        aesKey: 111
 
 mobile:
   unMeasureUrl: http://wisdomhousewechat.sudaonline.net/prevention/adundetect_wx_message.html

+ 14 - 1
web/src/main/resources/application-test.yml

@@ -33,4 +33,17 @@ wx:
     tokenUrl: "https://api.weixin.qq.com/cgi-bin/token"
     refreshOAuth2TokenUrl: "https://api.weixin.qq.com/sns/oauth2/refresh_token"
     createQrCodeUrl: "https://api.weixin.qq.com/cgi-bin/qrcode/create"
-    showQrCodeUrl: "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+    showQrCodeUrl: "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+  cp:
+    corpId: wwe5fb36b045f42c42
+    appConfigs:
+      - agentId: 1000001
+        secret: jWDwhE92FPB7it7ciO0PzJbu2c0mErMYUNtsiYR3sEo
+        token: M9Kq
+        aesKey: K9j9sJJ6QhNZeCprXoFCU5NYm7jOlyyIXfY0Dewbfnm
+      - agentId: 1000002
+        secret: 1111
+        token: 111
+        aesKey: 111
+
+