修复媒体消息转换错误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

View File

@ -738,3 +738,19 @@
+ weixin4j-base:主要调整退款相关类与官网一致 + weixin4j-base:主要调整退款相关类与官网一致
+ weixin4j-base:获取cache时加锁处理(via 风车车) + weixin4j-base:获取cache时加锁处理(via 风车车)
* 2016-08-05
+ weixin4j-base:model包拆分media/paging
+ weixin4j-base:type包拆分card/mch
+ weixin4j-base:新增card卡券相关类
+ weixin4j-mp:新增CardApi:创建卡券接口
* 2016-08-09
+ weixin4j-base:修复媒体消息转换错误bug
+ weixin4j-mp:新增创建卡券二维码接口

View File

@ -160,7 +160,7 @@
* 2016-08-05 * 2016-08-05
+ model包拆分media/paing + model包拆分media/paging
+ type包拆分card/mch + type包拆分card/mch

View File

@ -1,6 +1,8 @@
package com.foxinmy.weixin4j.http; package com.foxinmy.weixin4j.http;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import com.foxinmy.weixin4j.util.StringUtil; 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_XML;
public static final MimeType TEXT_JSON; public static final MimeType TEXT_JSON;
public static final List<MimeType> STREAM_MIMETYPES;
static { static {
APPLICATION_FORM_URLENCODED = valueOf("application/x-www-form-urlencoded"); APPLICATION_FORM_URLENCODED = valueOf("application/x-www-form-urlencoded");
APPLICATION_JSON = valueOf("application/json"); APPLICATION_JSON = valueOf("application/json");
APPLICATION_OCTET_STREAM = valueOf("application/octet-stream"); APPLICATION_OCTET_STREAM = valueOf("application/octet-stream");
APPLICATION_XML = valueOf("application/xml"); 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_HTML = valueOf("text/html");
TEXT_PLAIN = valueOf("text/plain"); TEXT_PLAIN = valueOf("text/plain");
IMAGE_JPG = valueOf("image/jpg"); IMAGE_JPG = valueOf("image/jpg");
@ -48,6 +52,12 @@ public class MimeType implements Serializable {
VIDEO_MPEG4 = valueOf("video/mpeg4"); VIDEO_MPEG4 = valueOf("video/mpeg4");
TEXT_XML = valueOf("text/xml"); TEXT_XML = valueOf("text/xml");
TEXT_JSON = valueOf("text/json"); 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) { public MimeType(String type) {
@ -72,28 +82,33 @@ public class MimeType implements Serializable {
} }
public boolean isWildcardSubtype() { public boolean isWildcardSubtype() {
return WILDCARD_TYPE.equals(getSubType()) || getSubType().startsWith("*+"); return WILDCARD_TYPE.equals(getSubType())
|| getSubType().startsWith("*+");
} }
public static MimeType valueOf(String value) { public static MimeType valueOf(String value) {
if (StringUtil.isBlank(value)) { if (StringUtil.isBlank(value)) {
return null; 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)) { if (WILDCARD_TYPE.equals(mimeType)) {
mimeType = "*/*"; mimeType = "*/*";
} }
int subIndex = mimeType.indexOf('/'); int subIndex = mimeType.indexOf('/');
if (subIndex == -1) { if (subIndex == -1) {
throw new IllegalArgumentException(mimeType + ":does not contain '/'"); throw new IllegalArgumentException(mimeType
+ ":does not contain '/'");
} }
if (subIndex == mimeType.length() - 1) { 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 type = mimeType.substring(0, subIndex);
String subType = mimeType.substring(subIndex + 1, mimeType.length()); String subType = mimeType.substring(subIndex + 1, mimeType.length());
if (WILDCARD_TYPE.equals(type) && !WILDCARD_TYPE.equals(subType)) { 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); return new MimeType(type, subType);
} }
@ -121,10 +136,14 @@ public class MimeType implements Serializable {
// application/*+xml includes application/soap+xml // application/*+xml includes application/soap+xml
int otherPlusIdx = other.getSubType().indexOf('+'); int otherPlusIdx = other.getSubType().indexOf('+');
if (otherPlusIdx != -1) { if (otherPlusIdx != -1) {
String thisSubtypeNoSuffix = getSubType().substring(0, thisPlusIdx); String thisSubtypeNoSuffix = getSubType().substring(0,
String thisSubtypeSuffix = getSubType().substring(thisPlusIdx + 1); thisPlusIdx);
String otherSubtypeSuffix = other.getSubType().substring(otherPlusIdx + 1); String thisSubtypeSuffix = getSubType().substring(
if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) { thisPlusIdx + 1);
String otherSubtypeSuffix = other.getSubType()
.substring(otherPlusIdx + 1);
if (thisSubtypeSuffix.equals(otherSubtypeSuffix)
&& WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
return true; return true;
} }
} }
@ -148,7 +167,8 @@ public class MimeType implements Serializable {
return false; return false;
} }
MimeType otherType = (MimeType) other; 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 @Override

View File

@ -15,6 +15,7 @@ import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpParams; import com.foxinmy.weixin4j.http.HttpParams;
import com.foxinmy.weixin4j.http.HttpRequest; import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse; import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.URLParameter; import com.foxinmy.weixin4j.http.URLParameter;
import com.foxinmy.weixin4j.http.apache.FormBodyPart; import com.foxinmy.weixin4j.http.apache.FormBodyPart;
import com.foxinmy.weixin4j.http.apache.HttpMultipartMode; import com.foxinmy.weixin4j.http.apache.HttpMultipartMode;
@ -129,8 +130,6 @@ public class WeixinRequestExecutor {
+ request.getURI().toString()); + request.getURI().toString());
HttpResponse httpResponse = httpClient.execute(request); HttpResponse httpResponse = httpClient.execute(request);
WeixinResponse response = new WeixinResponse(httpResponse); WeixinResponse response = new WeixinResponse(httpResponse);
logger.info("weixin response << " + httpResponse.getProtocol()
+ httpResponse.getStatus() + ":" + response.getAsString());
handleResponse(response); handleResponse(response);
return response; return response;
} catch (HttpClientException e) { } 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 * handle the weixin response
* *
@ -147,6 +164,16 @@ public class WeixinRequestExecutor {
*/ */
protected void handleResponse(WeixinResponse response) protected void handleResponse(WeixinResponse response)
throws WeixinException { 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(); ApiResult result = response.getAsResult();
if (!SUCCESS_CODE.contains(String.format(",%s,", result.getReturnCode() if (!SUCCESS_CODE.contains(String.format(",%s,", result.getReturnCode()
.toLowerCase()))) { .toLowerCase()))) {

View File

@ -5,7 +5,6 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.http.HttpHeaders; 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<XmlResult> XMLRESULT_CLAZZ = new TypeReference<XmlResult>() {
}; };
private final TypeReference<JSONObject> JSONOBJECT_CLAZZ = new TypeReference<JSONObject>() {
};
static { static {
messageConverters.add(new JsonMessageConverter()); messageConverters.add(new JsonMessageConverter());
@ -56,7 +57,7 @@ public class WeixinResponse implements HttpResponse {
} }
public JSONObject getAsJson() { public JSONObject getAsJson() {
return JSON.parseObject(getAsString()); return getAsObject(JSONOBJECT_CLAZZ);
} }
public XmlResult getAsXml() { public XmlResult getAsXml() {
@ -71,7 +72,8 @@ public class WeixinResponse implements HttpResponse {
try { try {
return messageConverter.convert(clazz, response); return messageConverter.convert(clazz, response);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("IO error on convert to " + typeReference, e); throw new RuntimeException("IO error on convert to "
+ typeReference, e);
} }
} }
} }

View File

@ -63,7 +63,7 @@ public class Button implements Serializable {
} }
/** /**
* 创建一个菜单 * 创建一个具有子菜单的菜单
* *
* @param name * @param name
* 菜单名 * 菜单名
@ -76,7 +76,7 @@ public class Button implements Serializable {
} }
/** /**
* 创建一个菜单 * 创建一个普通菜单
* *
* @param name * @param name
* 菜单名 * 菜单名

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
/** /**
* 卡券构造器 * 卡券构造器

View File

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

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.card; package com.foxinmy.weixin4j.model.card;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.card.CardType; import com.foxinmy.weixin4j.type.card.CardType;

View File

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

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.mp.model; package com.foxinmy.weixin4j.model.qr;
import java.io.Serializable; import java.io.Serializable;
@ -19,6 +19,8 @@ public class QRResult implements Serializable {
private String ticket; private String ticket;
private String url; private String url;
@JSONField(name = "show_qrcode_url")
private String showUrl;
@JSONField(name = "expire_seconds") @JSONField(name = "expire_seconds")
private int expireSeconds; private int expireSeconds;
private byte[] content; private byte[] content;
@ -39,6 +41,14 @@ public class QRResult implements Serializable {
this.url = url; this.url = url;
} }
public String getShowUrl() {
return showUrl;
}
public void setShowUrl(String showUrl) {
this.showUrl = showUrl;
}
public int getExpireSeconds() { public int getExpireSeconds() {
return expireSeconds; return expireSeconds;
} }
@ -57,7 +67,8 @@ public class QRResult implements Serializable {
@Override @Override
public String toString() { public String toString() {
return "QRResult [ticket=" + ticket + ", url=" + url return "QRResult [ticket=" + ticket + ", url=" + url + ", showUrl="
+ ", expireSeconds=" + expireSeconds + ", content=...]"; + showUrl + ", expireSeconds=" + expireSeconds
+ ", content=..." + "]";
} }
} }

View File

@ -32,7 +32,7 @@ public class TokenManager extends CacheManager<Token> {
/** /**
* 获取token字符串 * 获取token字符串
* *
* @return * @return token字符串
* @throws WeixinException * @throws WeixinException
*/ */
public String getAccessToken() throws WeixinException { public String getAccessToken() throws WeixinException {

View File

@ -21,5 +21,13 @@ public enum QRType {
/** /**
* 永久二维码(场景值为字符串长度在1-64之间) * 永久二维码(场景值为字符串长度在1-64之间)
*/ */
QR_LIMIT_STR_SCENE; QR_LIMIT_STR_SCENE,
/**
* 卡券二维码单个卡券
*/
QR_CARD,
/**
* 卡券二维码多个卡券
*/
QR_MULTIPLE_CARD;
} }

View File

@ -4,18 +4,21 @@ import java.io.InputStream;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.foxinmy.weixin4j.card.CardCoupon;
import com.foxinmy.weixin4j.card.CardCoupons;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.weixin.ApiResult; import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.model.Button; import com.foxinmy.weixin4j.model.Button;
import com.foxinmy.weixin4j.model.WeixinAccount; import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.model.card.CardCoupon;
import com.foxinmy.weixin4j.model.card.CardCoupons;
import com.foxinmy.weixin4j.model.card.CardQR;
import com.foxinmy.weixin4j.model.media.MediaCounter; import com.foxinmy.weixin4j.model.media.MediaCounter;
import com.foxinmy.weixin4j.model.media.MediaDownloadResult; import com.foxinmy.weixin4j.model.media.MediaDownloadResult;
import com.foxinmy.weixin4j.model.media.MediaItem; import com.foxinmy.weixin4j.model.media.MediaItem;
import com.foxinmy.weixin4j.model.media.MediaRecord; import com.foxinmy.weixin4j.model.media.MediaRecord;
import com.foxinmy.weixin4j.model.media.MediaUploadResult; import com.foxinmy.weixin4j.model.media.MediaUploadResult;
import com.foxinmy.weixin4j.model.paging.Pageable; import com.foxinmy.weixin4j.model.paging.Pageable;
import com.foxinmy.weixin4j.model.qr.QRParameter;
import com.foxinmy.weixin4j.model.qr.QRResult;
import com.foxinmy.weixin4j.mp.api.CardApi; import com.foxinmy.weixin4j.mp.api.CardApi;
import com.foxinmy.weixin4j.mp.api.CustomApi; import com.foxinmy.weixin4j.mp.api.CustomApi;
import com.foxinmy.weixin4j.mp.api.DataApi; import com.foxinmy.weixin4j.mp.api.DataApi;
@ -43,8 +46,6 @@ import com.foxinmy.weixin4j.mp.model.KfSession.KfSessionCounter;
import com.foxinmy.weixin4j.mp.model.Menu; import com.foxinmy.weixin4j.mp.model.Menu;
import com.foxinmy.weixin4j.mp.model.MenuMatchRule; import com.foxinmy.weixin4j.mp.model.MenuMatchRule;
import com.foxinmy.weixin4j.mp.model.MenuSetting; import com.foxinmy.weixin4j.mp.model.MenuSetting;
import com.foxinmy.weixin4j.mp.model.QRParameter;
import com.foxinmy.weixin4j.mp.model.QRResult;
import com.foxinmy.weixin4j.mp.model.SemQuery; import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.model.SemResult; import com.foxinmy.weixin4j.mp.model.SemResult;
import com.foxinmy.weixin4j.mp.model.Tag; import com.foxinmy.weixin4j.mp.model.Tag;
@ -176,6 +177,7 @@ public class WeixinProxy {
componentTokenManager), perTicketManager.getCacheStorager())); componentTokenManager), perTicketManager.getCacheStorager()));
this.settings = new Weixin4jSettings<WeixinAccount>(new WeixinAccount( this.settings = new Weixin4jSettings<WeixinAccount>(new WeixinAccount(
perTicketManager.getAuthAppId(), null)); perTicketManager.getAuthAppId(), null));
this.settings.setCacheStorager(perTicketManager.getCacheStorager());
} }
/** /**
@ -1376,8 +1378,8 @@ public class WeixinProxy {
* 二维码参数 * 二维码参数
* @return 二维码结果对象 * @return 二维码结果对象
* @throws WeixinException * @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.QRResult * @see com.foxinmy.weixin4j.model.qr.QRResult
* @see com.foxinmy.weixin4j.mp.model.QRParameter * @see com.foxinmy.weixin4j.model.qr.QRParameter
* @see com.foxinmy.weixin4j.mp.api.QrApi * @see com.foxinmy.weixin4j.mp.api.QrApi
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542&token=&lang=zh_CN">
@ -1904,5 +1906,25 @@ public class WeixinProxy {
return cardApi.setCardSelfConsumeCell(cardId, isOpen); return cardApi.setCardSelfConsumeCell(cardId, isOpen);
} }
/**
* 创建卡券二维码 开发者可调用该接口生成一张卡券二维码供用户扫码后添加卡券到卡包
*
* @param expireSeconds
* 指定二维码的有效时间范围是60 ~ 1800秒不填默认为365天有效
* @param cardQRs
* 二维码参数:二维码领取单张卡券/多张卡券
* @return 二维码结果对象
* @see com.foxinmy.weixin4j.model.qr.QRResult
* @see com.foxinmy.weixin4j.model.qr.QRParameter
* @see com.foxinmy.weixin4j.mp.api.CardApi
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025062&token=&lang=zh_CN">投放卡券</a>
* @throws WeixinException
*/
public QRResult createCardQR(Integer expireSeconds, CardQR... cardQRs)
throws WeixinException {
return cardApi.createCardQR(expireSeconds, cardQRs);
}
public final static String VERSION = "1.7.0"; public final static String VERSION = "1.7.0";
} }

