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

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

View File

@ -341,4 +341,10 @@
* 2015-06-22
+ **weixin4j-qy**: 新增企业号[第三方应用代理](weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/WeixinSuiteProxy.java)。
+ **weixin4j-qy**: 新增企业号[第三方应用代理](weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/WeixinSuiteProxy.java)。
* 2015-06-23
+ **weixin4j-mp**: 新增企业付款查询接口
+ **weixin4j-server**: 对多个公众号的接入支持

11
pom.xml
View File

@ -110,17 +110,6 @@
</plugins>
<pluginManagement>
<plugins>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId>
<version>2.3</version> <executions> <execution> <id>standard</id> <phase>package</phase>
<goals> <goal>shade</goal> </goals> <configuration> <createSourcesJar>false</createSourcesJar>
<artifactSet> <includes> <include>com.foxinmy:weixin4j-base</include> </includes>
</artifactSet> <createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </execution> <execution> <id>full</id> <phase>package</phase>
<goals> <goal>shade</goal> </goals> <configuration> <createSourcesJar>false</createSourcesJar>
<artifactSet> <includes> <include>com.foxinmy:weixin4j-base</include> <include>com.alibaba:fastjson</include>
</includes> </artifactSet> <finalName>${project.artifactId}-${project.version}-full</finalName>
<createDependencyReducedPom>false</createDependencyReducedPom> </configuration>
</execution> </executions> </plugin> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>

View File

@ -112,4 +112,8 @@
* 2015-06-04
+ 新增查询红包接口
+ 新增查询红包接口
* 2015-06-23
+ 新增企业付款查询接口

View File

@ -18,6 +18,7 @@ import com.foxinmy.weixin4j.mp.payment.coupon.CouponResult;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponStock;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.payment.v3.MPPayment;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentRecord;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult;
import com.foxinmy.weixin4j.mp.payment.v3.Redpacket;
import com.foxinmy.weixin4j.mp.payment.v3.RedpacketRecord;
@ -30,7 +31,6 @@ import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.type.RefundType;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.token.TokenStorager;
import com.foxinmy.weixin4j.util.ConfigUtil;
/**
* 微信支付接口实现
@ -239,9 +239,8 @@ public class WeixinPayProxy {
IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId, String opUserPasswd)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return refundV2(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, opUserPasswd);
return refundV2(PayApi.DEFAULT_CA_FILE, idQuery, outRefundNo, totalFee,
refundFee, opUserId, opUserPasswd);
}
/**
@ -358,9 +357,8 @@ public class WeixinPayProxy {
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, CurrencyType.CNY, opUserId);
return pay3Api.refund(PayApi.DEFAULT_CA_FILE, idQuery, outRefundNo,
totalFee, refundFee, CurrencyType.CNY, opUserId);
}
/**
@ -444,8 +442,7 @@ public class WeixinPayProxy {
* @throws WeixinException
*/
public ApiResult reverse(IdQuery idQuery) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return payApi.reverse(caFile, idQuery);
return payApi.reverse(PayApi.DEFAULT_CA_FILE, idQuery);
}
/**
@ -552,9 +549,8 @@ public class WeixinPayProxy {
*/
public CouponResult sendCoupon(String couponStockId, String partnerTradeNo,
String openId) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return couponApi.sendCoupon(caFile, couponStockId, partnerTradeNo,
openId, null);
return couponApi.sendCoupon(PayApi.DEFAULT_CA_FILE, couponStockId,
partnerTradeNo, openId, null);
}
/**
@ -618,8 +614,7 @@ public class WeixinPayProxy {
*/
public RedpacketSendResult sendRedpack(Redpacket redpacket)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return cashApi.sendRedpack(caFile, redpacket);
return cashApi.sendRedpack(PayApi.DEFAULT_CA_FILE, redpacket);
}
/**
@ -648,8 +643,7 @@ public class WeixinPayProxy {
*/
public RedpacketRecord queryRedpack(String outTradeNo)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return cashApi.queryRedpack(caFile, outTradeNo);
return cashApi.queryRedpack(PayApi.DEFAULT_CA_FILE, outTradeNo);
}
/**
@ -679,7 +673,35 @@ public class WeixinPayProxy {
*/
public MPPaymentResult mpPayment(MPPayment mpPayment)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return cashApi.mpPayment(caFile, mpPayment);
return cashApi.mpPayment(PayApi.DEFAULT_CA_FILE, mpPayment);
}
/**
* 企业付款查询 用于商户的企业付款操作进行结果查询返回付款操作详细结果
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param outTradeNo
* 商户调用企业付款API时使用的商户订单号
* @return 付款记录
* @see com.foxinmy.weixin4j.mp.api.CashApi
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPaymentRecord
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/mch_pay.php?chapter=14_3">企业付款查询</a>
* @throws WeixinException
*/
public MPPaymentRecord mpPaymentQuery(File caFile, String outTradeNo)
throws WeixinException {
return cashApi.mpPaymentQuery(caFile, outTradeNo);
}
/**
* 企业付款查询采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#mpPaymentQuery(File, String)}
*/
public MPPaymentRecord mpPaymentQuery(String outTradeNo)
throws WeixinException {
return cashApi.mpPaymentQuery(PayApi.DEFAULT_CA_FILE, outTradeNo);
}
}

