@@ -1,4 +1,5 @@ | |||
*.class | |||
test-output | |||
# Mobile Tools for Java (J2ME) | |||
.mtj.tmp/ | |||
@@ -10,3 +11,9 @@ | |||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | |||
hs_err_pid* | |||
target | |||
.project | |||
.classpath | |||
src/test/resources/test-config.xml |
@@ -0,0 +1,66 @@ | |||
<?xml version="1.0"?> | |||
<project | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>chanjarster.weixin</groupId> | |||
<artifactId>weixin-java</artifactId> | |||
<version>1.0.0-SNAPSHOT</version> | |||
<name>WeiXin Java Toolset</name> | |||
<url>https://github.com/chanjarster/weixin-java</url> | |||
<properties> | |||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
<downloadJavadocs>true</downloadJavadocs> | |||
<downloadSources>true</downloadSources> | |||
</properties> | |||
<dependencies> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<version>4.11</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.apache.httpcomponents</groupId> | |||
<artifactId>fluent-hc</artifactId> | |||
<version>4.3.5</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>javax.xml.bind</groupId> | |||
<artifactId>jaxb-api</artifactId> | |||
<version>2.2.7</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.sun.xml.bind</groupId> | |||
<artifactId>jaxb-impl</artifactId> | |||
<version>2.2.7</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.apache.oltu.oauth2</groupId> | |||
<artifactId>org.apache.oltu.oauth2.client</artifactId> | |||
<version>1.0.0</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.code.gson</groupId> | |||
<artifactId>gson</artifactId> | |||
<version>2.2.2</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.testng</groupId> | |||
<artifactId>testng</artifactId> | |||
<version>6.8.7</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-all</artifactId> | |||
<version>1.9.5</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-lang3</artifactId> | |||
<version>3.1</version> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,21 @@ | |||
package chanjarster.weixin.exception; | |||
import chanjarster.weixin.in.WxError; | |||
public class WxErrorException extends Exception { | |||
private static final long serialVersionUID = -6357149550353160810L; | |||
private WxError error; | |||
public WxErrorException(WxError error) { | |||
super(error.toString()); | |||
this.error = error; | |||
} | |||
public WxError getError() { | |||
return error; | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
package chanjarster.weixin.in; | |||
import chanjarster.weixin.util.WxGsonBuilder; | |||
public class WxAccessToken { | |||
private String access_token; | |||
private int expires_in; | |||
public String getAccess_token() { | |||
return access_token; | |||
} | |||
public void setAccess_token(String access_token) { | |||
this.access_token = access_token; | |||
} | |||
public int getExpires_in() { | |||
return expires_in; | |||
} | |||
public void setExpires_in(int expires_in) { | |||
this.expires_in = expires_in; | |||
} | |||
public static WxAccessToken fromJson(String json) { | |||
return WxGsonBuilder.create().fromJson(json, WxAccessToken.class); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
package chanjarster.weixin.in; | |||
import chanjarster.weixin.util.WxGsonBuilder; | |||
public class WxError { | |||
private int errcode; | |||
private String errmsg; | |||
public int getErrcode() { | |||
return errcode; | |||
} | |||
public void setErrcode(int errcode) { | |||
this.errcode = errcode; | |||
} | |||
public String getErrmsg() { | |||
return errmsg; | |||
} | |||
public void setErrmsg(String errmsg) { | |||
this.errmsg = errmsg; | |||
} | |||
public static WxError fromJson(String json) { | |||
return WxGsonBuilder.create().fromJson(json, WxError.class); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ errcode=" + errcode + ", errmsg=" + errmsg + "}"; | |||
} | |||
} |
@@ -0,0 +1,124 @@ | |||
package chanjarster.weixin.out; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import chanjarster.weixin.util.WxGsonBuilder; | |||
/** | |||
* 回复给用户的客服消息 | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxCustomMessage { | |||
protected String touser; | |||
protected String msgtype; | |||
protected String content; | |||
protected String media_id; | |||
protected String thumb_media_id; | |||
protected String title; | |||
protected String description; | |||
protected String musicurl; | |||
protected String hqmusicurl; | |||
protected List<WxArticle> articles = new ArrayList<WxArticle>(); | |||
public String getTouser() { | |||
return touser; | |||
} | |||
public void setTouser(String touser) { | |||
this.touser = touser; | |||
} | |||
public String getMsgtype() { | |||
return msgtype; | |||
} | |||
public void setMsgtype(String msgtype) { | |||
this.msgtype = msgtype; | |||
} | |||
public String getContent() { | |||
return content; | |||
} | |||
public void setContent(String content) { | |||
this.content = content; | |||
} | |||
public String getMedia_id() { | |||
return media_id; | |||
} | |||
public void setMedia_id(String media_id) { | |||
this.media_id = media_id; | |||
} | |||
public String getThumb_media_id() { | |||
return thumb_media_id; | |||
} | |||
public void setThumb_media_id(String thumb_media_id) { | |||
this.thumb_media_id = thumb_media_id; | |||
} | |||
public String getTitle() { | |||
return title; | |||
} | |||
public void setTitle(String title) { | |||
this.title = title; | |||
} | |||
public String getDescription() { | |||
return description; | |||
} | |||
public void setDescription(String description) { | |||
this.description = description; | |||
} | |||
public String getMusicurl() { | |||
return musicurl; | |||
} | |||
public void setMusicurl(String musicurl) { | |||
this.musicurl = musicurl; | |||
} | |||
public String getHqmusicurl() { | |||
return hqmusicurl; | |||
} | |||
public void setHqmusicurl(String hqmusicurl) { | |||
this.hqmusicurl = hqmusicurl; | |||
} | |||
public List<WxArticle> getArticles() { | |||
return articles; | |||
} | |||
public void setArticles(List<WxArticle> articles) { | |||
this.articles = articles; | |||
} | |||
public String toJson() { | |||
return WxGsonBuilder.INSTANCE.create().toJson(this); | |||
} | |||
public static class WxArticle { | |||
protected String title; | |||
protected String description; | |||
protected String url; | |||
protected String picurl; | |||
public String getTitle() { | |||
return title; | |||
} | |||
public void setTitle(String title) { | |||
this.title = title; | |||
} | |||
public String getDescription() { | |||
return description; | |||
} | |||
public void setDescription(String description) { | |||
this.description = description; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
public void setUrl(String url) { | |||
this.url = url; | |||
} | |||
public String getPicurl() { | |||
return picurl; | |||
} | |||
public void setPicurl(String picurl) { | |||
this.picurl = picurl; | |||
} | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
package chanjarster.weixin.out; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* 公众号菜单 | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMenu { | |||
protected List<WxMenuButton> button = new ArrayList<WxMenuButton>(); | |||
public List<WxMenuButton> getButton() { | |||
return button; | |||
} | |||
public void setButton(List<WxMenuButton> button) { | |||
this.button = button; | |||
} | |||
public static class WxMenuButton { | |||
protected String type; | |||
protected String name; | |||
protected String key; | |||
protected String url; | |||
protected List<WxMenuButton> sub_button = new ArrayList<WxMenuButton>(); | |||
public String getType() { | |||
return type; | |||
} | |||
public void setType(String type) { | |||
this.type = type; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
} | |||
public String getKey() { | |||
return key; | |||
} | |||
public void setKey(String key) { | |||
this.key = key; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
public void setUrl(String url) { | |||
this.url = url; | |||
} | |||
public List<WxMenuButton> getSub_button() { | |||
return sub_button; | |||
} | |||
public void setSub_button(List<WxMenuButton> sub_button) { | |||
this.sub_button = sub_button; | |||
} | |||
} | |||
} |
@@ -0,0 +1,418 @@ | |||
package chanjarster.weixin.out; | |||
import javax.xml.bind.JAXBException; | |||
import javax.xml.bind.annotation.XmlAccessType; | |||
import javax.xml.bind.annotation.XmlAccessorType; | |||
import javax.xml.bind.annotation.XmlElement; | |||
import javax.xml.bind.annotation.XmlRegistry; | |||
import javax.xml.bind.annotation.XmlRootElement; | |||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||
import chanjarster.weixin.util.AdapterCDATA; | |||
import chanjarster.weixin.util.XmlTransformer; | |||
/** | |||
* <pre> | |||
* 微信推送过来的消息,同时也是同步回复给用户的消息 | |||
* 相关字段的解释看微信开发者文档: | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收普通消息 | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收事件推送 | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收语音识别结果 | |||
* </pre> | |||
* @author chanjarster | |||
* | |||
*/ | |||
@XmlRootElement(name = "xml") | |||
@XmlAccessorType(XmlAccessType.FIELD) | |||
public class WxUserMessage { | |||
/////////////////////// | |||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | |||
/////////////////////// | |||
@XmlElement(name="ToUserName") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String ToUserName; | |||
@XmlElement(name="FromUserName") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String FromUserName; | |||
@XmlElement(name="CreateTime") | |||
private Long CreateTime; | |||
@XmlElement(name="MsgType") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String MsgType; | |||
@XmlElement(name="Content") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Content; | |||
@XmlElement(name="MsgId") | |||
private Long MsgId; | |||
@XmlElement(name="PicUrl") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String PicUrl; | |||
@XmlElement(name="MediaId") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String MediaId; | |||
@XmlElement(name="Format") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Format; | |||
@XmlElement(name="ThumbMediaId") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String ThumbMediaId; | |||
@XmlElement(name="Location_X") | |||
private Double Location_X; | |||
@XmlElement(name="Location_Y") | |||
private Double Location_Y; | |||
@XmlElement(name="Scale") | |||
private Double Scale; | |||
@XmlElement(name="Label") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Label; | |||
@XmlElement(name="Title") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Title; | |||
@XmlElement(name="Description") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Description; | |||
@XmlElement(name="Url") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Url; | |||
@XmlElement(name="Event") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Event; | |||
@XmlElement(name="EventKey") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String EventKey; | |||
@XmlElement(name="Ticket") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Ticket; | |||
@XmlElement(name="Latitude") | |||
private Double Latitude; | |||
@XmlElement(name="Longitude") | |||
private Double Longitude; | |||
@XmlElement(name="Precision") | |||
private Double Precision; | |||
@XmlElement(name="Recognition") | |||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||
private String Recognition; | |||
public String getToUserName() { | |||
return ToUserName; | |||
} | |||
public void setToUserName(String toUserName) { | |||
ToUserName = toUserName; | |||
} | |||
public Long getCreateTime() { | |||
return CreateTime; | |||
} | |||
public void setCreateTime(Long createTime) { | |||
CreateTime = createTime; | |||
} | |||
public String getMsgType() { | |||
return MsgType; | |||
} | |||
public void setMsgType(String msgType) { | |||
MsgType = msgType; | |||
} | |||
public String getContent() { | |||
return Content; | |||
} | |||
public void setContent(String content) { | |||
Content = content; | |||
} | |||
public Long getMsgId() { | |||
return MsgId; | |||
} | |||
public void setMsgId(Long msgId) { | |||
MsgId = msgId; | |||
} | |||
public String getPicUrl() { | |||
return PicUrl; | |||
} | |||
public void setPicUrl(String picUrl) { | |||
PicUrl = picUrl; | |||
} | |||
public String getMediaId() { | |||
return MediaId; | |||
} | |||
public void setMediaId(String mediaId) { | |||
MediaId = mediaId; | |||
} | |||
public String getFormat() { | |||
return Format; | |||
} | |||
public void setFormat(String format) { | |||
Format = format; | |||
} | |||
public String getThumbMediaId() { | |||
return ThumbMediaId; | |||
} | |||
public void setThumbMediaId(String thumbMediaId) { | |||
ThumbMediaId = thumbMediaId; | |||
} | |||
public Double getLocation_X() { | |||
return Location_X; | |||
} | |||
public void setLocation_X(Double location_X) { | |||
Location_X = location_X; | |||
} | |||
public Double getLocation_Y() { | |||
return Location_Y; | |||
} | |||
public void setLocation_Y(Double location_Y) { | |||
Location_Y = location_Y; | |||
} | |||
public Double getScale() { | |||
return Scale; | |||
} | |||
public void setScale(Double scale) { | |||
Scale = scale; | |||
} | |||
public String getLabel() { | |||
return Label; | |||
} | |||
public void setLabel(String label) { | |||
Label = label; | |||
} | |||
public String getTitle() { | |||
return Title; | |||
} | |||
public void setTitle(String title) { | |||
Title = title; | |||
} | |||
public String getDescription() { | |||
return Description; | |||
} | |||
public void setDescription(String description) { | |||
Description = description; | |||
} | |||
public String getUrl() { | |||
return Url; | |||
} | |||
public void setUrl(String url) { | |||
Url = url; | |||
} | |||
public String getEvent() { | |||
return Event; | |||
} | |||
public void setEvent(String event) { | |||
Event = event; | |||
} | |||
public String getEventKey() { | |||
return EventKey; | |||
} | |||
public void setEventKey(String eventKey) { | |||
EventKey = eventKey; | |||
} | |||
public String getTicket() { | |||
return Ticket; | |||
} | |||
public void setTicket(String ticket) { | |||
Ticket = ticket; | |||
} | |||
public Double getLatitude() { | |||
return Latitude; | |||
} | |||
public void setLatitude(Double latitude) { | |||
Latitude = latitude; | |||
} | |||
public Double getLongitude() { | |||
return Longitude; | |||
} | |||
public void setLongitude(Double longitude) { | |||
Longitude = longitude; | |||
} | |||
public Double getPrecision() { | |||
return Precision; | |||
} | |||
public void setPrecision(Double precision) { | |||
Precision = precision; | |||
} | |||
public String getRecognition() { | |||
return Recognition; | |||
} | |||
public void setRecognition(String recognition) { | |||
Recognition = recognition; | |||
} | |||
public String getFromUserName() { | |||
return FromUserName; | |||
} | |||
public void setFromUserName(String fromUserName) { | |||
FromUserName = fromUserName; | |||
} | |||
public String toXml() { | |||
try { | |||
return XmlTransformer.toXml(WxUserMessage.class, this); | |||
} catch (JAXBException e) { | |||
e.printStackTrace(); | |||
} | |||
return ""; | |||
} | |||
public static WxUserMessage fromXml(String xml) { | |||
try { | |||
return XmlTransformer.fromXml(WxUserMessage.class, xml); | |||
} catch (JAXBException e) { | |||
e.printStackTrace(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public int hashCode() { | |||
final int prime = 31; | |||
int result = 1; | |||
result = prime * result + ((Content == null) ? 0 : Content.hashCode()); | |||
result = prime * result + ((CreateTime == null) ? 0 : CreateTime.hashCode()); | |||
result = prime * result + ((Description == null) ? 0 : Description.hashCode()); | |||
result = prime * result + ((Event == null) ? 0 : Event.hashCode()); | |||
result = prime * result + ((EventKey == null) ? 0 : EventKey.hashCode()); | |||
result = prime * result + ((Format == null) ? 0 : Format.hashCode()); | |||
result = prime * result + ((FromUserName == null) ? 0 : FromUserName.hashCode()); | |||
result = prime * result + ((Label == null) ? 0 : Label.hashCode()); | |||
result = prime * result + ((Latitude == null) ? 0 : Latitude.hashCode()); | |||
result = prime * result + ((Location_X == null) ? 0 : Location_X.hashCode()); | |||
result = prime * result + ((Location_Y == null) ? 0 : Location_Y.hashCode()); | |||
result = prime * result + ((Longitude == null) ? 0 : Longitude.hashCode()); | |||
result = prime * result + ((MediaId == null) ? 0 : MediaId.hashCode()); | |||
result = prime * result + ((MsgId == null) ? 0 : MsgId.hashCode()); | |||
result = prime * result + ((MsgType == null) ? 0 : MsgType.hashCode()); | |||
result = prime * result + ((PicUrl == null) ? 0 : PicUrl.hashCode()); | |||
result = prime * result + ((Precision == null) ? 0 : Precision.hashCode()); | |||
result = prime * result + ((Recognition == null) ? 0 : Recognition.hashCode()); | |||
result = prime * result + ((Scale == null) ? 0 : Scale.hashCode()); | |||
result = prime * result + ((ThumbMediaId == null) ? 0 : ThumbMediaId.hashCode()); | |||
result = prime * result + ((Ticket == null) ? 0 : Ticket.hashCode()); | |||
result = prime * result + ((Title == null) ? 0 : Title.hashCode()); | |||
result = prime * result + ((ToUserName == null) ? 0 : ToUserName.hashCode()); | |||
result = prime * result + ((Url == null) ? 0 : Url.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; | |||
WxUserMessage other = (WxUserMessage) obj; | |||
if (Content == null) { | |||
if (other.Content != null) return false; | |||
} else if (!Content.equals(other.Content)) return false; | |||
if (CreateTime == null) { | |||
if (other.CreateTime != null) return false; | |||
} else if (!CreateTime.equals(other.CreateTime)) return false; | |||
if (Description == null) { | |||
if (other.Description != null) return false; | |||
} else if (!Description.equals(other.Description)) return false; | |||
if (Event == null) { | |||
if (other.Event != null) return false; | |||
} else if (!Event.equals(other.Event)) return false; | |||
if (EventKey == null) { | |||
if (other.EventKey != null) return false; | |||
} else if (!EventKey.equals(other.EventKey)) return false; | |||
if (Format == null) { | |||
if (other.Format != null) return false; | |||
} else if (!Format.equals(other.Format)) return false; | |||
if (FromUserName == null) { | |||
if (other.FromUserName != null) return false; | |||
} else if (!FromUserName.equals(other.FromUserName)) return false; | |||
if (Label == null) { | |||
if (other.Label != null) return false; | |||
} else if (!Label.equals(other.Label)) return false; | |||
if (Latitude == null) { | |||
if (other.Latitude != null) return false; | |||
} else if (!Latitude.equals(other.Latitude)) return false; | |||
if (Location_X == null) { | |||
if (other.Location_X != null) return false; | |||
} else if (!Location_X.equals(other.Location_X)) return false; | |||
if (Location_Y == null) { | |||
if (other.Location_Y != null) return false; | |||
} else if (!Location_Y.equals(other.Location_Y)) return false; | |||
if (Longitude == null) { | |||
if (other.Longitude != null) return false; | |||
} else if (!Longitude.equals(other.Longitude)) return false; | |||
if (MediaId == null) { | |||
if (other.MediaId != null) return false; | |||
} else if (!MediaId.equals(other.MediaId)) return false; | |||
if (MsgId == null) { | |||
if (other.MsgId != null) return false; | |||
} else if (!MsgId.equals(other.MsgId)) return false; | |||
if (MsgType == null) { | |||
if (other.MsgType != null) return false; | |||
} else if (!MsgType.equals(other.MsgType)) return false; | |||
if (PicUrl == null) { | |||
if (other.PicUrl != null) return false; | |||
} else if (!PicUrl.equals(other.PicUrl)) return false; | |||
if (Precision == null) { | |||
if (other.Precision != null) return false; | |||
} else if (!Precision.equals(other.Precision)) return false; | |||
if (Recognition == null) { | |||
if (other.Recognition != null) return false; | |||
} else if (!Recognition.equals(other.Recognition)) return false; | |||
if (Scale == null) { | |||
if (other.Scale != null) return false; | |||
} else if (!Scale.equals(other.Scale)) return false; | |||
if (ThumbMediaId == null) { | |||
if (other.ThumbMediaId != null) return false; | |||
} else if (!ThumbMediaId.equals(other.ThumbMediaId)) return false; | |||
if (Ticket == null) { | |||
if (other.Ticket != null) return false; | |||
} else if (!Ticket.equals(other.Ticket)) return false; | |||
if (Title == null) { | |||
if (other.Title != null) return false; | |||
} else if (!Title.equals(other.Title)) return false; | |||
if (ToUserName == null) { | |||
if (other.ToUserName != null) return false; | |||
} else if (!ToUserName.equals(other.ToUserName)) return false; | |||
if (Url == null) { | |||
if (other.Url != null) return false; | |||
} else if (!Url.equals(other.Url)) return false; | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
package chanjarster.weixin.service; | |||
public interface WxConfigProvider { | |||
public void updateAccessToken(String accessToken, Integer expiresIn); | |||
public String getAccessToken(); | |||
public String getAppId(); | |||
public String getSecret(); | |||
public String getToken(); | |||
} |
@@ -0,0 +1,16 @@ | |||
package chanjarster.weixin.service; | |||
import java.util.Map; | |||
import chanjarster.weixin.out.WxUserMessage; | |||
/** | |||
* 处理微信推送消息的处理器 | |||
* @author chanjarster | |||
* | |||
*/ | |||
public interface WxMessageHandler { | |||
public void handle(WxUserMessage wxMessage, Map<String, Object> context); | |||
} |
@@ -0,0 +1,22 @@ | |||
package chanjarster.weixin.service; | |||
import java.util.Map; | |||
import chanjarster.weixin.out.WxUserMessage; | |||
/** | |||
* 微信消息拦截器,可以用来做一些验证 | |||
* @author qianjia | |||
* | |||
*/ | |||
public interface WxMessageInterceptor { | |||
/** | |||
* 拦截微信消息 | |||
* @param wxMessage | |||
* @return true代表OK,false代表不OK | |||
*/ | |||
public boolean intercept(WxUserMessage wxMessage, Map<String, Object> context); | |||
} |
@@ -0,0 +1,192 @@ | |||
package chanjarster.weixin.service; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import chanjarster.weixin.out.WxUserMessage; | |||
/** | |||
* 微信消息路由器,通过代码化的配置,把来自微信的消息交给某个的handler处理 | |||
* @author qianjia | |||
* | |||
*/ | |||
public class WxMessageRouter { | |||
private List<Rule> rules = new ArrayList<Rule>(); | |||
/** | |||
* 开始一个新的Route规则 | |||
* @return | |||
*/ | |||
public Rule start() { | |||
return new Rule(this); | |||
} | |||
/** | |||
* 处理微信消息 | |||
* @param wxMessage | |||
*/ | |||
public void route(WxUserMessage wxMessage) { | |||
for (Rule rule : rules) { | |||
boolean doNext = rule.service(wxMessage); | |||
if (!doNext) { | |||
break; | |||
} | |||
} | |||
} | |||
public static class Rule { | |||
private final WxMessageRouter routerBuilder; | |||
private String msgType; | |||
private String event; | |||
private String eventKey; | |||
private String content; | |||
private boolean forward = false; | |||
private List<WxMessageHandler> handlers = new ArrayList<WxMessageHandler>(); | |||
private List<WxMessageInterceptor> interceptors = new ArrayList<WxMessageInterceptor>(); | |||
protected Rule(WxMessageRouter routerBuilder) { | |||
this.routerBuilder = routerBuilder; | |||
} | |||
/** | |||
* 如果msgType等于某值 | |||
* @param msgType | |||
* @return | |||
*/ | |||
public Rule msgType(String msgType) { | |||
this.msgType = msgType; | |||
return this; | |||
} | |||
/** | |||
* 如果event等于某值 | |||
* @param event | |||
* @return | |||
*/ | |||
public Rule event(String event) { | |||
this.event = event; | |||
return this; | |||
} | |||
/** | |||
* 如果eventKey等于某值 | |||
* @param eventKey | |||
* @return | |||
*/ | |||
public Rule eventKey(String eventKey) { | |||
this.eventKey = eventKey; | |||
return this; | |||
} | |||
/** | |||
* 如果content等于某值 | |||
* @param content | |||
* @return | |||
*/ | |||
public Rule content(String content) { | |||
this.content = content; | |||
return this; | |||
} | |||
/** | |||
* 如果本规则命中,在执行完handler后,还会接着给后面的Rule执行 | |||
* @return | |||
*/ | |||
public Rule forward() { | |||
this.forward = true; | |||
return this; | |||
} | |||
/** | |||
* 添加interceptor | |||
* @param interceptor | |||
* @param otherInterceptors | |||
* @return | |||
*/ | |||
public Rule interceptor(WxMessageInterceptor interceptor, WxMessageInterceptor... otherInterceptors) { | |||
this.interceptors.add(interceptor); | |||
if (otherInterceptors != null && otherInterceptors.length > 0) { | |||
for (WxMessageInterceptor i : otherInterceptors) { | |||
this.interceptors.add(i); | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* 添加handler | |||
* @param handler | |||
* @return | |||
*/ | |||
public Rule handler(WxMessageHandler handler, WxMessageHandler... otherHandlers) { | |||
this.handlers.add(handler); | |||
if (otherHandlers != null && otherHandlers.length > 0) { | |||
for (WxMessageHandler i : otherHandlers) { | |||
this.handlers.add(i); | |||
} | |||
} | |||
return this; | |||
} | |||
/** | |||
* 规则结束 | |||
* @return | |||
*/ | |||
public WxMessageRouter end() { | |||
this.routerBuilder.rules.add(this); | |||
return this.routerBuilder; | |||
} | |||
protected boolean test(WxUserMessage wxMessage) { | |||
return | |||
(this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) | |||
&& | |||
(this.event == null || this.event.equals(wxMessage.getEvent())) | |||
&& | |||
(this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) | |||
&& | |||
(this.content == null || this.content.equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) | |||
; | |||
} | |||
/** | |||
* 处理微信推送过来的消息 | |||
* @param wxMessage | |||
* @return true 代表继续执行别的router,false 代表停止执行别的router | |||
*/ | |||
protected boolean service(WxUserMessage wxMessage) { | |||
// 如果不匹配本规则,那么接着执行后面的Rule | |||
if (!test(wxMessage)) { | |||
return true; | |||
} | |||
Map<String, Object> context = new HashMap<String, Object>(); | |||
// 如果拦截器不通过 | |||
for (WxMessageInterceptor interceptor : this.interceptors) { | |||
if (!interceptor.intercept(wxMessage, context)) { | |||
return this.forward; | |||
} | |||
} | |||
// 交给handler处理 | |||
for (WxMessageHandler interceptor : this.handlers) { | |||
interceptor.handle(wxMessage, context); | |||
} | |||
return this.forward; | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
package chanjarster.weixin.service; | |||
public class WxMsgType { | |||
public static final String TEXT = "text"; | |||
public static final String IMAGE = "image"; | |||
public static final String VOICE = "voice"; | |||
public static final String MUSIC = "music"; | |||
public static final String VIDEO = "video"; | |||
public static final String NEWS = "news"; | |||
public static final String LOCATION = "location"; | |||
public static final String LINK = "link"; | |||
} |
@@ -0,0 +1,23 @@ | |||
package chanjarster.weixin.service; | |||
import chanjarster.weixin.exception.WxErrorException; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import chanjarster.weixin.out.WxMenu; | |||
/** | |||
* 微信相关的常量 | |||
*/ | |||
public interface WxService { | |||
public void refreshAccessToken() throws WxErrorException; | |||
public String sendCustomMessage(WxCustomMessage message) throws WxErrorException; | |||
public String createMenu(WxMenu menu) throws WxErrorException; | |||
public String deleteMenu() throws WxErrorException; | |||
public WxMenu getMenu() throws WxErrorException; | |||
public void setWxConfigProvider(WxConfigProvider wxConfigProvider); | |||
} |
@@ -0,0 +1,179 @@ | |||
package chanjarster.weixin.service; | |||
import java.io.IOException; | |||
import java.nio.charset.Charset; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
import org.apache.http.client.methods.HttpPost; | |||
import org.apache.http.entity.StringEntity; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClients; | |||
import chanjarster.weixin.exception.WxErrorException; | |||
import chanjarster.weixin.in.WxAccessToken; | |||
import chanjarster.weixin.in.WxError; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import chanjarster.weixin.out.WxMenu; | |||
public class WxServiceImpl implements WxService { | |||
/** | |||
* 全局的是否正在刷新Access Token的flag | |||
* true: 正在刷新 | |||
* false: 没有刷新 | |||
*/ | |||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); | |||
protected static final CloseableHttpClient httpclient = HttpClients.createDefault(); | |||
protected static final Charset UTF8 = Charset.forName("UTF-8"); | |||
protected WxConfigProvider wxConfigProvider; | |||
/** | |||
* 获得access_token | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public void refreshAccessToken() throws WxErrorException { | |||
if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | |||
try { | |||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | |||
+ "&appid=" + wxConfigProvider.getAppId() | |||
+ "&secret=" + wxConfigProvider.getSecret() | |||
; | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||
wxConfigProvider.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in()); | |||
} catch (ClientProtocolException e) { | |||
throw new RuntimeException(e); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} finally { | |||
GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false); | |||
} | |||
} else { | |||
// 每隔100ms检查一下是否刷新完毕了 | |||
while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) { | |||
try { | |||
Thread.sleep(100); | |||
} catch (InterruptedException e) { | |||
} | |||
} | |||
// 刷新完毕了,就没他什么事儿了 | |||
} | |||
} | |||
/** | |||
* 发送客服消息 | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息 | |||
* @param message | |||
* @throws WxErrorException | |||
*/ | |||
public String sendCustomMessage(WxCustomMessage message) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | |||
return post(url, message.toJson()); | |||
} | |||
protected String post(String uri, String data) throws WxErrorException { | |||
return execute("POST", uri, data); | |||
} | |||
protected String get(String uri, String data) throws WxErrorException { | |||
return execute("GET", uri, data); | |||
} | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
* @param request | |||
* @return 微信服务端返回的结果 | |||
* @throws WxErrorException | |||
*/ | |||
protected String execute(String method, String uri, String data) throws WxErrorException { | |||
if (StringUtils.isBlank(wxConfigProvider.getAccessToken())) { | |||
refreshAccessToken(); | |||
} | |||
String accessToken = wxConfigProvider.getAccessToken(); | |||
String uriWithAccessToken = uri; | |||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | |||
try { | |||
String resultContent = null; | |||
if ("POST".equals(method)) { | |||
HttpPost httpPost = new HttpPost(uriWithAccessToken); | |||
StringEntity entity = new StringEntity(data, UTF8); | |||
httpPost.setEntity(entity); | |||
CloseableHttpResponse response = httpclient.execute(httpPost); | |||
resultContent = new BasicResponseHandler().handleResponse(response); | |||
} else if ("GET".equals(method)) { | |||
HttpGet httpGet = new HttpGet(uriWithAccessToken); | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
resultContent = new BasicResponseHandler().handleResponse(response); | |||
} | |||
WxError error = WxError.fromJson(resultContent); | |||
/* | |||
* 关于微信返回错误码 详情请看 http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 | |||
* 40001 微信图片不对 | |||
* 42001 access_token超时 | |||
*/ | |||
if (error.getErrcode() == 42001 || error.getErrcode() == 40001) { | |||
refreshAccessToken(); | |||
return execute(method, uri, data); | |||
} | |||
if (error.getErrcode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
return resultContent; | |||
} catch (ClientProtocolException e) { | |||
throw new RuntimeException(e); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
/** | |||
* | |||
* @param menu | |||
* @throws WxErrorException | |||
*/ | |||
public String createMenu(WxMenu menu) throws WxErrorException { | |||
// TODO | |||
return null; | |||
} | |||
/** | |||
* | |||
* @throws WxErrorException | |||
*/ | |||
public String deleteMenu() throws WxErrorException { | |||
// TODO | |||
return null; | |||
} | |||
/** | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public WxMenu getMenu() throws WxErrorException { | |||
// TODO | |||
return null; | |||
} | |||
public void setWxConfigProvider(WxConfigProvider wxConfigProvider) { | |||
this.wxConfigProvider = wxConfigProvider; | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
package chanjarster.weixin.util; | |||
import javax.xml.bind.annotation.adapters.XmlAdapter; | |||
/** | |||
* | |||
* http://stackoverflow.com/questions/14193944/jaxb-marshalling-unmarshalling-with-cdata | |||
* | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class AdapterCDATA extends XmlAdapter<String, String> { | |||
@Override | |||
public String marshal(String arg0) throws Exception { | |||
return "<![CDATA[" + arg0 + "]]>"; | |||
} | |||
@Override | |||
public String unmarshal(String arg0) throws Exception { | |||
return arg0; | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
/* | |||
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||
* | |||
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||
* arose from modification of the original source, or other redistribution of this source | |||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
*/ | |||
package chanjarster.weixin.util; | |||
import java.lang.reflect.Type; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import chanjarster.weixin.out.WxCustomMessage.WxArticle; | |||
import chanjarster.weixin.service.WxMsgType; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
/** | |||
* | |||
* @author qianjia | |||
* | |||
*/ | |||
public class WxCustomMessageGsonAdapter implements JsonSerializer<WxCustomMessage> { | |||
public JsonElement serialize(WxCustomMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||
JsonObject messageJson = new JsonObject(); | |||
messageJson.addProperty("touser", message.getTouser()); | |||
messageJson.addProperty("msgtype", message.getMsgtype()); | |||
if (WxMsgType.TEXT.equals(message.getMsgtype())) { | |||
JsonObject text = new JsonObject(); | |||
text.addProperty("content", message.getContent()); | |||
messageJson.add("text", text); | |||
} | |||
if (WxMsgType.IMAGE.equals(message.getMsgtype())) { | |||
JsonObject image = new JsonObject(); | |||
image.addProperty("media_id", message.getMedia_id()); | |||
messageJson.add("image", image); | |||
} | |||
if (WxMsgType.VOICE.equals(message.getMsgtype())) { | |||
JsonObject voice = new JsonObject(); | |||
voice.addProperty("media_id", message.getMedia_id()); | |||
messageJson.add("voice", voice); | |||
} | |||
if (WxMsgType.VIDEO.equals(message.getMsgtype())) { | |||
JsonObject video = new JsonObject(); | |||
video.addProperty("media_id", message.getMedia_id()); | |||
video.addProperty("thumb_media_id", message.getThumb_media_id()); | |||
video.addProperty("title", message.getTitle()); | |||
video.addProperty("description", message.getDescription()); | |||
messageJson.add("video", video); | |||
} | |||
if (WxMsgType.MUSIC.equals(message.getMsgtype())) { | |||
JsonObject music = new JsonObject(); | |||
music.addProperty("title", message.getTitle()); | |||
music.addProperty("description", message.getDescription()); | |||
music.addProperty("thumb_media_id", message.getThumb_media_id()); | |||
music.addProperty("musicurl", message.getMusicurl()); | |||
music.addProperty("hqmusicurl", message.getHqmusicurl()); | |||
messageJson.add("music", music); | |||
} | |||
if (WxMsgType.NEWS.equals(message.getMsgtype())) { | |||
JsonArray articleJsonArray = new JsonArray(); | |||
for (WxArticle article : message.getArticles()) { | |||
JsonObject articleJson = new JsonObject(); | |||
articleJson.addProperty("title", article.getTitle()); | |||
articleJson.addProperty("description", article.getDescription()); | |||
articleJson.addProperty("url", article.getUrl()); | |||
articleJson.addProperty("picurl", article.getPicurl()); | |||
articleJsonArray.add(articleJson); | |||
} | |||
messageJson.add("articles", articleJsonArray); | |||
} | |||
return messageJson; | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
package chanjarster.weixin.util; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
public class WxGsonBuilder { | |||
public static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
static { | |||
INSTANCE.disableHtmlEscaping(); | |||
INSTANCE.registerTypeAdapter(WxCustomMessage.class, new WxCustomMessageGsonAdapter()); | |||
} | |||
public static Gson create() { | |||
return INSTANCE.create(); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||
* | |||
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||
* arose from modification of the original source, or other redistribution of this source | |||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
*/ | |||
package chanjarster.weixin.util; | |||
import java.lang.reflect.Type; | |||
import chanjarster.weixin.out.WxMenu; | |||
import chanjarster.weixin.out.WxMenu.WxMenuButton; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
/** | |||
* | |||
* @author qianjia | |||
* | |||
*/ | |||
public class WxMenuGsonAdapter implements JsonSerializer<WxMenu> { | |||
public JsonElement serialize(WxMenu menu, Type typeOfSrc, JsonSerializationContext context) { | |||
JsonObject json = new JsonObject(); | |||
JsonArray buttonArray = new JsonArray(); | |||
for (WxMenuButton button : menu.getButton()) { | |||
JsonObject buttonJson = serialize(button); | |||
buttonArray.add(buttonJson); | |||
} | |||
json.add("button", buttonArray); | |||
return json; | |||
} | |||
protected JsonObject serialize(WxMenuButton button) { | |||
JsonObject buttonJson = new JsonObject(); | |||
buttonJson.addProperty("name", button.getName()); | |||
// TODO 其他字段 | |||
if (button.getSub_button() == null || button.getSub_button().size() == 0) { | |||
JsonArray buttonArray = new JsonArray(); | |||
for (WxMenuButton sub_button : button.getSub_button()) { | |||
buttonArray.add(serialize(sub_button)); | |||
} | |||
buttonJson.add("sub_button", buttonArray); | |||
} | |||
return buttonJson; | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
package chanjarster.weixin.util; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.StringReader; | |||
import java.io.StringWriter; | |||
import java.io.Writer; | |||
import javax.xml.bind.JAXBContext; | |||
import javax.xml.bind.JAXBException; | |||
import javax.xml.bind.Marshaller; | |||
import javax.xml.bind.Unmarshaller; | |||
import com.sun.xml.bind.marshaller.CharacterEscapeHandler; | |||
public class XmlTransformer { | |||
/** | |||
* xml -> pojo | |||
* @param clazz | |||
* @param object | |||
* @return | |||
* @throws JAXBException | |||
*/ | |||
public static <T> T fromXml(Class<T> clazz, String xml) throws JAXBException { | |||
JAXBContext context = JAXBContext.newInstance(clazz); | |||
Unmarshaller um = context.createUnmarshaller(); | |||
T object = (T) um.unmarshal(new StringReader(xml)); | |||
return object; | |||
} | |||
public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | |||
JAXBContext context = JAXBContext.newInstance(clazz); | |||
Unmarshaller um = context.createUnmarshaller(); | |||
T object = (T) um.unmarshal(is); | |||
return object; | |||
} | |||
/** | |||
* pojo -> xml | |||
* @param clazz | |||
* @return | |||
* @throws JAXBException | |||
*/ | |||
public static <T> String toXml(Class<T> clazz, T object) throws JAXBException { | |||
StringWriter stringWriter = new StringWriter(); | |||
toXml(clazz, object, stringWriter); | |||
return stringWriter.getBuffer().toString(); | |||
} | |||
public static <T> void toXml(Class<T> clazz, T object, Writer writer) throws JAXBException { | |||
JAXBContext context = JAXBContext.newInstance(clazz); | |||
Marshaller m = context.createMarshaller(); | |||
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); | |||
m.setProperty(CharacterEscapeHandler.class.getName(), characterUnescapeHandler); | |||
m.marshal(object, writer); | |||
} | |||
protected static CharacterEscapeHandler characterUnescapeHandler = new CharacterUnescapeHandler(); | |||
protected static class CharacterUnescapeHandler implements CharacterEscapeHandler { | |||
public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException { | |||
writer.write(ac, i, j); | |||
} | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
package chanjarster.weixin.in; | |||
import org.testng.Assert; | |||
import org.testng.annotations.Test; | |||
import chanjarster.weixin.in.WxError; | |||
@Test | |||
public class WxErrorTest { | |||
public void testFromJson() { | |||
String json = "{ \"errcode\": 40003, \"errmsg\": \"invalid openid\" }"; | |||
WxError wxError = WxError.fromJson(json); | |||
Assert.assertTrue(wxError.getErrcode() == 40003); | |||
Assert.assertEquals(wxError.getErrmsg(), "invalid openid"); | |||
} | |||
public void testFromBadJson1() { | |||
String json = "{ \"errcode\": 40003, \"errmsg\": \"invalid openid\", \"media_id\": \"12323423dsfafsf232f\" }"; | |||
WxError wxError = WxError.fromJson(json); | |||
Assert.assertTrue(wxError.getErrcode() == 40003); | |||
Assert.assertEquals(wxError.getErrmsg(), "invalid openid"); | |||
} | |||
public void testFromBadJson2() { | |||
String json = "{\"access_token\":\"ACCESS_TOKEN\",\"expires_in\":7200}"; | |||
WxError wxError = WxError.fromJson(json); | |||
Assert.assertTrue(wxError.getErrcode() == 0); | |||
Assert.assertEquals(wxError.getErrmsg(), null); | |||
} | |||
} |
@@ -0,0 +1,83 @@ | |||
package chanjarster.weixin.out; | |||
import org.testng.Assert; | |||
import org.testng.annotations.Test; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import chanjarster.weixin.out.WxCustomMessage.WxArticle; | |||
import chanjarster.weixin.service.WxMsgType; | |||
@Test | |||
public class WxCustomMessageTest { | |||
public void testTextReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.TEXT); | |||
reply.setContent("sfsfdsdf"); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); | |||
} | |||
public void testImageReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.IMAGE); | |||
reply.setMedia_id("MEDIA_ID"); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); | |||
} | |||
public void testVoiceReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.VOICE); | |||
reply.setMedia_id("MEDIA_ID"); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); | |||
} | |||
public void testVideoReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.VIDEO); | |||
reply.setMedia_id("MEDIA_ID"); | |||
reply.setThumb_media_id("MEDIA_ID"); | |||
reply.setTitle("TITLE"); | |||
reply.setDescription("DESCRIPTION"); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}"); | |||
} | |||
public void testMusicReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.MUSIC); | |||
reply.setThumb_media_id("MEDIA_ID"); | |||
reply.setDescription("DESCRIPTION"); | |||
reply.setTitle("TITLE"); | |||
reply.setMusicurl("MUSIC_URL"); | |||
reply.setHqmusicurl("HQ_MUSIC_URL"); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"music\",\"music\":{\"title\":\"TITLE\",\"description\":\"DESCRIPTION\",\"thumb_media_id\":\"MEDIA_ID\",\"musicurl\":\"MUSIC_URL\",\"hqmusicurl\":\"HQ_MUSIC_URL\"}}"); | |||
} | |||
public void testNewsReply() { | |||
WxCustomMessage reply = new WxCustomMessage(); | |||
reply.setTouser("OPENID"); | |||
reply.setMsgtype(WxMsgType.NEWS); | |||
WxArticle article1 = new WxArticle(); | |||
article1.setUrl("URL"); | |||
article1.setPicurl("PIC_URL"); | |||
article1.setDescription("Is Really A Happy Day"); | |||
article1.setTitle("Happy Day"); | |||
reply.getArticles().add(article1); | |||
WxArticle article2 = new WxArticle(); | |||
article2.setUrl("URL"); | |||
article2.setPicurl("PIC_URL"); | |||
article2.setDescription("Is Really A Happy Day"); | |||
article2.setTitle("Happy Day"); | |||
reply.getArticles().add(article2); | |||
System.out.println(reply.toJson()); | |||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}"); | |||
} | |||
} |
@@ -0,0 +1,119 @@ | |||
package chanjarster.weixin.out; | |||
import org.testng.Assert; | |||
import org.testng.annotations.Test; | |||
import chanjarster.weixin.out.WxUserMessage; | |||
import chanjarster.weixin.service.WxMsgType; | |||
@Test | |||
public class WxUserMessageTest { | |||
public void testFromXml() { | |||
String xml = "<xml>" | |||
+ "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
+ "<FromUserName><![CDATA[fromUser]]></FromUserName> " | |||
+ "<CreateTime>1348831860</CreateTime>" | |||
+ "<MsgType><![CDATA[text]]></MsgType>" | |||
+ "<Content><![CDATA[this is a test]]></Content>" | |||
+ "<MsgId>1234567890123456</MsgId>" | |||
+ "<PicUrl><![CDATA[this is a url]]></PicUrl>" | |||
+ "<MediaId><![CDATA[media_id]]></MediaId>" | |||
+ "<Format><![CDATA[Format]]></Format>" | |||
+ "<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>" | |||
+ "<Location_X>23.134521</Location_X>" | |||
+ "<Location_Y>113.358803</Location_Y>" | |||
+ "<Scale>20</Scale>" | |||
+ "<Label><![CDATA[位置信息]]></Label>" | |||
+ "<Description><![CDATA[公众平台官网链接]]></Description>" | |||
+ "<Url><![CDATA[url]]></Url>" | |||
+ "<Title><![CDATA[公众平台官网链接]]></Title>" | |||
+ "<Event><![CDATA[subscribe]]></Event>" | |||
+ "<EventKey><![CDATA[qrscene_123123]]></EventKey>" | |||
+ "<Ticket><![CDATA[TICKET]]></Ticket>" | |||
+ "<Latitude>23.137466</Latitude>" | |||
+ "<Longitude>113.352425</Longitude>" | |||
+ "<Precision>119.385040</Precision>" | |||
+ "</xml>"; | |||
WxUserMessage wxMessage = WxUserMessage.fromXml(xml); | |||
Assert.assertEquals(wxMessage.getToUserName(), "toUser"); | |||
Assert.assertEquals(wxMessage.getFromUserName(), "fromUser"); | |||
Assert.assertEquals(wxMessage.getCreateTime(), new Long(1348831860l)); | |||
Assert.assertEquals(wxMessage.getMsgType(), WxMsgType.TEXT); | |||
Assert.assertEquals(wxMessage.getContent(), "this is a test"); | |||
Assert.assertEquals(wxMessage.getMsgId(), new Long(1234567890123456l)); | |||
Assert.assertEquals(wxMessage.getPicUrl(), "this is a url"); | |||
Assert.assertEquals(wxMessage.getMediaId(), "media_id"); | |||
Assert.assertEquals(wxMessage.getFormat(), "Format"); | |||
Assert.assertEquals(wxMessage.getThumbMediaId(), "thumb_media_id"); | |||
Assert.assertEquals(wxMessage.getLocation_X(), new Double(23.134521d)); | |||
Assert.assertEquals(wxMessage.getLocation_Y(), new Double(113.358803d)); | |||
Assert.assertEquals(wxMessage.getScale(), new Double(20)); | |||
Assert.assertEquals(wxMessage.getLabel(), "位置信息"); | |||
Assert.assertEquals(wxMessage.getDescription(), "公众平台官网链接"); | |||
Assert.assertEquals(wxMessage.getUrl(), "url"); | |||
Assert.assertEquals(wxMessage.getTitle(), "公众平台官网链接"); | |||
Assert.assertEquals(wxMessage.getEvent(), "subscribe"); | |||
Assert.assertEquals(wxMessage.getEventKey(), "qrscene_123123"); | |||
Assert.assertEquals(wxMessage.getTicket(), "TICKET"); | |||
Assert.assertEquals(wxMessage.getLatitude(), new Double(23.137466)); | |||
Assert.assertEquals(wxMessage.getLongitude(), new Double(113.352425)); | |||
Assert.assertEquals(wxMessage.getPrecision(), new Double(119.385040)); | |||
} | |||
public void testToXml() { | |||
WxUserMessage wxMessage = new WxUserMessage(); | |||
wxMessage.setToUserName("toUser"); | |||
wxMessage.setFromUserName("fromUser"); | |||
wxMessage.setCreateTime(new Long(1348831860l)); | |||
wxMessage.setMsgType(WxMsgType.TEXT); | |||
wxMessage.setContent("this is a test"); | |||
wxMessage.setMsgId(new Long(1234567890123456l)); | |||
wxMessage.setPicUrl("this is a url"); | |||
wxMessage.setMediaId("media_id"); | |||
wxMessage.setFormat("Format"); | |||
wxMessage.setThumbMediaId("thumb_media_id"); | |||
wxMessage.setLocation_X(new Double(23.134521d)); | |||
wxMessage.setLocation_Y(new Double(113.358803d)); | |||
wxMessage.setScale(new Double(20)); | |||
wxMessage.setLabel("位置信息"); | |||
wxMessage.setDescription("公众平台官网链接"); | |||
wxMessage.setUrl("url"); | |||
wxMessage.setTitle("公众平台官网链接"); | |||
wxMessage.setEvent("subscribe"); | |||
wxMessage.setEventKey("qrscene_123123"); | |||
wxMessage.setTicket("TICKET"); | |||
wxMessage.setLatitude(new Double(23.137466)); | |||
wxMessage.setLongitude(new Double(113.352425)); | |||
wxMessage.setPrecision(new Double(119.385040)); | |||
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" | |||
+ "<xml>\n" | |||
+ " <ToUserName><![CDATA[toUser]]></ToUserName>\n" | |||
+ " <FromUserName><![CDATA[fromUser]]></FromUserName>\n" | |||
+ " <CreateTime>1348831860</CreateTime>\n" | |||
+ " <MsgType><![CDATA[text]]></MsgType>\n" | |||
+ " <Content><![CDATA[this is a test]]></Content>\n" | |||
+ " <MsgId>1234567890123456</MsgId>\n" | |||
+ " <PicUrl><![CDATA[this is a url]]></PicUrl>\n" | |||
+ " <MediaId><![CDATA[media_id]]></MediaId>\n" | |||
+ " <Format><![CDATA[Format]]></Format>\n" | |||
+ " <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>\n" | |||
+ " <Location_X>23.134521</Location_X>\n" | |||
+ " <Location_Y>113.358803</Location_Y>\n" | |||
+ " <Scale>20.0</Scale>\n" | |||
+ " <Label><![CDATA[位置信息]]></Label>\n" | |||
+ " <Title><![CDATA[公众平台官网链接]]></Title>\n" | |||
+ " <Description><![CDATA[公众平台官网链接]]></Description>\n" | |||
+ " <Url><![CDATA[url]]></Url>\n" | |||
+ " <Event><![CDATA[subscribe]]></Event>\n" | |||
+ " <EventKey><![CDATA[qrscene_123123]]></EventKey>\n" | |||
+ " <Ticket><![CDATA[TICKET]]></Ticket>\n" | |||
+ " <Latitude>23.137466</Latitude>\n" | |||
+ " <Longitude>113.352425</Longitude>\n" | |||
+ " <Precision>119.38504</Precision>\n" | |||
+ "</xml>\n"; | |||
Assert.assertEquals(wxMessage.toXml(), xml); | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
package chanjarster.weixin.service; | |||
// TODO | |||
public class WxMessageRouterTest { | |||
} |
@@ -0,0 +1,125 @@ | |||
package chanjarster.weixin.service; | |||
import java.io.InputStream; | |||
import javax.xml.bind.JAXBException; | |||
import javax.xml.bind.annotation.XmlAccessType; | |||
import javax.xml.bind.annotation.XmlAccessorType; | |||
import javax.xml.bind.annotation.XmlRootElement; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Test; | |||
import chanjarster.weixin.exception.WxErrorException; | |||
import chanjarster.weixin.out.WxCustomMessage; | |||
import chanjarster.weixin.util.XmlTransformer; | |||
public class WxServiceTest { | |||
@Test(dataProvider = "configs") | |||
public void testRefreshAccessToken(WxConfigProvider config) throws WxErrorException { | |||
String before = config.getAccessToken(); | |||
WxService wxService = new WxServiceImpl(); | |||
wxService.setWxConfigProvider(config); | |||
wxService.refreshAccessToken(); | |||
String after = config.getAccessToken(); | |||
Assert.assertNotEquals(before, after); | |||
Assert.assertTrue(StringUtils.isNotBlank(after)); | |||
} | |||
@Test(dataProvider = "configs") | |||
public void sendCustomMessage(SimpleWxConfigProvider config) throws WxErrorException { | |||
WxService wxService = new WxServiceImpl(); | |||
wxService.setWxConfigProvider(config); | |||
WxCustomMessage message = new WxCustomMessage(); | |||
message.setMsgtype(WxMsgType.TEXT); | |||
message.setTouser(config.getOpenId()); | |||
message.setContent("欢迎使用教务系统微信公众号\n下面\n<a href=\"http://192.168.1.249:9180/eams-rc/login.action\">Hello World</a>"); | |||
wxService.sendCustomMessage(message); | |||
} | |||
/** | |||
* 返回新的access_token | |||
* @return | |||
* @throws JAXBException | |||
*/ | |||
@DataProvider(name = "configs") | |||
public Object[][] getConfig() throws JAXBException { | |||
/** | |||
* 将 src/test/resources/test-config.sample.xml 改成 test-config.xml 并设置appId, secret, 一个过期的accessToken | |||
*/ | |||
// 没有access_token | |||
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | |||
SimpleWxConfigProvider config1 = XmlTransformer.fromXml(SimpleWxConfigProvider.class, is1); | |||
return new Object[][] { | |||
new Object[] { | |||
config1 | |||
} | |||
}; | |||
} | |||
@XmlRootElement(name = "xml") | |||
@XmlAccessorType(XmlAccessType.FIELD) | |||
public static class SimpleWxConfigProvider implements WxConfigProvider { | |||
private String appId; | |||
private String secret; | |||
private String accessToken = ""; | |||
private Integer expiresIn; | |||
private String token; | |||
private String openId; | |||
public void updateAccessToken(String accessToken, Integer expiresIn) { | |||
this.accessToken = accessToken; | |||
this.expiresIn = expiresIn; | |||
} | |||
public String getAccessToken() { | |||
return accessToken; | |||
} | |||
public String getAppId() { | |||
return appId; | |||
} | |||
public String getSecret() { | |||
return secret; | |||
} | |||
public String getToken() { | |||
return token; | |||
} | |||
public void setAppId(String appId) { | |||
this.appId = appId; | |||
} | |||
public void setSecret(String secret) { | |||
this.secret = secret; | |||
} | |||
public void setToken(String token) { | |||
this.token = token; | |||
} | |||
public Integer getExpiresIn() { | |||
return expiresIn; | |||
} | |||
public void setExpiresIn(Integer expiresIn) { | |||
this.expiresIn = expiresIn; | |||
} | |||
public void setAccessToken(String accessToken) { | |||
this.accessToken = accessToken; | |||
} | |||
public String getOpenId() { | |||
return openId; | |||
} | |||
public void setOpenId(String openId) { | |||
this.openId = openId; | |||
} | |||
@Override | |||
public String toString() { | |||
return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken | |||
+ ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
<xml> | |||
<appId></appId> | |||
<secret></secret> | |||
<accessToken></accessToken> | |||
<expiresIn></expiresIn> | |||
<openId></openId> | |||
</xml> |