diff --git a/pom.xml b/pom.xml
index a0a19c9..55e9ded 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
1.1
2.8.2
3.17
+ 1.0
@@ -53,6 +54,8 @@
udf-starter-datasource-dynamic
udf-starter-dal-mybatis
udf-starter-cache-redis
+ udf-starter-weixin-mp
+ udf-starter-weixin-mp-api
@@ -193,6 +196,18 @@
udf-starter-cache-redis
${udf-starter.version}
+
+
+ org.itkk.udf
+ udf-starter-weixin-mp-api
+ ${udf-starter.version}
+
+
+
+ org.itkk.udf
+ udf-starter-weixin-mp
+ ${udf-starter.version}
+
diff --git a/udf-starter-core/src/main/java/org/itkk/udf/core/constant/UdfConstant.java b/udf-starter-core/src/main/java/org/itkk/udf/core/constant/UdfConstant.java
new file mode 100644
index 0000000..5467fff
--- /dev/null
+++ b/udf-starter-core/src/main/java/org/itkk/udf/core/constant/UdfConstant.java
@@ -0,0 +1,53 @@
+package org.itkk.udf.core.constant;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * UdfConstant
+ */
+@Slf4j
+public class UdfConstant {
+
+ /**
+ * UdfConstant
+ */
+ private UdfConstant() {
+
+ }
+
+ /**
+ * 读取返回的信息
+ *
+ * @param in 输入流
+ * @param characterSet 字符集
+ * @return 数据
+ * @throws IOException 异常
+ */
+ public static String getData(InputStream in, String characterSet) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader br = null;
+ String line;
+ try { //NOSONAR
+ br = new BufferedReader(new InputStreamReader(in, characterSet));
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ br.close();
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException e) {
+ log.warn("close时遇到错误", e);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/udf-starter-mail/src/main/java/org/itkk/udf/mail/service/MailService.java b/udf-starter-mail/src/main/java/org/itkk/udf/mail/service/MailService.java
index f19f59a..799717c 100644
--- a/udf-starter-mail/src/main/java/org/itkk/udf/mail/service/MailService.java
+++ b/udf-starter-mail/src/main/java/org/itkk/udf/mail/service/MailService.java
@@ -74,16 +74,16 @@ public class MailService {
//必要字段
helper.setFrom(IDN.toASCII(mailProperties.getFrom()));
helper.setSentDate(new Date());
- helper.setTo(Arrays.stream(mailInfo.getTo()).map(to -> IDN.toASCII(to)).toArray(String[]::new));
+ helper.setTo(Arrays.stream(mailInfo.getTo()).map(IDN::toASCII).toArray(String[]::new));
helper.setSubject(mailInfo.getSubject());
helper.setText(mailInfo.getText(), mailInfo.getIsHtmlText());
//秘密抄送
if (ArrayUtils.isNotEmpty(mailInfo.getBcc())) {
- helper.setBcc(Arrays.stream(mailInfo.getBcc()).map(to -> IDN.toASCII(to)).toArray(String[]::new));
+ helper.setBcc(Arrays.stream(mailInfo.getBcc()).map(IDN::toASCII).toArray(String[]::new));
}
//抄送
if (ArrayUtils.isNotEmpty(mailInfo.getCc())) {
- helper.setCc(Arrays.stream(mailInfo.getCc()).map(to -> IDN.toASCII(to)).toArray(String[]::new));
+ helper.setCc(Arrays.stream(mailInfo.getCc()).map(IDN::toASCII).toArray(String[]::new));
}
//优先级
if (mailInfo.getPriority() != null) {
diff --git a/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/ShaOne.java b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/ShaOne.java
new file mode 100644
index 0000000..08f193b
--- /dev/null
+++ b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/ShaOne.java
@@ -0,0 +1,69 @@
+/**
+ * ShaOne.java
+ * Created at 2016-04-23
+ * Created by Administrator
+ * Copyright (C) 2016 LLSFW, All rights reserved.
+ */
+package org.itkk.udf.weixin.mp.api;
+
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ *
+ * Title: SHA1算法
+ *
+ */
+public final class ShaOne {
+
+ /**
+ * ShaOne
+ */
+ private ShaOne() {
+
+ }
+
+ /**
+ * Takes the raw bytes from the digest and formats them correct.
+ *
+ * @param bytes the raw bytes from the digest.
+ * @return the formatted bytes.
+ */
+ private static String getFormattedText(byte[] bytes) {
+ final int num2 = 2;
+ final int num4 = 4;
+ final int num0x0f = 0x0f;
+ final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ int len = bytes.length;
+ StringBuilder buf = new StringBuilder(len * num2);
+ // 把密文转换成十六进制的字符串形式
+ for (byte aByte : bytes) {
+ buf.append(hexDigits[(aByte >> num4) & num0x0f]);
+ buf.append(hexDigits[aByte & num0x0f]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
+ * Description: 编码
+ *
+ *
+ * @param str 字符串
+ * @return 编码后的字符串
+ */
+ public static String encode(String str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
+ messageDigest.update(str.getBytes("UTF-8"));
+ return getFormattedText(messageDigest.digest());
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new RuntimeException(e); //NOSONAR
+ }
+ }
+}
diff --git a/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiConfig.java b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiConfig.java
new file mode 100644
index 0000000..4de3206
--- /dev/null
+++ b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiConfig.java
@@ -0,0 +1,63 @@
+package org.itkk.udf.weixin.mp.api;
+
+import org.springframework.amqp.core.DirectExchange;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * WeixinMpApiConfig
+ */
+@Configuration
+public class WeixinMpApiConfig {
+
+ /**
+ * EXCHANGE_WEIXIN_MP_MESSAGE
+ */
+ public static final String EXCHANGE_WEIXIN_MP_MESSAGE = "EXCHANGE_WEIXIN_MP_MESSAGE";
+
+ /**
+ * EXCHANGE_WEIXIN_MP_EVENT
+ */
+ public static final String EXCHANGE_WEIXIN_MP_EVENT = "EXCHANGE_WEIXIN_MP_EVENT";
+
+ /**
+ * exchangeWeixinMpMessage
+ *
+ * @return DirectExchange
+ */
+ @Bean
+ public DirectExchange exchangeWeixinMpMessage() {
+ return new DirectExchange(EXCHANGE_WEIXIN_MP_MESSAGE, true, false);
+ }
+
+ /**
+ * exchangeWeixinMpMessageDlx
+ *
+ * @return DirectExchange
+ */
+ @Bean
+ public DirectExchange exchangeWeixinMpMessageDlx() {
+ return new DirectExchange(EXCHANGE_WEIXIN_MP_MESSAGE + "_DLX", true, false);
+ }
+
+ /**
+ * exchangeWeixinMpEvent
+ *
+ * @return DirectExchange
+ */
+ @Bean
+ public DirectExchange exchangeWeixinMpEvent() {
+ return new DirectExchange(EXCHANGE_WEIXIN_MP_EVENT, true, false);
+ }
+
+ /**
+ * exchangeWeixinMpEventDlx
+ *
+ * @return DirectExchange
+ */
+ @Bean
+ public DirectExchange exchangeWeixinMpEventDlx() {
+ return new DirectExchange(EXCHANGE_WEIXIN_MP_EVENT + "_DLX", true, false);
+ }
+
+}
diff --git a/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiProperties.java b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiProperties.java
new file mode 100644
index 0000000..4112be6
--- /dev/null
+++ b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiProperties.java
@@ -0,0 +1,23 @@
+package org.itkk.udf.weixin.mp.api;
+
+import lombok.Data;
+import org.itkk.udf.weixin.mp.api.meta.WeixinMpApiAuthMeta;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * WeixinMpApiProperties
+ */
+@Component
+@ConfigurationProperties(prefix = "org.itkk.weixin.mp.api.properties")
+@Data
+public class WeixinMpApiProperties {
+
+ /**
+ * 微信MP认证信息
+ */
+ private Map auth;
+
+}
diff --git a/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/meta/WeixinMpApiAuthMeta.java b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/meta/WeixinMpApiAuthMeta.java
new file mode 100644
index 0000000..38057e5
--- /dev/null
+++ b/udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/meta/WeixinMpApiAuthMeta.java
@@ -0,0 +1,43 @@
+package org.itkk.udf.weixin.mp.api.meta;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * WeixinMpApiAuthMeta
+ */
+@Data
+public class WeixinMpApiAuthMeta implements Serializable {
+
+ /**
+ * 描述 : ID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 开发者ID
+ */
+ private String appId;
+
+ /**
+ * 开发者密码
+ */
+ private String appSecret;
+
+ /**
+ * 令牌
+ */
+ private String token;
+
+ /**
+ * 消息加解密密钥
+ */
+ private String encodingAesKey;
+
+ /**
+ * 是否安全模式(密文消息)
+ */
+ private Boolean safeModel = true;
+
+}
diff --git a/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpConsont.java b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpConsont.java
new file mode 100644
index 0000000..efe444b
--- /dev/null
+++ b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpConsont.java
@@ -0,0 +1,65 @@
+package org.itkk.udf.weixin.mp;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * WeixinMpConsont
+ */
+public class WeixinMpConsont {
+
+ /**
+ * WeixinMpConsont
+ */
+ private WeixinMpConsont() {
+
+ }
+
+ /**
+ * 描述 : 获得节点值
+ *
+ * @param root xml文档
+ * @param nodeName 节点名称
+ * @return 节点值
+ */
+ public static String getXmlNodeValue(Element root, String nodeName) {
+ NodeList toUserNameNodelist = root.getElementsByTagName(nodeName);
+ return toUserNameNodelist.item(0).getTextContent();
+ }
+
+ /**
+ * 描述 : 解析消息
+ *
+ * @param inputMsg 消息
+ * @return 消息
+ * @throws ParserConfigurationException 异常
+ * @throws SAXException 异常
+ * @throws IOException 异常
+ */
+ public static Element formatInputMessage(String inputMsg) throws ParserConfigurationException, SAXException, IOException {
+ //解析消息
+ StringReader sr = null;
+ try { //NOSONAR
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ sr = new StringReader(inputMsg);
+ InputSource is = new InputSource(sr);
+ Document document = db.parse(is);
+ return document.getDocumentElement();
+ } finally {
+ if (sr != null) {
+ sr.close();
+ }
+ }
+ }
+
+}
diff --git a/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpException.java b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpException.java
new file mode 100644
index 0000000..6b13612
--- /dev/null
+++ b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpException.java
@@ -0,0 +1,66 @@
+/**
+ * TokenInvalidException.java
+ * Created at 2016-03-01
+ * Created by wangkang
+ * Copyright (C) 2016 itkk.org, All rights reserved.
+ */
+package org.itkk.udf.weixin.mp;
+
+/**
+ *
+ * ClassName: KqPosException
+ *
+ *
+ * Description: KqPosException
+ *
+ *
+ * Author: wangkang
+ *
+ *
+ * Date: 2016年3月23日
+ *
+ */
+public class WeixinMpException extends RuntimeException {
+
+ /**
+ *
+ * Field serialVersionUID: 序列号
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ *
+ * Description: 构造函数
+ *
+ *
+ * @param message 异常信息
+ */
+ public WeixinMpException(String message) {
+ super(message);
+ }
+
+ /**
+ *
+ * Description: 构造函数
+ *
+ *
+ * @param cause 堆栈
+ */
+ public WeixinMpException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ *
+ * Description: 构造函数
+ *
+ *
+ * @param message 异常信息
+ * @param cause 堆栈
+ */
+ public WeixinMpException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/service/WeixinMpCallbackService.java b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/service/WeixinMpCallbackService.java
new file mode 100644
index 0000000..b780c5c
--- /dev/null
+++ b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/service/WeixinMpCallbackService.java
@@ -0,0 +1,125 @@
+package org.itkk.udf.weixin.mp.service;
+
+import com.qq.weixin.mp.aes.WXBizMsgCrypt;
+import lombok.extern.slf4j.Slf4j;
+import org.itkk.udf.amqp.rabbitmq.Rabbitmq;
+import org.itkk.udf.amqp.rabbitmq.RabbitmqMessage;
+import org.itkk.udf.weixin.mp.WeixinMpConsont;
+import org.itkk.udf.weixin.mp.WeixinMpException;
+import org.itkk.udf.weixin.mp.api.ShaOne;
+import org.itkk.udf.weixin.mp.api.WeixinMpApiConfig;
+import org.itkk.udf.weixin.mp.api.WeixinMpApiProperties;
+import org.itkk.udf.weixin.mp.api.meta.WeixinMpApiAuthMeta;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.w3c.dom.Element;
+
+import java.util.*;
+
+/**
+ * WeixinMpCallbackService
+ */
+@Service
+@Slf4j
+public class WeixinMpCallbackService {
+
+ /**
+ * weixinMpApiProperties
+ */
+ @Autowired
+ private WeixinMpApiProperties weixinMpApiProperties;
+
+ /**
+ * 描述 : rabbitmq
+ */
+ @Autowired
+ private Rabbitmq rabbitmq;
+
+ /**
+ * check
+ *
+ * @param businessCode businessCode
+ * @param signature signature
+ * @param timestamp timestamp
+ * @param nonce nonce
+ * @throws Exception Exception
+ */
+ public void check(String businessCode, String signature, String timestamp, String nonce) {
+ final int num2 = 2;
+ WeixinMpApiAuthMeta weixinMpApiAuthMeta = weixinMpApiProperties.getAuth().get(businessCode);
+ List params = new ArrayList<>();
+ params.add(weixinMpApiAuthMeta.getToken());
+ params.add(timestamp);
+ params.add(nonce);
+ //1. 将token、timestamp、nonce三个参数进行字典序排序
+ Collections.sort(params, Comparator.naturalOrder());
+ //2. 将三个参数字符串拼接成一个字符串进行sha1加密
+ String temp = ShaOne.encode(params.get(0) + params.get(1) + params.get(num2));
+ //3. 开发者获得加密后的字符串可与signature对比,如果不一致,则抛出异常
+ if (!temp.equals(signature)) {
+ throw new WeixinMpException("checkSignature Fail");
+ }
+ }
+
+ /**
+ * callback
+ *
+ * @param businessCode businessCode
+ * @param signature signature
+ * @param timestamp timestamp
+ * @param nonce nonce
+ * @param inputMessage inputMessage
+ * @return String
+ * @throws Exception Exception
+ */
+ public String callback(String businessCode, String signature, String timestamp, String nonce, String inputMessage) {
+ //常量
+ final String defReturnMessage = "success";
+ final String eventMessageType = "event";
+ //开始处理
+ try {
+ //获得身份认证元数据
+ WeixinMpApiAuthMeta weixinMpApiAuthMeta = weixinMpApiProperties.getAuth().get(businessCode);
+ //验证签名
+ this.check(weixinMpApiAuthMeta.getToken(), signature, timestamp, nonce);
+ //实例化加密解密工具类
+ WXBizMsgCrypt pc = new WXBizMsgCrypt(weixinMpApiAuthMeta.getToken(), weixinMpApiAuthMeta.getEncodingAesKey(), weixinMpApiAuthMeta.getAppId());
+ //获得输入参数
+ String inputXmlMessage = inputMessage;
+ //判断是否需要解密
+ if (weixinMpApiAuthMeta.getSafeModel()) {
+ //解析密文输入参数
+ Element root = WeixinMpConsont.formatInputMessage(inputXmlMessage);
+ String msgSignature = WeixinMpConsont.getXmlNodeValue(root, "MsgSignature");
+ inputXmlMessage = pc.decryptMsg(msgSignature, timestamp, nonce, inputXmlMessage);
+ }
+ //解析明文输入参数
+ Element root = WeixinMpConsont.formatInputMessage(inputXmlMessage);
+ //获得消息类型
+ String msgType = WeixinMpConsont.getXmlNodeValue(root, "MsgType");
+ //定义MQ的交换机名称和路由键名称(默认是消息类型)
+ String exchange = WeixinMpApiConfig.EXCHANGE_WEIXIN_MP_MESSAGE;
+ String routingKey = msgType;
+ //判断处理
+ if (eventMessageType.equals(msgType)) { //事件
+ //更改为事件类型
+ exchange = WeixinMpApiConfig.EXCHANGE_WEIXIN_MP_EVENT;
+ routingKey = WeixinMpConsont.getXmlNodeValue(root, "Event");
+ }
+ //构造消息头
+ Map header = new HashMap<>();
+ header.put("businessCode", businessCode);
+ //构造消息
+ RabbitmqMessage message = new RabbitmqMessage<>(header, inputXmlMessage);
+ //发送消息
+ rabbitmq.convertAndSend(exchange, routingKey, message);
+ //日志输出
+ log.info("send weixin message ---> businessCode : {} , exchange : {} , routingKey : {} , body : {} ", businessCode, exchange, routingKey, inputXmlMessage);
+ //返回
+ return defReturnMessage;
+ } catch (Exception e) {
+ throw new WeixinMpException(e);
+ }
+ }
+
+}
diff --git a/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/web/IWeixinMpCallbackController.java b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/web/IWeixinMpCallbackController.java
new file mode 100644
index 0000000..b8a2157
--- /dev/null
+++ b/udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/web/IWeixinMpCallbackController.java
@@ -0,0 +1,38 @@
+package org.itkk.udf.weixin.mp.web;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * IWeixinMpCallbackController
+ */
+@ApiIgnore
+public interface IWeixinMpCallbackController {
+
+ /**
+ * 微信验证(GET)
+ *
+ * @param businessCode 业务代码
+ * @param signature 微信加密签名
+ * @param timestamp 时间戳
+ * @param nonce 随机数
+ * @param echostr 随机数
+ */
+ @GetMapping
+ void check(@PathVariable String businessCode, String signature, String timestamp, String nonce, String echostr);
+
+ /**
+ * 微信消息推送(POST)
+ *
+ * @param businessCode 业务代码
+ * @param signature 微信加密签名
+ * @param timestamp 时间戳
+ * @param nonce 随机数
+ * @param echostr 随机数
+ */
+ @PostMapping
+ void callback(@PathVariable String businessCode, String signature, String timestamp, String nonce, String echostr);
+
+}