mirror of
https://gitee.com/wangkang/udf.git
synced 2026-11-01 09:53:41 +00:00
添加微信MP的集成
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -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;
|
||||
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -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);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user