View File

@ -16,6 +16,7 @@ import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.v3.MPPayment;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentRecord;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult;
import com.foxinmy.weixin4j.mp.payment.v3.Redpacket;
import com.foxinmy.weixin4j.mp.payment.v3.RedpacketRecord;
@ -194,4 +195,52 @@ public class CashApi extends MpApi {
.replaceFirst("</mchid>", "</mch_id>");
return XmlStream.fromXML(text, MPPaymentResult.class);
}
/**
* 企业付款查询 用于商户的企业付款操作进行结果查询返回付款操作详细结果
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param outTradeNo
* 商户调用企业付款API时使用的商户订单号
* @return 付款记录
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPaymentRecord
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/mch_pay.php?chapter=14_3">企业付款查询</a>
* @throws WeixinException
*/
public MPPaymentRecord mpPaymentQuery(File caFile, String outTradeNo)
throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("nonce_str", RandomUtil.generateString(16));
obj.put("mch_id", weixinAccount.getMchId());
obj.put("appid", weixinAccount.getId());
obj.put("partner_trade_no", outTradeNo);
String sign = PayUtil.paysignMd5(obj, weixinAccount.getPaySignKey());
obj.put("sign", sign);
String param = XmlStream.map2xml(obj);
String mp_payquery_uri = getRequestUri("mp_payquery_uri");
WeixinResponse response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
SSLHttpClinet request = new SSLHttpClinet(weixinAccount.getMchId(),
ca);
response = request.post(mp_payquery_uri, param);
} catch (WeixinException e) {
throw e;
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
return response.getAsObject(new TypeReference<MPPaymentRecord>() {
});
}
}

View File

