新增企业付款查询接口 & 对多个公众号的接入支持

This commit is contained in:
jinyu
2015-06-23 22:20:24 +08:00
parent 37503acac6
commit bb79042d4e
27 changed files with 667 additions and 136 deletions
@@ -3,6 +3,7 @@ package com.foxinmy.weixin4j.request;
import java.io.Serializable;
import com.foxinmy.weixin4j.type.EncryptType;
import com.foxinmy.weixin4j.util.AesToken;
/**
* 微信请求
@@ -60,10 +61,15 @@ public class WeixinRequest implements Serializable, Cloneable {
* xml消息密文主体(AES时存在)
*/
private String encryptContent;
/**
* aes & token
*/
private AesToken aesToken;
public WeixinRequest(String method, EncryptType encryptType,
String echoStr, String timeStamp, String nonce, String signature,
String msgSignature, String originalContent, String encryptContent) {
String msgSignature, String originalContent, String encryptContent,
AesToken aesToken) {
this.method = method;
this.encryptType = encryptType;
this.echoStr = echoStr;
@@ -73,6 +79,7 @@ public class WeixinRequest implements Serializable, Cloneable {
this.msgSignature = msgSignature;
this.originalContent = originalContent;
this.encryptContent = encryptContent;
this.aesToken = aesToken;
}
public String getMethod() {
@@ -111,12 +118,17 @@ public class WeixinRequest implements Serializable, Cloneable {
return encryptContent;
}
public AesToken getAesToken() {
return aesToken;
}
@Override
public String toString() {
return "WeixinRequest [encryptContent=" + encryptContent
+ ", encryptType=" + encryptType + ", echoStr=" + echoStr
+ ", timeStamp=" + timeStamp + ", nonce=" + nonce
+ ", signature=" + signature + ", originalContent="
+ originalContent + ", method=" + method + "]";
+ originalContent + ", method=" + method + ", aesToken="
+ aesToken + "]";
}
}
@@ -0,0 +1,19 @@
package com.foxinmy.weixin4j.response;
/**
* 单一的字符串回复,如回复SUCCESS
*
* @className SingleResponse
* @author jy
* @date 2015年6月23日
* @since JDK 1.7
* @see
*/
public interface SingleResponse {
/**
* 回复内容
*
* @return
*/
public String toContent();
}
@@ -1,5 +1,6 @@
package com.foxinmy.weixin4j.response;
/**
* 微信被动消息回复
*
@@ -20,18 +21,11 @@ package com.foxinmy.weixin4j.response;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E8%A2%AB%E5%8A%A8%E5%93%8D%E5%BA%94%E6%B6%88%E6%81%AF">企业号的被动响应消息</a>
*/
public interface WeixinResponse {
public interface WeixinResponse extends SingleResponse {
/**
* 消息类型
* 回复的消息类型
*
* @return
*/
public String getMsgType();
/**
* 消息内容
*
* @return
*/
public String toContent();
}
@@ -0,0 +1,62 @@
package com.foxinmy.weixin4j.socket;
import java.io.Serializable;
import com.foxinmy.weixin4j.type.EncryptType;
import com.foxinmy.weixin4j.util.AesToken;
/**
* 消息传递
*
* @className MessageTransfer
* @author jy
* @date 2015年6月23日
* @since JDK 1.7
* @see
*/
public class MessageTransfer implements Serializable {
private static final long serialVersionUID = 7779948135156353261L;
/**
* aes & token
*/
private AesToken aesToken;
/**
* 加密类型
*/
private EncryptType encryptType;
/**
* 消息接收方
*/
private String toUserName;
/**
* 消息发送方
*/
private String fromUserName;
public MessageTransfer(AesToken aesToken, EncryptType encryptType,
String toUserName, String fromUserName) {
this.aesToken = aesToken;
this.encryptType = encryptType;
this.toUserName = toUserName;
this.fromUserName = fromUserName;
}
public AesToken getAesToken() {
return aesToken;
}
public EncryptType getEncryptType() {
return encryptType;
}
public String getToUserName() {
return toUserName;
}
public String getFromUserName() {
return fromUserName;
}
}
@@ -35,10 +35,10 @@ public class WeixinMessageDecoder extends
private final InternalLogger logger = InternalLoggerFactory
.getInstance(getClass());
private AesToken aesToken;
private Map<String, AesToken> aesTokenMap;
public WeixinMessageDecoder(AesToken aesToken) {
this.aesToken = aesToken;
public WeixinMessageDecoder(Map<String, AesToken> aesTokenMap) {
this.aesTokenMap = aesTokenMap;
}
@Override
@@ -47,8 +47,9 @@ public class WeixinMessageDecoder extends
String content = req.content().toString(Consts.UTF_8);
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri(),
true);
String methodName = req.getMethod().name();
logger.info("decode request:{} use {} method invoking", req.getUri(),
req.getMethod().name());
methodName);
Map<String, List<String>> parameters = queryDecoder.parameters();
EncryptType encryptType = parameters.containsKey("encrypt_type") ? EncryptType
.valueOf(parameters.get("encrypt_type").get(0).toUpperCase())
@@ -63,22 +64,23 @@ public class WeixinMessageDecoder extends
.get("signature").get(0) : "";
String msgSignature = parameters.containsKey("msg_signature") ? parameters
.get("msg_signature").get(0) : "";
String weixinId = parameters.containsKey("weixin_id") ? parameters.get(
"weixin_id").get(0) : null;
AesToken aesToken = aesTokenMap.get(weixinId);
String originalContent = content;
String encryptContent = null;
if (!content.isEmpty()) {
if (encryptType == EncryptType.AES) {
if (StringUtil.isBlank(aesToken.getAesKey())
|| StringUtil.isBlank(aesToken.getAppid())) {
throw new WeixinException(
"AESEncodingKey or AppId not be null in AES mode");
}
encryptContent = EncryptMessageHandler.parser(content);
originalContent = MessageUtil.aesDecrypt(aesToken.getAppid(),
aesToken.getAesKey(), encryptContent);
if (!content.isEmpty() && encryptType == EncryptType.AES) {
if (StringUtil.isBlank(aesToken.getAesKey())
|| StringUtil.isBlank(aesToken.getWeixinId())) {
throw new WeixinException(
"AESEncodingKey or WeixinId not be null in AES mode");
}
encryptContent = EncryptMessageHandler.parser(content);
originalContent = MessageUtil.aesDecrypt(aesToken.getWeixinId(),
aesToken.getAesKey(), encryptContent);
}
out.add(new WeixinRequest(req.getMethod().name(), encryptType, echoStr,
timeStamp, nonce, signature, msgSignature, originalContent,
encryptContent));
out.add(new WeixinRequest(methodName, encryptType, echoStr, timeStamp,
nonce, signature, msgSignature, originalContent,
encryptContent, aesToken));
}
}
@@ -18,7 +18,6 @@ import com.foxinmy.weixin4j.util.AesToken;
import com.foxinmy.weixin4j.util.Consts;
import com.foxinmy.weixin4j.util.HttpUtil;
import com.foxinmy.weixin4j.util.MessageUtil;
import com.foxinmy.weixin4j.util.StringUtil;
import com.foxinmy.weixin4j.xml.CruxMessageHandler;
/**
@@ -34,12 +33,11 @@ public class WeixinRequestHandler extends
SimpleChannelInboundHandler<WeixinRequest> {
private final InternalLogger logger = InternalLoggerFactory
.getInstance(getClass());
private final AesToken aesToken;
private final WeixinMessageDispatcher messageDispatcher;
public WeixinRequestHandler(AesToken aesToken,
WeixinMessageDispatcher messageDispatcher) throws WeixinException {
this.aesToken = aesToken;
public WeixinRequestHandler(WeixinMessageDispatcher messageDispatcher)
throws WeixinException {
this.messageDispatcher = messageDispatcher;
}
@@ -56,6 +54,7 @@ public class WeixinRequestHandler extends
@Override
protected void channelRead0(ChannelHandlerContext ctx, WeixinRequest request)
throws WeixinException {
final AesToken aesToken = request.getAesToken();
if (request.getMethod().equals(HttpMethod.GET.name())) {
if (MessageUtil.signature(aesToken.getToken(),
request.getTimeStamp(), request.getNonce()).equals(
@@ -98,14 +97,10 @@ public class WeixinRequestHandler extends
}
CruxMessageHandler cruxMessage = CruxMessageHandler.parser(request
.getOriginalContent());
ctx.channel().attr(Consts.ENCRYPTTYPE_KEY)
.set(request.getEncryptType());
ctx.channel().attr(Consts.USEROPENID_KEY)
.set(cruxMessage.getFromUserName());
if (StringUtil.isBlank(aesToken.getAppid())) {
ctx.channel().attr(Consts.ACCOUNTOPENID_KEY)
.set(cruxMessage.getToUserName());
}
MessageTransfer messageTransfer = new MessageTransfer(aesToken,
request.getEncryptType(), cruxMessage.getToUserName(),
cruxMessage.getFromUserName());
ctx.channel().attr(Consts.MESSAGE_TRANSFER_KEY).set(messageTransfer);
messageDispatcher.doDispatch(ctx, request, cruxMessage);
}
}
@@ -36,22 +36,16 @@ public class WeixinResponseEncoder extends
private final InternalLogger logger = InternalLoggerFactory
.getInstance(getClass());
private final AesToken aesToken;
public WeixinResponseEncoder(AesToken aesToken) {
this.aesToken = aesToken;
}
@Override
protected void encode(ChannelHandlerContext ctx, WeixinResponse response,
List<Object> out) throws WeixinException {
EncryptType encryptType = ctx.channel().attr(Consts.ENCRYPTTYPE_KEY)
.get();
String userOpenId = ctx.channel().attr(Consts.USEROPENID_KEY).get();
String accountOpenId = ctx.channel().attr(Consts.ACCOUNTOPENID_KEY)
.get();
if (StringUtil.isBlank(accountOpenId)) {
accountOpenId = aesToken.getAppid();
MessageTransfer messageTransfer = ctx.channel()
.attr(Consts.MESSAGE_TRANSFER_KEY).get();
AesToken aesToken = messageTransfer.getAesToken();
EncryptType encryptType = messageTransfer.getEncryptType();
String weixinId = aesToken.getWeixinId();
if (StringUtil.isBlank(weixinId)) {
weixinId = messageTransfer.getToUserName();
}
StringBuilder content = new StringBuilder();
if (response instanceof BlankResponse) {
@@ -59,10 +53,10 @@ public class WeixinResponseEncoder extends
} else {
content.append("<xml>");
content.append(String.format(
"<ToUserName><![CDATA[%s]]></ToUserName>", userOpenId));
"<ToUserName><![CDATA[%s]]></ToUserName>",
messageTransfer.getFromUserName()));
content.append(String.format(
"<FromUserName><![CDATA[%s]]></FromUserName>",
accountOpenId));
"<FromUserName><![CDATA[%s]]></FromUserName>", weixinId));
content.append(String.format(
"<CreateTime><![CDATA[%d]]></CreateTime>",
System.currentTimeMillis() / 1000l));
@@ -74,7 +68,7 @@ public class WeixinResponseEncoder extends
String nonce = RandomUtil.generateString(32);
String timestamp = String
.valueOf(System.currentTimeMillis() / 1000l);
String encrtypt = MessageUtil.aesEncrypt(accountOpenId,
String encrtypt = MessageUtil.aesEncrypt(weixinId,
aesToken.getAesKey(), content.toString());
String msgSignature = MessageUtil.signature(
aesToken.getToken(), nonce, timestamp, encrtypt);
@@ -6,6 +6,8 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import java.util.Map;
import com.foxinmy.weixin4j.dispatcher.WeixinMessageDispatcher;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.util.AesToken;
@@ -21,15 +23,15 @@ import com.foxinmy.weixin4j.util.AesToken;
*/
public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
private final AesToken aesToken;
private final Map<String, AesToken> aesTokenMap;
private final WeixinMessageDispatcher messageDispatcher;
public WeixinServerInitializer(AesToken aesToken,
public WeixinServerInitializer(Map<String, AesToken> aesTokenMap,
WeixinMessageDispatcher messageDispatcher) throws WeixinException {
if (aesToken == null) {
if (aesTokenMap.isEmpty()) {
throw new WeixinException("AesToken not be null.");
}
this.aesToken = aesToken;
this.aesTokenMap = aesTokenMap;
this.messageDispatcher = messageDispatcher;
}
@@ -38,8 +40,8 @@ public class WeixinServerInitializer extends ChannelInitializer<SocketChannel> {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new WeixinMessageDecoder(aesToken));
pipeline.addLast(new WeixinResponseEncoder(aesToken));
pipeline.addLast(new WeixinRequestHandler(aesToken, messageDispatcher));
pipeline.addLast(new WeixinMessageDecoder(aesTokenMap));
pipeline.addLast(new WeixinResponseEncoder());
pipeline.addLast(new WeixinRequestHandler(messageDispatcher));
}
}
@@ -11,8 +11,10 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.foxinmy.weixin4j.dispatcher.BeanFactory;
import com.foxinmy.weixin4j.dispatcher.DefaultMessageMatcher;
@@ -54,7 +56,7 @@ public final class WeixinServerBootstrap {
/**
* 服务启动的默认端口
*/
public final static int DEFAULT_SERVERPORT = 30000;
public final static int DEFAULT_SERVERPORT = 80;
/**
* 消息分发器
*/
@@ -73,16 +75,19 @@ public final class WeixinServerBootstrap {
* aes and token
*
*/
private final AesToken aesToken;
private final Map<String, AesToken> aesTokenMap;
/**
* 明文模式
*
* * @param token 开发者token
* @param openid
* 微信号(原始ID)
* @param token
* 开发者token
*
*/
public WeixinServerBootstrap(String token) {
this(new AesToken(token));
public WeixinServerBootstrap(String openid, String token) {
this(openid, token, null);
}
/**
@@ -105,12 +110,32 @@ public final class WeixinServerBootstrap {
public WeixinServerBootstrap(AesToken aesToken,
WeixinMessageMatcher messageMatcher) {
this.aesToken = aesToken;
this.aesTokenMap = new HashMap<String, AesToken>();
this.aesTokenMap.put(aesToken.getWeixinId(), aesToken);
this.aesTokenMap.put(null, aesToken);
this.messageHandlerList = new LinkedList<WeixinMessageHandler>();
this.messageInterceptorList = new LinkedList<WeixinMessageInterceptor>();
this.messageDispatcher = new WeixinMessageDispatcher(messageMatcher);
}
/**
* 多个公众号的支持
* <p>
* <font color="red">请注意:需在服务接收事件的URL中附加一个名为wexin_id的参数,其值视加密模式而定,
* 如为明文模式weixin_id则填写公众号的微信号(即原始ID),如为AES加密模式weixin_id则填写公众号的应用ID(即appid)
* </font>
* <p>
*
* @param aesTokens
* @return
*/
public WeixinServerBootstrap multAesToken(AesToken... aesTokens) {
for (AesToken aesToken : aesTokens) {
this.aesTokenMap.put(aesToken.getWeixinId(), aesToken);
}
return this;
}
/**
* 默认端口启动服务
*
@@ -144,7 +169,7 @@ public final class WeixinServerBootstrap {
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
.childHandler(
new WeixinServerInitializer(aesToken,
new WeixinServerInitializer(aesTokenMap,
messageDispatcher));
Channel ch = b.bind(serverPort).sync().channel();
logger.info("weixin4j server startup OK:{}", serverPort);
@@ -0,0 +1,26 @@
package com.foxinmy.weixin4j.suite;
/**
* 应用套件回调事件
*
* @className SuiteEventType
* @author jy
* @date 2015年6月21日
* @since JDK 1.7
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE">第三方回调协议</a>
*/
public enum SuiteEventType {
/**
* 推送ticket
*/
suite_ticket,
/**
* 变更授权
*/
change_auth,
/**
* 取消授权
*/
cancel_auth;
}
@@ -0,0 +1,23 @@
package com.foxinmy.weixin4j.suite;
import com.foxinmy.weixin4j.response.SingleResponse;
/**
* 处理第三方应用套件请求
*
* @className SuiteMessageHandler
* @author jy
* @date 2015年6月23日
* @since JDK 1.7
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AC%AC%E4%B8%89%E6%96%B9%E5%9B%9E%E8%B0%83%E5%8D%8F%E8%AE%AE">套件回调协议</a>
*/
public interface SuiteMessageHandler {
/**
* 处理套件消息
*
* @param suiteMessage
* @return
*/
public SingleResponse handle(WeixinSuiteMessage suiteMessage);
}
@@ -0,0 +1,76 @@
package com.foxinmy.weixin4j.suite;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* 套件消息
*
* @className WeixinSuiteMessage
* @author jy
* @date 2015年6月23日
* @since JDK 1.7
* @see
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class WeixinSuiteMessage implements Serializable {
private static final long serialVersionUID = 6457919241019021514L;
/**
* 应用套件的SuiteId
*/
@XmlElement(name = "SuiteId")
private String suiteId;
/**
* 事件类型
*/
@XmlElement(name = "InfoType")
private SuiteEventType eventType;
/**
* 时间戳
*/
@XmlElement(name = "TimeStamp")
private long timeStamp;
/**
* Ticket内容
*/
@XmlElement(name = "SuiteTicket")
private String SuiteTicket;
/**
* 授权方企业号的corpid
*/
@XmlElement(name = "AuthCorpId")
private String authCorpId;
public String getSuiteId() {
return suiteId;
}
public SuiteEventType getEventType() {
return eventType;
}
public long getTimeStamp() {
return timeStamp;
}
public String getSuiteTicket() {
return SuiteTicket;
}
public String getAuthCorpId() {
return authCorpId;
}
@Override
public String toString() {
return "WeixinSuiteMessage [suiteId=" + suiteId + ", eventType="
+ eventType + ", timeStamp=" + timeStamp + ", SuiteTicket="
+ SuiteTicket + ", authCorpId=" + authCorpId + "]";
}
}
@@ -16,9 +16,9 @@ public class AesToken implements Serializable {
private static final long serialVersionUID = -6001008896414323534L;
/**
* 账号ID
* 账号ID(原始ID或者appid)
*/
private String appid;
private String weixinId;
/**
* 开发者的token
*/
@@ -28,18 +28,36 @@ public class AesToken implements Serializable {
*/
private String aesKey;
public AesToken(String token) {
this.token = token;
/**
* 一般为明文模式
*
* @param openid
* 微信号(原始ID)
* @param token
* 开发者的Token
*/
public AesToken(String openid, String token) {
this(openid, token, null);
}
/**
* 一般为AES加密模式
*
* @param appid
* 应用ID
* @param token
* 开发者Token
* @param aesKey
* 解密的EncodingAESKey
*/
public AesToken(String appid, String token, String aesKey) {
this.appid = appid;
this.weixinId = appid;
this.token = token;
this.aesKey = aesKey;
}
public String getAppid() {
return appid;
public String getWeixinId() {
return weixinId;
}
public String getToken() {
@@ -4,7 +4,7 @@ import io.netty.util.AttributeKey;
import java.nio.charset.Charset;
import com.foxinmy.weixin4j.type.EncryptType;
import com.foxinmy.weixin4j.socket.MessageTransfer;
/**
* 常量类
@@ -35,10 +35,6 @@ public final class Consts {
public static final String CONTENTTYPE$APPLICATION_XML = "application/xml";
public static final String CONTENTTYPE$TEXT_PLAIN = "text/plain";
public static final AttributeKey<EncryptType> ENCRYPTTYPE_KEY = AttributeKey
.valueOf("ENCRYPTTYPE");
public static final AttributeKey<String> ACCOUNTOPENID_KEY = AttributeKey
.valueOf("ACCOUNTOPENID");
public static final AttributeKey<String> USEROPENID_KEY = AttributeKey
.valueOf("USEROPENID");
public static final AttributeKey<MessageTransfer> MESSAGE_TRANSFER_KEY = AttributeKey
.valueOf("$_MESSAGETRANSFER");
}
@@ -23,11 +23,13 @@ import com.foxinmy.weixin4j.util.Consts;
*/
public class EncryptMessageHandler extends DefaultHandler {
private String toUserName;
private String encryptContent;
private String content;
@Override
public void startDocument() throws SAXException {
toUserName = null;
encryptContent = null;
}
@@ -42,6 +44,8 @@ public class EncryptMessageHandler extends DefaultHandler {
throws SAXException {
if (localName.equalsIgnoreCase("encrypt")) {
encryptContent = content;
} else if (localName.equalsIgnoreCase("tousername")) {
toUserName = content;
}
}
@@ -51,6 +55,10 @@ public class EncryptMessageHandler extends DefaultHandler {
this.content = new String(ch, start, length);
}
public String getToUserName() {
return toUserName;
}
public String getEncryptContent() {
return encryptContent;
}