修复媒体消息转换错误bug & 新增创建卡券二维码接口

This commit is contained in:
jinyu
2016-08-09 11:46:37 +08:00
parent 1c2c966ea2
commit 4c8e33dfac
29 changed files with 551 additions and 205 deletions
+1 -1
View File
@@ -160,7 +160,7 @@
* 2016-08-05
+ model包拆分media/paing
+ model包拆分media/paging
+ type包拆分card/mch
@@ -1,6 +1,8 @@
package com.foxinmy.weixin4j.http;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.foxinmy.weixin4j.util.StringUtil;
@@ -35,12 +37,14 @@ public class MimeType implements Serializable {
public static final MimeType TEXT_XML;
public static final MimeType TEXT_JSON;
public static final List<MimeType> STREAM_MIMETYPES;
static {
APPLICATION_FORM_URLENCODED = valueOf("application/x-www-form-urlencoded");
APPLICATION_JSON = valueOf("application/json");
APPLICATION_OCTET_STREAM = valueOf("application/octet-stream");
APPLICATION_XML = valueOf("application/xml");
MULTIPART_FORM_DATA = MimeType.valueOf("multipart/form-data");
MULTIPART_FORM_DATA = valueOf("multipart/form-data");
TEXT_HTML = valueOf("text/html");
TEXT_PLAIN = valueOf("text/plain");
IMAGE_JPG = valueOf("image/jpg");
@@ -48,6 +52,12 @@ public class MimeType implements Serializable {
VIDEO_MPEG4 = valueOf("video/mpeg4");
TEXT_XML = valueOf("text/xml");
TEXT_JSON = valueOf("text/json");
STREAM_MIMETYPES = new ArrayList<MimeType>(4);
STREAM_MIMETYPES.add(APPLICATION_OCTET_STREAM);
STREAM_MIMETYPES.add(valueOf("image/*"));
STREAM_MIMETYPES.add(valueOf("audio/*"));
STREAM_MIMETYPES.add(valueOf("video/*"));
}
public MimeType(String type) {
@@ -72,28 +82,33 @@ public class MimeType implements Serializable {
}
public boolean isWildcardSubtype() {
return WILDCARD_TYPE.equals(getSubType()) || getSubType().startsWith("*+");
return WILDCARD_TYPE.equals(getSubType())
|| getSubType().startsWith("*+");
}
public static MimeType valueOf(String value) {
if (StringUtil.isBlank(value)) {
return null;
}
String mimeType = StringUtil.tokenizeToStringArray(value, ";")[0].trim().toLowerCase(Locale.ENGLISH);
String mimeType = StringUtil.tokenizeToStringArray(value, ";")[0]
.trim().toLowerCase(Locale.ENGLISH);
if (WILDCARD_TYPE.equals(mimeType)) {
mimeType = "*/*";
}
int subIndex = mimeType.indexOf('/');
if (subIndex == -1) {
throw new IllegalArgumentException(mimeType + ":does not contain '/'");
throw new IllegalArgumentException(mimeType
+ ":does not contain '/'");
}
if (subIndex == mimeType.length() - 1) {
throw new IllegalArgumentException(mimeType + ":does not contain subtype after '/'");
throw new IllegalArgumentException(mimeType
+ ":does not contain subtype after '/'");
}
String type = mimeType.substring(0, subIndex);
String subType = mimeType.substring(subIndex + 1, mimeType.length());
if (WILDCARD_TYPE.equals(type) && !WILDCARD_TYPE.equals(subType)) {
throw new IllegalArgumentException(mimeType + ":wildcard type is legal only in '*/*' (all mime types)");
throw new IllegalArgumentException(mimeType
+ ":wildcard type is legal only in '*/*' (all mime types)");
}
return new MimeType(type, subType);
}
@@ -121,10 +136,14 @@ public class MimeType implements Serializable {
// application/*+xml includes application/soap+xml
int otherPlusIdx = other.getSubType().indexOf('+');
if (otherPlusIdx != -1) {
String thisSubtypeNoSuffix = getSubType().substring(0, thisPlusIdx);
String thisSubtypeSuffix = getSubType().substring(thisPlusIdx + 1);
String otherSubtypeSuffix = other.getSubType().substring(otherPlusIdx + 1);
if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
String thisSubtypeNoSuffix = getSubType().substring(0,
thisPlusIdx);
String thisSubtypeSuffix = getSubType().substring(
thisPlusIdx + 1);
String otherSubtypeSuffix = other.getSubType()
.substring(otherPlusIdx + 1);
if (thisSubtypeSuffix.equals(otherSubtypeSuffix)
&& WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
return true;
}
}
@@ -148,7 +167,8 @@ public class MimeType implements Serializable {
return false;
}
MimeType otherType = (MimeType) other;
return this.type.equalsIgnoreCase(otherType.type) && this.subType.equalsIgnoreCase(otherType.subType);
return this.type.equalsIgnoreCase(otherType.type)
&& this.subType.equalsIgnoreCase(otherType.subType);
}
@Override
@@ -15,6 +15,7 @@ import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpParams;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.URLParameter;
import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.HttpMultipartMode;
@@ -129,8 +130,6 @@ public class WeixinRequestExecutor {
+ request.getURI().toString());
HttpResponse httpResponse = httpClient.execute(request);
WeixinResponse response = new WeixinResponse(httpResponse);
logger.info("weixin response << " + httpResponse.getProtocol()
+ httpResponse.getStatus() + ":" + response.getAsString());
handleResponse(response);
return response;
} catch (HttpClientException e) {
@@ -138,6 +137,24 @@ public class WeixinRequestExecutor {
}
}
/**
* 响应内容是否为流
*
* @param response
* 微信响应
* @return true/false
*/
private boolean hasStreamMimeType(WeixinResponse response) {
MimeType responseMimeType = MimeType.valueOf(response.getHeaders()
.getContentType());
for (MimeType streamMimeType : MimeType.STREAM_MIMETYPES) {
if (streamMimeType.includes(responseMimeType)) {
return true;
}
}
return false;
}
/**
* handle the weixin response
*
@@ -147,6 +164,16 @@ public class WeixinRequestExecutor {
*/
protected void handleResponse(WeixinResponse response)
throws WeixinException {
boolean hasStreamMimeType = hasStreamMimeType(response);
logger.info("weixin response << "
+ response.getProtocol()
+ response.getStatus()
+ ":"
+ (hasStreamMimeType ? response.getHeaders().getContentType()
: response.getAsString()));
if (hasStreamMimeType) {
return;
}
ApiResult result = response.getAsResult();
if (!SUCCESS_CODE.contains(String.format(",%s,", result.getReturnCode()
.toLowerCase()))) {
@@ -5,7 +5,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.http.HttpHeaders;
@@ -34,6 +33,8 @@ public class WeixinResponse implements HttpResponse {
};
private final TypeReference<XmlResult> XMLRESULT_CLAZZ = new TypeReference<XmlResult>() {
};
private final TypeReference<JSONObject> JSONOBJECT_CLAZZ = new TypeReference<JSONObject>() {
};
static {
messageConverters.add(new JsonMessageConverter());
@@ -56,7 +57,7 @@ public class WeixinResponse implements HttpResponse {
}
public JSONObject getAsJson() {
return JSON.parseObject(getAsString());
return getAsObject(JSONOBJECT_CLAZZ);
}
public XmlResult getAsXml() {
@@ -71,7 +72,8 @@ public class WeixinResponse implements HttpResponse {
try {
return messageConverter.convert(clazz, response);
} catch (IOException e) {
throw new RuntimeException("IO error on convert to " + typeReference, e);
throw new RuntimeException("IO error on convert to "
+ typeReference, e);
}
}
}
@@ -63,7 +63,7 @@ public class Button implements Serializable {
}
/**
* 创建一个菜单
* 创建一个具有子菜单的菜单
*
* @param name
* 菜单名
@@ -76,7 +76,7 @@ public class Button implements Serializable {
}
/**
* 创建一个菜单
* 创建一个普通菜单
*
* @param name
* 菜单名
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
/**
* 卡券构造器
@@ -0,0 +1,156 @@
package com.foxinmy.weixin4j.model.card;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 卡券二维码参数
*
* @className CardQR
* @author jinyu(foxinmy@gmail.com)
* @date 2016年8月9日
* @since JDK 1.6
* @see Builder
*/
public class CardQR implements Serializable {
private static final long serialVersionUID = -2810577326677518511L;
/**
* 卡券ID
*/
@JSONField(name = "card_id")
private String cardId;
/**
* 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。
*/
@JSONField(name = "code")
private String cardCode;
/**
* 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。
*/
@JSONField(name = "openid")
private String openId;
/**
* 领取场景值,用于领取渠道的数据统计,默认值为0,字段类型为整型,长度限制为60位数字。用户领取卡券后触发的事件推送中会带上此自定义场景值。
*/
@JSONField(name = "outer_str")
private String sceneValue;
/**
* 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,
* 卡券须通过审核且库存不为0。
*/
@JSONField(name = "is_unique_code")
private boolean isUniqueCode;
private CardQR(Builder builder) {
this.cardId = builder.cardId;
this.cardCode = builder.cardCode;
this.isUniqueCode = builder.isUniqueCode;
this.openId = builder.openId;
this.sceneValue = builder.sceneValue;
}
public String getCardId() {
return cardId;
}
public String getCardCode() {
return cardCode;
}
public String getOpenId() {
return openId;
}
public String getSceneValue() {
return sceneValue;
}
public boolean isUniqueCode() {
return isUniqueCode;
}
@Override
public String toString() {
return "CardQR [cardId=" + cardId + ", cardCode=" + cardCode
+ ", openId=" + openId + ", sceneValue=" + sceneValue
+ ", isUniqueCode=" + isUniqueCode + "]";
}
public static class Builder {
/**
* 卡券ID
*/
private String cardId;
/**
* 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。
*/
private String cardCode;
/**
* 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。
*/
private String openId;
/**
* 用户首次领卡时,会通过领取事件推送给商户;
* 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源
*/
private String sceneValue;
/**
* 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,
* 卡券须通过审核且库存不为0。
*/
private boolean isUniqueCode;
public Builder(String cardId) {
this.cardId = cardId;
}
/**
* 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。
*
* @param cardCode
* 卡券code码
*/
public Builder cardCode(String cardCode) {
this.cardCode = cardCode;
return this;
}
/**
* 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。
*
* @param openId
* 用户openid
*/
public Builder openId(String openId) {
this.openId = openId;
return this;
}
/**
* 用户首次领卡时,会通过领取事件推送给商户;
* 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源
*
* @param sceneValue
* 场景值
*/
public Builder sceneValuer(String sceneValue) {
this.sceneValue = sceneValue;
return this;
}
/**
* 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,
* 卡券须通过审核且库存不为0。
*/
public Builder openUniqueCode() {
this.isUniqueCode = true;
return this;
}
public CardQR build() {
return new CardQR(this);
}
}
}
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import java.io.Serializable;
import java.util.ArrayList;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import java.io.Serializable;
import java.util.Arrays;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card;
package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType;
@@ -0,0 +1,173 @@
package com.foxinmy.weixin4j.model.qr;
import java.io.Serializable;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.model.card.CardQR;
import com.foxinmy.weixin4j.type.QRType;
/**
* 二维码参数对象
*
* @className QRParameter
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月8日
* @since JDK 1.6
* @see #createTemporaryQR(int, long) 创建整型临时二维码
* @see #createPermanenceQR(int) 创建整型永久二维码
* @see #createPermanenceQR(String) 创建字符串型永久二维码
* @see #createCardCouponQR(Integer, CardQR...) 创建卡券二维码
*/
public class QRParameter implements Serializable {
private static final long serialVersionUID = 6611187606558274253L;
/**
* 二维码的类型
*
* @see com.foxinmy.weixin4j.type.QRType
*/
@JSONField(name = "action_name")
private QRType qrType;
/**
* 二维码的有效时间
*/
@JSONField(name = "expire_seconds")
private Integer expireSeconds;
/**
* 二维码的内容
*/
@JSONField(name = "action_info")
private JSONObject sceneContent;
private QRParameter(QRType qrType, Integer expireSeconds,
JSONObject sceneContent) {
this.qrType = qrType;
this.expireSeconds = expireSeconds;
this.sceneContent = sceneContent;
}
public Integer getExpireSeconds() {
return expireSeconds;
}
public QRType getQrType() {
return qrType;
}
public JSONObject getSceneContent() {
return sceneContent;
}
/**
* 创建临时二维码
*
* @param expireSeconds
* 二维码有效时间,以秒为单位。 最大不超过2592000(即30天)
* @param sceneValue
* 二维码的场景值 <font color="red">临时二维码最大值为无符号32位非0整型</font>
* @return 二维码参数
*/
public static QRParameter createTemporaryQR(int expireSeconds,
long sceneValue) {
JSONObject sceneContent = new JSONObject();
JSONObject scene = new JSONObject();
scene.put("scene_id", sceneValue);
sceneContent.put("scene", scene);
return new QRParameter(QRType.QR_SCENE, expireSeconds, sceneContent);
}
/**
* 创建永久二维码(场景值为int)
*
* @param sceneValue
* 场景值 最大值为100000 (目前参数只支持1--100000)
*/
public static QRParameter createPermanenceQR(int sceneValue) {
JSONObject sceneContent = new JSONObject();
JSONObject scene = new JSONObject();
scene.put("scene_id", sceneValue);
sceneContent.put("scene", scene);
return new QRParameter(QRType.QR_LIMIT_SCENE, null, sceneContent);
}
/**
* 创建永久二维码(场景值为string)
*
* @param sceneValue
* 场景值
*/
public static QRParameter createPermanenceQR(String sceneValue) {
JSONObject sceneContent = new JSONObject();
JSONObject scene = new JSONObject();
scene.put("scene_str", sceneValue);
sceneContent.put("scene", scene);
return new QRParameter(QRType.QR_LIMIT_STR_SCENE, null, sceneContent);
}
/**
* 创建卡券二维码
*
* @param expireSeconds
* 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效
* @param cardQRs
* 二维码参数:二维码领取单张卡券/多张卡券
*/
public static QRParameter createCardCouponQR(Integer expireSeconds,
CardQR... cardQRs) {
QRType qrType = QRType.QR_CARD;
JSONObject sceneContent = new JSONObject();
if (cardQRs.length > 1) {
qrType = QRType.QR_MULTIPLE_CARD;
JSONObject multipleCard = new JSONObject();
multipleCard.put("card_list", cardQRs);
sceneContent.put("multiple_card", multipleCard);
} else {
sceneContent.put("card", cardQRs[0]);
}
return new QRParameter(qrType, expireSeconds, sceneContent);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((expireSeconds == null) ? 0 : expireSeconds.hashCode());
result = prime * result + ((qrType == null) ? 0 : qrType.hashCode());
result = prime * result
+ ((sceneContent == null) ? 0 : sceneContent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
QRParameter other = (QRParameter) obj;
if (expireSeconds == null) {
if (other.expireSeconds != null)
return false;
} else if (!expireSeconds.equals(other.expireSeconds))
return false;
if (qrType != other.qrType)
return false;
if (sceneContent == null) {
if (other.sceneContent != null)
return false;
} else if (!sceneContent.equals(other.sceneContent))
return false;
return true;
}
@Override
public String toString() {
return "QRParameter [qrType=" + qrType + ", expireSeconds="
+ expireSeconds + ", sceneContent=" + sceneContent + "]";
}
}
@@ -0,0 +1,74 @@
package com.foxinmy.weixin4j.model.qr;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 二维码
*
* @className QRResult
* @author jinyu(foxinmy@gmail.com)
* @date 2015年7月29日
* @since JDK 1.6
* @see
*/
public class QRResult implements Serializable {
private static final long serialVersionUID = 7730781702153258151L;
private String ticket;
private String url;
@JSONField(name = "show_qrcode_url")
private String showUrl;
@JSONField(name = "expire_seconds")
private int expireSeconds;
private byte[] content;
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getShowUrl() {
return showUrl;
}
public void setShowUrl(String showUrl) {
this.showUrl = showUrl;
}
public int getExpireSeconds() {
return expireSeconds;
}
public void setExpireSeconds(int expireSeconds) {
this.expireSeconds = expireSeconds;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
@Override
public String toString() {
return "QRResult [ticket=" + ticket + ", url=" + url + ", showUrl="
+ showUrl + ", expireSeconds=" + expireSeconds
+ ", content=..." + "]";
}
}
@@ -32,7 +32,7 @@ public class TokenManager extends CacheManager<Token> {
/**
* 获取token字符串
*
* @return
* @return token字符串
* @throws WeixinException
*/
public String getAccessToken() throws WeixinException {
@@ -21,5 +21,13 @@ public enum QRType {
/**
* 永久二维码(场景值为字符串长度在1-64之间)
*/
QR_LIMIT_STR_SCENE;
QR_LIMIT_STR_SCENE,
/**
* 卡券二维码:单个卡券
*/
QR_CARD,
/**
* 卡券二维码:多个卡券
*/
QR_MULTIPLE_CARD;
}