View File

@ -1,13 +1,21 @@
package com.foxinmy.weixin4j.mp.api; package com.foxinmy.weixin4j.mp.api;
import java.io.IOException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.card.CardCoupon; import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.card.CardCoupons;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.weixin.ApiResult; import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse; import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token; import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.card.CardCoupon;
import com.foxinmy.weixin4j.model.card.CardCoupons;
import com.foxinmy.weixin4j.model.card.CardQR;
import com.foxinmy.weixin4j.model.qr.QRParameter;
import com.foxinmy.weixin4j.model.qr.QRResult;
import com.foxinmy.weixin4j.token.TokenManager; import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.util.IOUtil;
/** /**
* 卡券API * 卡券API
@ -111,4 +119,41 @@ public class CardApi extends MpApi {
token.getAccessToken()), params.toJSONString()); token.getAccessToken()), params.toJSONString());
return response.getAsResult(); return response.getAsResult();
} }
/**
* 创建卡券二维码 开发者可调用该接口生成一张卡券二维码供用户扫码后添加卡券到卡包
*
* @param expireSeconds
* 指定二维码的有效时间范围是60 ~ 1800秒不填默认为365天有效
* @param cardQRs
* 二维码参数:二维码领取单张卡券/多张卡券
* @return 二维码结果对象
* @see com.foxinmy.weixin4j.model.qr.QRResult
* @see com.foxinmy.weixin4j.model.qr.QRParameter
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025062&token=&lang=zh_CN">投放卡券</a>
* @throws WeixinException
*/
public QRResult createCardQR(Integer expireSeconds, CardQR... cardQRs)
throws WeixinException {
QRParameter parameter = QRParameter.createCardCouponQR(expireSeconds,
cardQRs);
Token token = tokenManager.getCache();
String qr_uri = getRequestUri("card_qr_ticket_uri");
WeixinResponse response = weixinExecutor.post(
String.format(qr_uri, token.getAccessToken()),
JSON.toJSONString(parameter));
QRResult result = response.getAsObject(new TypeReference<QRResult>() {
});
qr_uri = String.format(getRequestUri("qr_image_uri"),
result.getTicket());
response = weixinExecutor.get(qr_uri);
result.setShowUrl(qr_uri);
try {
result.setContent(IOUtil.toByteArray(response.getBody()));
} catch (IOException e) {
throw new WeixinException(e);
}
return result;
}
} }

