添加微信MP的集成

This commit is contained in:
udf_admin@163.com
2018-01-27 23:18:16 +08:00
parent 9396c21832
commit aade635a30
11 changed files with 563 additions and 3 deletions
+15
View File
@@ -30,6 +30,7 @@
<jsqlparser.version>1.1</jsqlparser.version>
<aliyun-sdk-oss.version>2.8.2</aliyun-sdk-oss.version>
<poi.version>3.17</poi.version>
<aes.version>1.0</aes.version>
</properties>
<!--子模块 -->
<modules>
@@ -53,6 +54,8 @@
<module>udf-starter-datasource-dynamic</module>
<module>udf-starter-dal-mybatis</module>
<module>udf-starter-cache-redis</module>
<module>udf-starter-weixin-mp</module>
<module>udf-starter-weixin-mp-api</module>
</modules>
<!-- 依赖管理 -->
<dependencyManagement>
@@ -193,6 +196,18 @@
<artifactId>udf-starter-cache-redis</artifactId>
<version>${udf-starter.version}</version>
</dependency>
<!-- udf-starter-weixin-mp-api -->
<dependency>
<groupId>org.itkk.udf</groupId>
<artifactId>udf-starter-weixin-mp-api</artifactId>
<version>${udf-starter.version}</version>
</dependency>
<!-- udf-starter-weixin-mp -->
<dependency>
<groupId>org.itkk.udf</groupId>
<artifactId>udf-starter-weixin-mp</artifactId>
<version>${udf-starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 构建 -->
@@ -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();
}
}
@@ -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) {
@@ -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;
/**
* <p>
* Title: SHA1算法
* </p>
*/
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();
}
/**
* <p>
* Description: 编码
* </p>
*
* @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
}
}
}
@@ -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);
}
}
@@ -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<String, WeixinMpApiAuthMeta> auth;
}
@@ -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;
}
@@ -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();
}
}
}
}
@@ -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;
/**
* <p>
* ClassName: KqPosException
* </p>
* <p>
* Description: KqPosException
* </p>
* <p>
* Author: wangkang
* </p>
* <p>
* Date: 2016年3月23日
* </p>
*/
public class WeixinMpException extends RuntimeException {
/**
* <p>
* Field serialVersionUID: 序列号
* </p>
*/
private static final long serialVersionUID = 1L;
/**
* <p>
* Description: 构造函数
* </p>
*
* @param message 异常信息
*/
public WeixinMpException(String message) {
super(message);
}
/**
* <p>
* Description: 构造函数
* </p>
*
* @param cause 堆栈
*/
public WeixinMpException(Throwable cause) {
super(cause);
}
/**
* <p>
* Description: 构造函数
* </p>
*
* @param message 异常信息
* @param cause 堆栈
*/
public WeixinMpException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -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<String> 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<String, String> header = new HashMap<>();
header.put("businessCode", businessCode);
//构造消息
RabbitmqMessage<String> 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);
}
}
}
@@ -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);
}