From aade635a303011737693cbb16baf676f46d82e88 Mon Sep 17 00:00:00 2001 From: "udf_admin@163.com" Date: Sat, 27 Jan 2018 23:18:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1MP=E7=9A=84?= =?UTF-8?q?=E9=9B=86=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 15 +++ .../itkk/udf/core/constant/UdfConstant.java | 53 ++++++++ .../itkk/udf/mail/service/MailService.java | 6 +- .../org/itkk/udf/weixin/mp/api/ShaOne.java | 69 ++++++++++ .../udf/weixin/mp/api/WeixinMpApiConfig.java | 63 +++++++++ .../weixin/mp/api/WeixinMpApiProperties.java | 23 ++++ .../mp/api/meta/WeixinMpApiAuthMeta.java | 43 ++++++ .../itkk/udf/weixin/mp/WeixinMpConsont.java | 65 +++++++++ .../itkk/udf/weixin/mp/WeixinMpException.java | 66 +++++++++ .../mp/service/WeixinMpCallbackService.java | 125 ++++++++++++++++++ .../mp/web/IWeixinMpCallbackController.java | 38 ++++++ 11 files changed, 563 insertions(+), 3 deletions(-) create mode 100644 udf-starter-core/src/main/java/org/itkk/udf/core/constant/UdfConstant.java create mode 100644 udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/ShaOne.java create mode 100644 udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiConfig.java create mode 100644 udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/WeixinMpApiProperties.java create mode 100644 udf-starter-weixin-mp-api/src/main/java/org/itkk/udf/weixin/mp/api/meta/WeixinMpApiAuthMeta.java create mode 100644 udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpConsont.java create mode 100644 udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/WeixinMpException.java create mode 100644 udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/service/WeixinMpCallbackService.java create mode 100644 udf-starter-weixin-mp/src/main/java/org/itkk/udf/weixin/mp/web/IWeixinMpCallbackController.java 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); + +}