View File

@ -1,13 +1,14 @@
package com.foxinmy.weixin4j.mp.api; package com.foxinmy.weixin4j.mp.api;
import java.io.IOException; import java.io.IOException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse; import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token; import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.QRParameter; import com.foxinmy.weixin4j.model.qr.QRParameter;
import com.foxinmy.weixin4j.mp.model.QRResult; import com.foxinmy.weixin4j.model.qr.QRResult;
import com.foxinmy.weixin4j.token.TokenManager; import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.util.IOUtil; import com.foxinmy.weixin4j.util.IOUtil;
@ -34,8 +35,8 @@ public class QrApi extends MpApi {
* 二维码参数 * 二维码参数
* @return 二维码结果对象 * @return 二维码结果对象
* @throws WeixinException * @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.QRResult * @see com.foxinmy.weixin4j.model.qr.QRResult
* @see com.foxinmy.weixin4j.mp.model.QRParameter * @see com.foxinmy.weixin4j.model.qr.QRParameter
* @see <a * @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542&token=&lang=zh_CN">生成二维码</a> * href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542&token=&lang=zh_CN">生成二维码</a>
*/ */
@ -44,12 +45,13 @@ public class QrApi extends MpApi {
String qr_uri = getRequestUri("qr_ticket_uri"); String qr_uri = getRequestUri("qr_ticket_uri");
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(
String.format(qr_uri, token.getAccessToken()), String.format(qr_uri, token.getAccessToken()),
parameter.getContent()); JSON.toJSONString(parameter));
QRResult result = response.getAsObject(new TypeReference<QRResult>() { QRResult result = response.getAsObject(new TypeReference<QRResult>() {
}); });
qr_uri = getRequestUri("qr_image_uri"); qr_uri = String.format(getRequestUri("qr_image_uri"),
response = weixinExecutor result.getTicket());
.get(String.format(qr_uri, result.getTicket())); response = weixinExecutor.get(qr_uri);
result.setShowUrl(qr_uri);
try { try {
result.setContent(IOUtil.toByteArray(response.getBody())); result.setContent(IOUtil.toByteArray(response.getBody()));
} catch (IOException e) { } catch (IOException e) {

View File

@ -186,6 +186,8 @@ card_create_uri={api_base_url}/card/create?access_token=%s
card_paycell_uri={api_base_url}/card/paycell/set?access_token=%s card_paycell_uri={api_base_url}/card/paycell/set?access_token=%s
# \u8bbe\u7f6e\u81ea\u52a9\u6838\u9500\u63a5\u53e3 # \u8bbe\u7f6e\u81ea\u52a9\u6838\u9500\u63a5\u53e3
card_selfconsumecell_uri={api_base_url}/card/selfconsumecell/set?access_token=%s card_selfconsumecell_uri={api_base_url}/card/selfconsumecell/set?access_token=%s
# \u521b\u5efa\u5361\u5238\u4e8c\u7ef4\u7801\u63a5\u53e3
card_qr_ticket_uri={api_base_url}/card/qrcode/create?access_token=%s
# \u4f7f\u7528\u6388\u6743\u7801\u6362\u53d6\u516c\u4f17\u53f7\u7684\u63a5\u53e3\u8c03\u7528\u51ed\u636e\u548c\u6388\u6743\u4fe1\u606f # \u4f7f\u7528\u6388\u6743\u7801\u6362\u53d6\u516c\u4f17\u53f7\u7684\u63a5\u53e3\u8c03\u7528\u51ed\u636e\u548c\u6388\u6743\u4fe1\u606f
component_exchange_authorizer_uri={api_cgi_url}/component/api_query_auth?component_access_token=%s component_exchange_authorizer_uri={api_cgi_url}/component/api_query_auth?component_access_token=%s

View File

@ -1,139 +0,0 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import com.foxinmy.weixin4j.type.QRType;
/**
* 二维码参数对象
* <p>
* 目前有2种类型的二维码,分别是临时二维码和永久二维码,前者有过期时间,最大为1800秒,但能够生成较多数量,后者无过期时间,数量较少(目前参数只支持1--
* 100000)
* </p>
*
* @className QRParameter
* @author jinyu(foxinmy@gmail.com)
* @date 2014年4月8日
* @since JDK 1.6
* @see #createPermanenceInt(int) 创建整型永久二维码
* @see #createPermanenceStr(String) 创建字符串型永久二维码
* @see #createTemporary(long) 创建整型默认30秒有效期临时二维码
* @see #createTemporary(int, long) 创建整型有效期临时二维码
*/
public class QRParameter implements Serializable {
private static final long serialVersionUID = 6611187606558274253L;
private static final int DEFAULT_TEMPORARY_EXPIRE_SECONDS = 30;
/**
* 临时二维码的有效时间, 最大不超过2592000即30天此字段如果不填则默认有效期为30秒
*/
private int expireSeconds;
/**
* 二维码类型
*
* @see com.foxinmy.weixin4j.type.QRType
*/
private QRType qrType;
/**
* 场景值I 根据qrType参数而定
*/
private String sceneValue;
private QRParameter() {
}
public int getExpireSeconds() {
return expireSeconds;
}
public QRType getQrType() {
return qrType;
}
public String getSceneValue() {
return sceneValue;
}
private String content;
public String getContent() {
return content;
}
/**
* 创建临时二维码
*
* @param expireSeconds
* 有效时间
* @param sceneValue
* 二维码的场景值 <font color="red">临时二维码最大值为无符号32位非0整型</font>
* @return 二维码参数
*/
public static QRParameter createTemporary(int expireSeconds, long sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_SCENE;
qr.expireSeconds = expireSeconds;
qr.sceneValue = Long.toString(sceneValue);
qr.content = String.format(
"{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_id\": %s}}}",
expireSeconds, QRType.QR_SCENE.name(), sceneValue);
return qr;
}
/**
* 创建临时二维码(默认有效期为30秒)
*
* @param sceneValue
* 二维码的场景值 <font color="red">临时二维码最大值为无符号32位非0整型</font>
* @return 二维码参数
*/
public static QRParameter createTemporary(long sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_SCENE;
qr.expireSeconds = DEFAULT_TEMPORARY_EXPIRE_SECONDS;
qr.sceneValue = Long.toString(sceneValue);
qr.content = String.format(
"{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_id\": %s}}}",
DEFAULT_TEMPORARY_EXPIRE_SECONDS, QRType.QR_SCENE.name(), sceneValue);
return qr;
}
/**
* 创建永久二维码(场景值为int)
*
* @param sceneValue
* 场景值 最大值为100000
* @return 二维码参数
*/
public static QRParameter createPermanenceInt(int sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_LIMIT_SCENE;
qr.sceneValue = Integer.toString(sceneValue);
qr.content = String.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_id\": %s}}}",
QRType.QR_LIMIT_SCENE.name(), sceneValue);
return qr;
}
/**
* 创建永久二维码(场景值为string)
*
* @param sceneValue
* 场景值
* @return 二维码参数
*/
public static QRParameter createPermanenceStr(String sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_LIMIT_STR_SCENE;
qr.sceneValue = sceneValue;
qr.content = String.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
QRType.QR_LIMIT_STR_SCENE, sceneValue);
return qr;
}
@Override
public String toString() {
return "QRParameter [expireSeconds=" + expireSeconds + ", qrType=" + qrType + ", sceneValue=" + sceneValue
+ "]";
}
}

View File

@ -7,10 +7,10 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.foxinmy.weixin4j.card.CardCoupon;
import com.foxinmy.weixin4j.card.CardCoupons;
import com.foxinmy.weixin4j.card.CouponBaseInfo;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.model.card.CardCoupon;
import com.foxinmy.weixin4j.model.card.CardCoupons;
import com.foxinmy.weixin4j.model.card.CouponBaseInfo;
import com.foxinmy.weixin4j.mp.api.CardApi; import com.foxinmy.weixin4j.mp.api.CardApi;
import com.foxinmy.weixin4j.type.card.CardCodeType; import com.foxinmy.weixin4j.type.card.CardCodeType;
import com.foxinmy.weixin4j.type.card.CardColor; import com.foxinmy.weixin4j.type.card.CardColor;

View File

@ -40,7 +40,7 @@ public class MenuTest extends TokenTest {
String domain = "http://wx.jdxg.doubimeizhi.com"; String domain = "http://wx.jdxg.doubimeizhi.com";
buttons.add(new Button("立即下单", domain, ButtonType.view)); buttons.add(new Button("立即下单", domain, ButtonType.view));
buttons.add(new Button("个人中心", domain + "/user", ButtonType.view)); //buttons.add(new Button("个人中心", domain + "/user", ButtonType.view));
Button button = new Button("小哥介绍", domain, ButtonType.view); Button button = new Button("小哥介绍", domain, ButtonType.view);
button.pushSub(new Button("小哥介绍", "http://x.eqxiu.com/s/89oy462U", button.pushSub(new Button("小哥介绍", "http://x.eqxiu.com/s/89oy462U",
@ -52,7 +52,7 @@ public class MenuTest extends TokenTest {
ButtonType.view)); ButtonType.view));
button.pushSub(new Button("服务流程", "FLOW", ButtonType.click)); button.pushSub(new Button("服务流程", "FLOW", ButtonType.click));
button.pushSub(new Button("在线客服", "KF", ButtonType.click)); button.pushSub(new Button("在线客服", "KF", ButtonType.click));
buttons.add(button); //buttons.add(button);
ApiResult result = menuApi.createMenu(buttons); ApiResult result = menuApi.createMenu(buttons);
Assert.assertEquals("0", result.getReturnCode()); Assert.assertEquals("0", result.getReturnCode());

View File

@ -7,9 +7,9 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.model.qr.QRParameter;
import com.foxinmy.weixin4j.model.qr.QRResult;
import com.foxinmy.weixin4j.mp.api.QrApi; import com.foxinmy.weixin4j.mp.api.QrApi;
import com.foxinmy.weixin4j.mp.model.QRParameter;
import com.foxinmy.weixin4j.mp.model.QRResult;
/** /**
* 二维码相关测试 * 二维码相关测试
@ -23,27 +23,27 @@ public class QRTest extends TokenTest {
private QrApi qrApi; private QrApi qrApi;
@Before @Before
public void init() { public void init() throws WeixinException {
qrApi = new QrApi(tokenManager); qrApi = new QrApi(tokenManager);
} }
@Test @Test
public void temp_qr() throws WeixinException, IOException { public void temp_qr() throws WeixinException, IOException {
QRResult result = qrApi.createQR(QRParameter QRResult result = qrApi.createQR(QRParameter.createTemporaryQR(1200,
.createTemporary(1200, 1200)); 1200L));
Assert.assertTrue(!result.getTicket().isEmpty()); Assert.assertTrue(!result.getTicket().isEmpty());
} }
@Test @Test
public void forever_qr_int() throws WeixinException, IOException { public void forever_qr_int() throws WeixinException, IOException {
QRResult result = qrApi.createQR(QRParameter.createPermanenceInt(2)); QRResult result = qrApi.createQR(QRParameter.createPermanenceQR(2));
Assert.assertTrue(!result.getTicket().isEmpty()); Assert.assertTrue(!result.getTicket().isEmpty());
} }
@Test @Test
public void forever_qr_str() throws WeixinException, IOException { public void forever_qr_str() throws WeixinException, IOException {
QRResult result = qrApi.createQR(QRParameter QRResult result = qrApi.createQR(QRParameter
.createPermanenceStr("1200中文")); .createPermanenceQR("1200中文"));
Assert.assertTrue(!result.getTicket().isEmpty()); Assert.assertTrue(!result.getTicket().isEmpty());
} }
} }

View File

@ -15,7 +15,8 @@ import com.foxinmy.weixin4j.type.TicketType;
* @className WeixinTicketCreator * @className WeixinTicketCreator
* @author jinyu(foxinmy@gmail.com) * @author jinyu(foxinmy@gmail.com)
* @date 2015年12月25日 * @date 2015年12月25日
* @since JDK 1.6 <a href= * @since JDK 1.6
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95" * "http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95"
* >JSTICKET</a> * >JSTICKET</a>
*/ */