@ -18,13 +18,13 @@ import com.foxinmy.weixin4j.util.ConfigUtil;
* @see <a href="http://mp.weixin.qq.com/wiki/index.php">api文档</a>
*/
public class MpApi extends BaseApi {
private final static ResourceBundle WEIXIN_BUNDLE;
/**
* 默认使用weixin4j.properties文件中的公众号信息
*/
public final static WeixinMpAccount DEFAULT_WEIXIN_ACCOUNT;
static {
WEIXIN_BUNDLE = ResourceBundle
.getBundle("com/foxinmy/weixin4j/mp/api/weixin");

View File

@ -17,6 +17,7 @@ import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil;
/**
@ -31,6 +32,12 @@ import com.foxinmy.weixin4j.util.DateUtil;
*/
public abstract class PayApi extends MpApi {
/**
* 默认的证书文件
*/
public final static File DEFAULT_CA_FILE = new File(
ConfigUtil.getClassPathValue("ca_file"));
protected final WeixinMpAccount weixinAccount;
protected final TokenHolder tokenHolder;
@ -93,8 +100,9 @@ public abstract class PayApi extends MpApi {
throws WeixinException {
String payfeedback_update_uri = getRequestUri("payfeedback_update_uri");
Token token = tokenHolder.getToken();
WeixinResponse response = weixinClient.get(String.format(payfeedback_update_uri,
token.getAccessToken(), openId, feedbackId));
WeixinResponse response = weixinClient.get(String.format(
payfeedback_update_uri, token.getAccessToken(), openId,
feedbackId));
return response.getAsJsonResult();
}

View File

@ -180,4 +180,6 @@ redpack_send_uri={mch_base_url}/mmpaymkttransfers/sendredpack
# \u67e5\u8be2\u73b0\u91d1\u7ea2\u5305
redpack_query_uri={mch_base_url}/mmpaymkttransfers/gethbinfo
# \u4f01\u4e1a\u5411\u4e2a\u4eba\u4ed8\u6b3e
mp_payment_uri={mch_base_url}/mmpaymkttransfers/promotion/transfers
mp_payment_uri={mch_base_url}/mmpaymkttransfers/promotion/transfers
# \u4f01\u4e1a\u4ed8\u6b3e\u67e5\u8be2
mp_payquery_uri={mch_base_url}/mmpaymkttransfers/gettransferinfo

View File

@ -0,0 +1,193 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.MPPaymentCheckNameType;
import com.foxinmy.weixin4j.util.DateUtil;
/**
* 企业付款记录
*
* @className MPPaymentRecord
* @author jy
* @date 2015年6月23日
* @since JDK 1.7
* @see
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class MPPaymentRecord extends ApiResult {
private static final long serialVersionUID = -1926873539419750498L;
/**
* 微信订单订单号
*/
@JSONField(name = "detail_id")
@XmlElement(name = "detail_id")
private String transactionId;
/**
* 商户订单号
*/
@JSONField(name = "partner_trade_no")
@XmlElement(name = "partner_trade_no")
private String outTradeNo;
/**
* 交易状态 SUCCESS:转账成功 FAILED:转账失败
*/
@JSONField(name = "status")
@XmlElement(name = "status")
private String transactionStatus;
/**
* 如果失败则应该有原因
*/
@JSONField(name = "reason")
@XmlElement(name = "reason")
private String failureReason;
/**
* 收款用户openid
*/
private String openid;
/**
* 收款用户姓名
*/
@JSONField(name = "transfer_name")
@XmlElement(name = "transfer_name")
private String transferName;
/**
* 付款金额(单位为分)
*/
@JSONField(name = "payment_amount")
@XmlElement(name = "payment_amount")
private int paymentAmount;
/**
* 转账时间
*/
@JSONField(name = "transfer_time")
@XmlElement(name = "transfer_time")
private String transferTime;
/**
* 校验用户姓名选项
*
* @see com.foxinmy.weixin4j.mp.type.MPPaymentCheckNameType
*/
@XmlElement(name = "check_name")
@JSONField(name = "check_name")
private MPPaymentCheckNameType checkNameType;
/**
* 企业付款描述信息
*/
private String desc;
/**
* 实名验证结果 PASS:通过 FAILED:不通过
*/
@JSONField(name = "check_name_result")
@XmlElement(name = "check_name_result")
private String checkNameResult;
protected MPPaymentRecord() {
// jaxb required
}
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getTransactionStatus() {
return transactionStatus;
}
/**
* 格式化交易状态
*
* @return
*/
@JSONField(serialize = false)
public boolean getFormatTransactionStatus() {
return "success".equalsIgnoreCase(transactionStatus);
}
public String getFailureReason() {
return failureReason;
}
public String getOpenid() {
return openid;
}
public String getTransferName() {
return transferName;
}
public int getPaymentAmount() {
return paymentAmount;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false)
public double getFormatPaymentAmount() {
return paymentAmount / 100d;
}
public String getTransferTime() {
return transferTime;
}
/**
* 格式化转账时间
*
* @return
*/
@JSONField(serialize = false)
public Date getFormatTransferTime() {
return DateUtil.parse2yyyyMMddHHmmss(transferTime);
}
public MPPaymentCheckNameType getCheckNameType() {
return checkNameType;
}
public String getDesc() {
return desc;
}
public String getCheckNameResult() {
return checkNameResult;
}
/**
* 格式化交易状态
*
* @return
*/
@JSONField(serialize = false)
public boolean getFormatCheckNameResult() {
return "pass".equalsIgnoreCase(checkNameResult);
}
@Override
public String toString() {
return "MPPaymentRecord [transactionId=" + transactionId
+ ", outTradeNo=" + outTradeNo + ", transactionStatus="
+ getFormatTransactionStatus() + ", failureReason="
+ failureReason + ", openid=" + openid + ", transferName="
+ transferName + ", paymentAmount=" + getFormatPaymentAmount()
+ ", transferTime=" + transferTime + ", checkNameType="
+ checkNameType + ", desc=" + desc + ", checkNameResult="
+ getFormatCheckNameResult() + ", " + super.toString() + "]";
}
}

View File

@ -48,7 +48,7 @@ public class SuiteTicketHolder {
* @param suiteTicket
* @throws WeixinException
*/
public void cachingTicket(SuiteTicketMessage suiteTicket)
public void cachingTicket(WeixinSuiteMessage suiteTicket)
throws WeixinException {
Token token = new Token(suiteTicket.getSuiteTicket());
token.setExpiresIn(-1);

View File

@ -7,9 +7,18 @@ 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 SuiteTicketMessage implements Serializable {
public class WeixinSuiteMessage implements Serializable {
private static final long serialVersionUID = 6457919241019021514L;
/**
@ -60,7 +69,7 @@ public class SuiteTicketMessage implements Serializable {
@Override
public String toString() {
return "SuiteTicketMessage [suiteId=" + suiteId + ", eventType="
return "WeixinSuiteMessage [suiteId=" + suiteId + ", eventType="
+ eventType + ", timeStamp=" + timeStamp + ", SuiteTicket="
+ SuiteTicket + ", authCorpId=" + authCorpId + "]";
}

View File

@ -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 + "]";
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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 + "]";
}
}

View File

@ -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() {

View File

@ -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");
}

View File

@ -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;
}

View File

@ -2,8 +2,6 @@ package com.foxinmy.weixin4j.server.test;
import io.netty.channel.ChannelHandlerContext;
import java.math.BigDecimal;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.handler.BlankMessageHandler;
import com.foxinmy.weixin4j.handler.DebugMessageHandler;
@ -27,6 +25,8 @@ import com.foxinmy.weixin4j.startup.WeixinServerBootstrap;
*/
public class MessageServerStartup {
// 微信号(原始ID)
final String openid = "gh_22b350df957b";
// 应用ID
final String appid = "wx4ab8f8de58159a57";
// 开发者token
@ -41,8 +41,8 @@ public class MessageServerStartup {
*/
public void test1() throws WeixinException {
// 所有请求都回复调试的文本消息
new WeixinServerBootstrap(token).addHandler(DebugMessageHandler.global)
.startup();
new WeixinServerBootstrap(openid, token).addHandler(
DebugMessageHandler.global).startup();
}
/**
@ -78,8 +78,8 @@ public class MessageServerStartup {
public void test4() throws WeixinException {
// 扫描包加载消息处理器
String packageToScan = "com.foxinmy.weixin4j.handler";
new WeixinServerBootstrap(token).handlerPackagesToScan(packageToScan)
.startup();
new WeixinServerBootstrap(openid, token).handlerPackagesToScan(
packageToScan).startup();
}
public void test5() throws WeixinException {
@ -109,14 +109,11 @@ public class MessageServerStartup {
System.err.println("请求处理完毕");
}
};
new WeixinServerBootstrap(token).addInterceptor(interceptor)
new WeixinServerBootstrap(openid, token).addInterceptor(interceptor)
.addHandler(BlankMessageHandler.global).startup();
}
public static void main(String[] args) throws Exception {
System.err.println(new BigDecimal(new Long(14212345l)).divide(
new BigDecimal("100000"))
.toString());
new MessageServerStartup().test1();
}
}