├── flow.png ├── src ├── test │ ├── resources │ │ └── dev.properties │ └── java │ │ └── me │ │ └── hao0 │ │ └── wechat │ │ ├── MyComponent.java │ │ ├── XmlWritersTest.java │ │ └── FuturesTest.java └── main │ └── java │ └── me │ └── hao0 │ └── wechat │ ├── core │ ├── AsyncFunction.java │ ├── Callback.java │ ├── Component.java │ ├── WechatBuilder.java │ ├── MenuBuilder.java │ ├── JsSdks.java │ ├── Menus.java │ ├── Bases.java │ ├── Wechat.java │ ├── QrCodes.java │ └── Users.java │ ├── model │ ├── base │ │ ├── AuthType.java │ │ ├── AccessToken.java │ │ └── AuthAccessToken.java │ ├── material │ │ ├── MaterialType.java │ │ ├── MaterialUploadType.java │ │ ├── NewsMaterial.java │ │ ├── NewsContent.java │ │ ├── CommonMaterial.java │ │ ├── PermMaterial.java │ │ ├── Material.java │ │ ├── TempMaterial.java │ │ ├── MaterialCount.java │ │ └── NewsContentItem.java │ ├── js │ │ ├── TicketType.java │ │ ├── Ticket.java │ │ └── Config.java │ ├── message │ │ ├── receive │ │ │ ├── event │ │ │ │ ├── RecvUnknownEvent.java │ │ │ │ ├── RecvUnSubscribeEvent.java │ │ │ │ ├── RecvEvent.java │ │ │ │ ├── RecvMenuEvent.java │ │ │ │ ├── RecvScanEvent.java │ │ │ │ ├── RecvSubscribeEvent.java │ │ │ │ ├── RecvTemplateSendJobFinishEvent.java │ │ │ │ ├── RecvLocationEvent.java │ │ │ │ └── RecvEventType.java │ │ │ ├── msg │ │ │ │ ├── RecvShortVideoMessage.java │ │ │ │ ├── RecvMsg.java │ │ │ │ ├── RecvTextMessage.java │ │ │ │ ├── RecvImageMessage.java │ │ │ │ ├── RecvVideoMessage.java │ │ │ │ ├── RecvLinkMessage.java │ │ │ │ ├── RecvVoiceMessage.java │ │ │ │ └── RecvLocationMessage.java │ │ │ ├── RecvMessageType.java │ │ │ └── RecvMessage.java │ │ ├── send │ │ │ ├── SendMessageScope.java │ │ │ ├── SendMessageType.java │ │ │ ├── TemplateField.java │ │ │ ├── SendPreviewMessage.java │ │ │ └── SendMessage.java │ │ └── resp │ │ │ ├── RespMessageType.java │ │ │ └── Article.java │ ├── qrcode │ │ ├── QrcodeType.java │ │ └── Qrcode.java │ ├── data │ │ ├── msg │ │ │ ├── MsgSendSummaryHour.java │ │ │ ├── MsgType.java │ │ │ ├── MsgSendDist.java │ │ │ └── MsgSendSummary.java │ │ ├── article │ │ │ ├── ArticleSummary.java │ │ │ ├── ArticleShareHour.java │ │ │ ├── ArticleShareScene.java │ │ │ ├── ArticleSource.java │ │ │ ├── ArticleTotalDetail.java │ │ │ ├── ArticleSummaryHour.java │ │ │ ├── ArticleDailySummary.java │ │ │ ├── ArticleShare.java │ │ │ ├── ArticleTotal.java │ │ │ └── CommonSummary.java │ │ ├── interfaces │ │ │ ├── InterfaceSummaryHour.java │ │ │ └── InterfaceSummary.java │ │ └── user │ │ │ ├── UserSource.java │ │ │ ├── UserCumulate.java │ │ │ └── UserSummary.java │ ├── customer │ │ ├── CsSession.java │ │ ├── UserSession.java │ │ ├── WaitingSession.java │ │ └── MsgRecord.java │ ├── user │ │ ├── Group.java │ │ ├── UserList.java │ │ ├── UserInfo.java │ │ └── User.java │ └── menu │ │ ├── Menu.java │ │ └── MenuType.java │ ├── serializer │ ├── DateDeserializer.java │ ├── UserSourceDeserializer.java │ ├── ArticleSourceDeserializer.java │ ├── MsgTypeDeserializer.java │ └── ArticleShareSceneDeserializer.java │ ├── loader │ ├── AccessTokenLoader.java │ ├── TicketLoader.java │ ├── DefaultAccessTokenLoader.java │ └── DefaultTicketLoader.java │ ├── exception │ ├── XmlException.java │ ├── EventException.java │ └── WechatException.java │ └── utils │ └── XmlWriters.java ├── alipay.png ├── wechat.png ├── .travis.yml ├── LICENSE.md ├── .gitignore ├── pom.xml └── README.md /flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaolin/wechat/HEAD/flow.png -------------------------------------------------------------------------------- /src/test/resources/dev.properties: -------------------------------------------------------------------------------- 1 | appId=xxyy 2 | appSecret=123456 -------------------------------------------------------------------------------- /alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaolin/wechat/HEAD/alipay.png -------------------------------------------------------------------------------- /wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihaolin/wechat/HEAD/wechat.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | - openjdk7 6 | 7 | script: mvn clean package -DskipTests -------------------------------------------------------------------------------- /src/test/java/me/hao0/wechat/MyComponent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat; 2 | 3 | import me.hao0.wechat.core.Component; 4 | 5 | /** 6 | * Author: haolin 7 | * Email: haolin.h0@gmail.com 8 | * Date: 18/11/15 9 | */ 10 | public class MyComponent extends Component { 11 | 12 | public String getAppId(){ 13 | return wechat.getAppId(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/AsyncFunction.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | /** 4 | * 异步执行函数 5 | * @param 范型结果 6 | * @since 1.4.0 7 | */ 8 | abstract class AsyncFunction { 9 | 10 | Callback cb; 11 | 12 | AsyncFunction(Callback cb){ 13 | this.cb = cb; 14 | } 15 | 16 | public abstract T execute() throws Exception; 17 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/base/AuthType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.base; 2 | 3 | /** 4 | * 授权类型 5 | */ 6 | public enum AuthType{ 7 | 8 | BASE("snsapi_base"), 9 | 10 | USER_INFO("snsapi_userinfo"); 11 | 12 | private String scope; 13 | 14 | private AuthType(String scope){ 15 | this.scope = scope; 16 | } 17 | 18 | public String scope(){ 19 | return scope; 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Callback.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | /** 4 | * Author: haolin 5 | * Email: haolin.h0@gmail.com 6 | * Date: 17/11/15 7 | * @since 1.4.0 8 | */ 9 | public interface Callback { 10 | 11 | /** 12 | * 成功时回调 13 | * @param t 结果类型 14 | */ 15 | void onSuccess(T t); 16 | 17 | /** 18 | * 失败时回调 19 | * @param e Exception 20 | */ 21 | void onFailure(Exception e); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/MaterialType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | /** 4 | * 素材类型 5 | */ 6 | public enum MaterialType { 7 | 8 | IMAGE("image"), 9 | 10 | VIDEO("video"), 11 | 12 | VOICE("voice"), 13 | 14 | NEWS("news"); 15 | 16 | private String value; 17 | 18 | private MaterialType(String value){ 19 | this.value = value; 20 | } 21 | 22 | public String value(){ 23 | return value; 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/js/TicketType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.js; 2 | 3 | /** 4 | * 临时凭证类型 5 | */ 6 | public enum TicketType { 7 | 8 | /** 9 | * 用于调用微信JSSDK的临时票据 10 | */ 11 | JSAPI("jsapi"), 12 | 13 | /** 14 | * 用于调用卡券相关接口的临时票据 15 | */ 16 | CARD("wx_card"); 17 | 18 | private String type; 19 | 20 | private TicketType(String type){ 21 | this.type = type; 22 | } 23 | 24 | public String type(){ 25 | return type; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvUnknownEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 未知事件(当解析到微信的新事件时) 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * @since 1.9.2 8 | */ 9 | public class RecvUnknownEvent extends RecvEvent { 10 | 11 | public RecvUnknownEvent(RecvEvent e){ 12 | super(e); 13 | this.eventType = e.eventType; 14 | } 15 | 16 | @Override 17 | public String getEventType() { 18 | return RecvEventType.UNKNOW.value(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvUnSubscribeEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 取消关注 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 9/11/15 8 | */ 9 | public class RecvUnSubscribeEvent extends RecvEvent { 10 | 11 | 12 | public RecvUnSubscribeEvent(RecvEvent e){ 13 | super(e); 14 | this.eventType = e.eventType; 15 | } 16 | 17 | @Override 18 | public String getEventType() { 19 | return RecvEventType.UN_SUBSCRIBE.value(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/MaterialUploadType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | /** 6 | * 素材上传类型 7 | */ 8 | public enum MaterialUploadType { 9 | 10 | IMAGE("image"), 11 | 12 | VIDEO("video"), 13 | 14 | VOICE("voice"), 15 | 16 | THUMB("thumb"); 17 | 18 | private String value; 19 | 20 | private MaterialUploadType(String value){ 21 | this.value = value; 22 | } 23 | 24 | @JsonValue 25 | public String value(){ 26 | return value; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/serializer/DateDeserializer.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.JsonDeserializer; 6 | import java.io.IOException; 7 | import java.util.Date; 8 | 9 | public class DateDeserializer extends JsonDeserializer { 10 | 11 | @Override 12 | public Date deserialize(JsonParser parser, DeserializationContext context) 13 | throws IOException { 14 | return new Date(parser.getIntValue() * 1000L); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/loader/AccessTokenLoader.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.loader; 2 | 3 | import me.hao0.wechat.model.base.AccessToken; 4 | 5 | /** 6 | * accessToken加载接口 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 10/11/15 10 | * @since 1.3.0 11 | */ 12 | public interface AccessTokenLoader { 13 | 14 | /** 15 | * 获取accessToken 16 | * @return accessToken,""或NULL会重新从微信服务器获取,并进行refresh 17 | */ 18 | String get(); 19 | 20 | /** 21 | * 刷新accessToken,实现时需要保存一段时间,以免频繁从微信服务器获取 22 | * @param token 从微信服务器获取AccessToken 23 | */ 24 | void refresh(AccessToken token); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/send/SendMessageScope.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.send; 2 | 3 | /** 4 | * 发送消息的范围 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 8/11/15 8 | */ 9 | public enum SendMessageScope { 10 | 11 | /** 12 | * 分组群发:【订阅号与服务号认证后均可用】 13 | */ 14 | GROUP(1, "分组群发"), 15 | 16 | /** 17 | * 按OpenId列表发: 订阅号不可用,服务号认证后可用 18 | */ 19 | OPEN_ID(2, "按OpenId列表发"); 20 | 21 | private Integer value; 22 | 23 | private String desc; 24 | 25 | private SendMessageScope(Integer value, String desc){ 26 | this.value = value; 27 | this.desc = desc; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvShortVideoMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 小视频消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvShortVideoMessage extends RecvVideoMessage { 12 | 13 | private static final long serialVersionUID = 4589295453710532536L; 14 | 15 | public RecvShortVideoMessage(RecvMsg m){ 16 | super(m); 17 | this.msgId = m.msgId; 18 | } 19 | 20 | @Override 21 | public String getMsgType() { 22 | return RecvMessageType.SHORT_VIDEO.value(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/loader/TicketLoader.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.loader; 2 | 3 | import me.hao0.wechat.model.js.Ticket; 4 | import me.hao0.wechat.model.js.TicketType; 5 | 6 | /** 7 | * 凭证加载器 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 15/11/15 11 | * @since 1.3.0 12 | */ 13 | public interface TicketLoader { 14 | 15 | /** 16 | * 获取Ticket 17 | * @param type ticket类型 18 | * @see me.hao0.wechat.model.js.TicketType 19 | * @return 有效的ticket,若返回""或null,则触发重新从微信请求Ticket的方法refresh 20 | */ 21 | String get(TicketType type); 22 | 23 | /** 24 | * 刷新Ticket 25 | * @param ticket 最新获取到的Ticket 26 | */ 27 | void refresh(Ticket ticket); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/qrcode/QrcodeType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.qrcode; 2 | 3 | /** 4 | * Author: haolin 5 | * Email: haolin.h0@gmail.com 6 | * Date: 9/11/15 7 | */ 8 | public enum QrcodeType { 9 | 10 | /** 11 | * 临时二维码,有过期时间,最长7天,604800s 12 | */ 13 | QR_SCENE("QR_SCENE"), 14 | 15 | /** 16 | * 永久二维码,最多100000个 17 | */ 18 | QR_LIMIT_SCENE("QR_LIMIT_SCENE"), 19 | 20 | /** 21 | * 永久二维码,字符串类型,长度限制为1到64 22 | */ 23 | QR_LIMIT_STR_SCENE("QR_LIMIT_STR_SCENE"); 24 | 25 | private String value; 26 | 27 | private QrcodeType(String value){ 28 | this.value = value; 29 | } 30 | 31 | public String value(){ 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/NewsMaterial.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | /** 4 | * 图文素材类 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 12/11/15 8 | */ 9 | public class NewsMaterial extends Material { 10 | 11 | private static final long serialVersionUID = 8483540691949616866L; 12 | 13 | private NewsContent content; 14 | 15 | public NewsContent getContent() { 16 | return content; 17 | } 18 | 19 | public void setContent(NewsContent content) { 20 | this.content = content; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "NewsMaterial{" + 26 | "content=" + content + 27 | "} " + super.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/send/SendMessageType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.send; 2 | 3 | /** 4 | * 发送消息的类型 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 8/11/15 8 | */ 9 | public enum SendMessageType { 10 | 11 | TEXT("text", "文本消息"), 12 | IMAGE("image", "图片消息"), 13 | VOICE("voice", "语音消息"), 14 | VIDEO("mpvideo", "视频消息"), 15 | NEWS("mpnews", "图文消息"), 16 | CARD("wxcard", "卡券消息"); 17 | 18 | private String value; 19 | 20 | private String desc; 21 | 22 | private SendMessageType(String value, String desc){ 23 | this.value = value; 24 | this.desc = desc; 25 | } 26 | 27 | public String value(){ 28 | return value; 29 | } 30 | 31 | public String desc(){ 32 | return desc; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/exception/XmlException.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.exception; 2 | 3 | /** 4 | * 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 5/11/15 8 | */ 9 | public class XmlException extends RuntimeException { 10 | 11 | public XmlException() { 12 | super(); 13 | } 14 | 15 | public XmlException(String message) { 16 | super(message); 17 | } 18 | 19 | public XmlException(String message, Throwable cause) { 20 | super(message, cause); 21 | } 22 | 23 | public XmlException(Throwable cause) { 24 | super(cause); 25 | } 26 | 27 | protected XmlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/serializer/UserSourceDeserializer.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | import me.hao0.wechat.model.data.user.UserSource; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 20/11/15 14 | */ 15 | public class UserSourceDeserializer extends JsonDeserializer { 16 | 17 | @Override 18 | public UserSource deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { 19 | return UserSource.from(parser.getIntValue()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/msg/MsgSendSummaryHour.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.msg; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Author: haolin 7 | * Email: haolin.h0@gmail.com 8 | * Date: 20/11/15 9 | */ 10 | public class MsgSendSummaryHour extends MsgSendSummary { 11 | 12 | private static final long serialVersionUID = -1978665674344648838L; 13 | 14 | @JsonProperty("ref_hour") 15 | private Integer hour; 16 | 17 | public Integer getHour() { 18 | return hour; 19 | } 20 | 21 | public void setHour(Integer hour) { 22 | this.hour = hour; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "MsgSendSummaryHour{" + 28 | "hour=" + hour + 29 | "} " + super.toString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/serializer/ArticleSourceDeserializer.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | import me.hao0.wechat.model.data.article.ArticleSource; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 20/11/15 14 | */ 15 | public class ArticleSourceDeserializer extends JsonDeserializer { 16 | 17 | @Override 18 | public ArticleSource deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { 19 | return ArticleSource.from(parser.getIntValue()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleSummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * 图文统计数据 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public class ArticleSummary extends CommonSummary { 12 | 13 | private static final long serialVersionUID = -5668724641461918373L; 14 | 15 | @JsonProperty("ref_date") 16 | private String date; 17 | 18 | public String getDate() { 19 | return date; 20 | } 21 | 22 | public void setDate(String date) { 23 | this.date = date; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "ArticleSummary{" + 29 | "date='" + date + '\'' + 30 | "} " + super.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/resp/RespMessageType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.resp; 2 | 3 | /** 4 | * 响应微信服务器的消息类型 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 8/11/15 8 | */ 9 | public enum RespMessageType { 10 | 11 | TEXT("text", "文本消息"), 12 | IMAGE("image", "图片消息"), 13 | VOICE("voice", "语音消息"), 14 | VIDEO("video", "视频消息"), 15 | MUSIC("music", "音乐消息"), 16 | NEWS("news", "图文消息"), 17 | CS("transfer_customer_service", "转发客服消息"); 18 | 19 | private String value; 20 | 21 | private String desc; 22 | 23 | private RespMessageType(String value, String desc){ 24 | this.value = value; 25 | this.desc = desc; 26 | } 27 | 28 | public String value(){ 29 | return value; 30 | } 31 | 32 | public String desc(){ 33 | return desc; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/serializer/MsgTypeDeserializer.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | import me.hao0.wechat.model.data.msg.MsgType; 8 | import me.hao0.wechat.model.data.user.UserSource; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * Author: haolin 14 | * Email: haolin.h0@gmail.com 15 | * Date: 20/11/15 16 | */ 17 | public class MsgTypeDeserializer extends JsonDeserializer { 18 | 19 | @Override 20 | public MsgType deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { 21 | return MsgType.from(parser.getIntValue()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/serializer/ArticleShareSceneDeserializer.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | import me.hao0.wechat.model.data.article.ArticleShareScene; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 20/11/15 14 | */ 15 | public class ArticleShareSceneDeserializer extends JsonDeserializer { 16 | 17 | @Override 18 | public ArticleShareScene deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { 19 | return ArticleShareScene.from(parser.getIntValue()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/loader/DefaultAccessTokenLoader.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.loader; 2 | 3 | import com.google.common.base.Strings; 4 | import me.hao0.wechat.model.base.AccessToken; 5 | 6 | /** 7 | * 一个内存式AccessToken加载器(生产环境不推荐使用) 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 10/11/15 11 | * @since 1.3.0 12 | */ 13 | public class DefaultAccessTokenLoader implements AccessTokenLoader { 14 | 15 | private volatile AccessToken validToken; 16 | 17 | @Override 18 | public String get() { 19 | return (validToken == null 20 | || Strings.isNullOrEmpty(validToken.getAccessToken()) 21 | || System.currentTimeMillis() > validToken.getExpiredAt()) ? null : validToken.getAccessToken(); 22 | } 23 | 24 | @Override 25 | public void refresh(AccessToken token) { 26 | validToken = token; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/interfaces/InterfaceSummaryHour.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.interfaces; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Author: haolin 7 | * Email: haolin.h0@gmail.com 8 | * Date: 20/11/15 9 | */ 10 | public class InterfaceSummaryHour extends InterfaceSummary { 11 | 12 | private static final long serialVersionUID = 7437638903080411923L; 13 | 14 | /** 15 | * 数据的小时 16 | */ 17 | @JsonProperty("ref_hour") 18 | private Integer hour; 19 | 20 | public Integer getHour() { 21 | return hour; 22 | } 23 | 24 | public void setHour(Integer hour) { 25 | this.hour = hour; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "InterfaceSummaryHour{" + 31 | "hour=" + hour + 32 | "} " + super.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/NewsContent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | /** 9 | * 图文素材类 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 12/11/15 13 | */ 14 | public class NewsContent implements Serializable { 15 | 16 | private static final long serialVersionUID = 8483540691949616866L; 17 | 18 | @JsonProperty("news_item") 19 | private List items; 20 | 21 | public List getItems() { 22 | return items; 23 | } 24 | 25 | public void setItems(List items) { 26 | this.items = items; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "NewsContent{" + 32 | "items=" + items + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/exception/EventException.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.exception; 2 | 3 | /** 4 | * 微信事件异常,接收微信消息时有可能抛出 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 5/11/15 8 | * @see me.hao0.wechat.model.message.receive.event.RecvEventType#from 9 | * @since 1.4.0 10 | */ 11 | public class EventException extends RuntimeException { 12 | 13 | public EventException() { 14 | super(); 15 | } 16 | 17 | public EventException(String message) { 18 | super(message); 19 | } 20 | 21 | public EventException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public EventException(Throwable cause) { 26 | super(cause); 27 | } 28 | 29 | protected EventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 30 | super(message, cause, enableSuppression, writableStackTrace); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleShareHour.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * 图文分享转发每日分时数据 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public class ArticleShareHour extends ArticleShare { 12 | 13 | private static final long serialVersionUID = 8015340884882046L; 14 | 15 | /** 16 | * 小时: 包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时 17 | */ 18 | @JsonProperty("ref_hour") 19 | private Integer hour; 20 | 21 | public Integer getHour() { 22 | return hour; 23 | } 24 | 25 | public void setHour(Integer hour) { 26 | this.hour = hour; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "ArticleShareHour{" + 32 | "hour=" + hour + 33 | "} " + super.toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvMsg.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessage; 4 | 5 | /** 6 | * 接收微信服务器的普通消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvMsg extends RecvMessage { 12 | 13 | private static final long serialVersionUID = 8863935279441026878L; 14 | 15 | /** 16 | * 消息ID 17 | */ 18 | protected Long msgId; 19 | 20 | public Long getMsgId() { 21 | return msgId; 22 | } 23 | 24 | public void setMsgId(Long msgId) { 25 | this.msgId = msgId; 26 | } 27 | 28 | public RecvMsg(){} 29 | 30 | public RecvMsg(RecvMessage e){ 31 | super(e); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "RecvMsg{" + 37 | "msgId=" + msgId + 38 | "} " + super.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/loader/DefaultTicketLoader.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.loader; 2 | 3 | import com.google.common.base.Strings; 4 | import me.hao0.wechat.model.js.Ticket; 5 | import me.hao0.wechat.model.js.TicketType; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * 一个内存式Ticket加载器(生产环境不推荐使用) 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 15/11/15 14 | * @since 1.3.0 15 | */ 16 | public class DefaultTicketLoader implements TicketLoader { 17 | 18 | private final Map tickets = new ConcurrentHashMap<>(); 19 | 20 | @Override 21 | public String get(TicketType type) { 22 | Ticket t = tickets.get(type); 23 | return (t == null 24 | || Strings.isNullOrEmpty(t.getTicket()) 25 | || System.currentTimeMillis() > t.getExpireAt()) ? null : t.getTicket(); 26 | } 27 | 28 | @Override 29 | public void refresh(Ticket ticket) { 30 | tickets.put(ticket.getType(), ticket); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleShareScene.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | /** 6 | * 分享的场景 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public enum ArticleShareScene { 12 | 13 | /** 14 | * 未知 15 | */ 16 | UNKNOWN(0), 17 | 18 | /** 19 | * 好友转发 20 | */ 21 | FRIEND(1), 22 | 23 | /** 24 | * 朋友圈 25 | */ 26 | TIMELINE(2), 27 | 28 | /** 29 | * 腾讯微博 30 | */ 31 | WEIBO(3); 32 | 33 | 34 | private Integer value; 35 | 36 | private ArticleShareScene(Integer scope){ 37 | this.value = scope; 38 | } 39 | 40 | public Integer value(){ 41 | return value; 42 | } 43 | 44 | public static ArticleShareScene from(Integer s){ 45 | for (ArticleShareScene source : ArticleShareScene.values()){ 46 | if (Objects.equal(source.value(), s)){ 47 | return source; 48 | } 49 | } 50 | return UNKNOWN; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/CommonMaterial.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | /** 4 | * 普通素材类(图片,视频,语音) 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 12/11/15 8 | */ 9 | public class CommonMaterial extends Material { 10 | 11 | private static final long serialVersionUID = -6397218526074423143L; 12 | 13 | /** 14 | * 文件名称 15 | */ 16 | private String name; 17 | 18 | /** 19 | * 图文页的URL,或者,当获取的列表是图片素材列表时,该字段是图片的URL 20 | */ 21 | private String url; 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | public String getUrl() { 32 | return url; 33 | } 34 | 35 | public void setUrl(String url) { 36 | this.url = url; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "CommonMaterial{" + 42 | "name='" + name + '\'' + 43 | ", url='" + url + '\'' + 44 | "} " + super.toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvTextMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 文本消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvTextMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = -8070100690774814611L; 14 | 15 | /** 16 | * 文本内容 17 | */ 18 | private String content; 19 | 20 | public RecvTextMessage(RecvMsg m){ 21 | super(m); 22 | this.msgId = m.msgId; 23 | } 24 | 25 | public String getContent() { 26 | return content; 27 | } 28 | 29 | public void setContent(String content) { 30 | this.content = content; 31 | } 32 | 33 | @Override 34 | public String getMsgType() { 35 | return RecvMessageType.TEXT.value(); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "RecvTextMessage{" + 41 | "content='" + content + '\'' + 42 | "} " + super.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Being simple 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/msg/MsgType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.msg; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | /** 6 | * 分析消息类型 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public enum MsgType { 12 | 13 | /** 14 | * 未知 15 | */ 16 | UNKNOWN(0), 17 | 18 | /** 19 | * 文字 20 | */ 21 | TEXT(1), 22 | 23 | /** 24 | * 图片 25 | */ 26 | IMAGE(2), 27 | 28 | /** 29 | * 语音 30 | */ 31 | VOICE(3), 32 | 33 | /** 34 | * 视频 35 | */ 36 | VIDEO(4), 37 | 38 | /** 39 | * 第三方应用消息(链接消息) 40 | */ 41 | THIRD(6); 42 | 43 | private Integer value; 44 | 45 | private MsgType(Integer scope){ 46 | this.value = scope; 47 | } 48 | 49 | public Integer value(){ 50 | return value; 51 | } 52 | 53 | public static MsgType from(Integer s){ 54 | for (MsgType source : MsgType.values()){ 55 | if (Objects.equal(source.value(), s)){ 56 | return source; 57 | } 58 | } 59 | return UNKNOWN; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/PermMaterial.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 上传永久素材后的返回对象 9 | * Author: haolin 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 13/11/15 13 | */ 14 | public class PermMaterial implements Serializable { 15 | 16 | private static final long serialVersionUID = 4907386605074554874L; 17 | 18 | @JsonProperty("media_id") 19 | private String mediaId; 20 | 21 | private String url; 22 | 23 | public String getMediaId() { 24 | return mediaId; 25 | } 26 | 27 | public void setMediaId(String mediaId) { 28 | this.mediaId = mediaId; 29 | } 30 | 31 | public String getUrl() { 32 | return url; 33 | } 34 | 35 | public void setUrl(String url) { 36 | this.url = url; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "PermMaterial{" + 42 | "mediaId='" + mediaId + '\'' + 43 | ", url='" + url + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessage; 4 | import me.hao0.wechat.model.message.receive.RecvMessageType; 5 | 6 | /** 7 | * 接收微信服务器的事件消息 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 9/11/15 11 | */ 12 | public class RecvEvent extends RecvMessage { 13 | 14 | /** 15 | * 事件类型: 16 | * @see RecvEvent 17 | */ 18 | protected String eventType; 19 | 20 | public RecvEvent(){} 21 | 22 | public RecvEvent(RecvMessage e){ 23 | super(e); 24 | } 25 | 26 | public void setEventType(String eventType){ 27 | this.eventType = eventType; 28 | } 29 | 30 | public String getEventType(){ 31 | return this.eventType; 32 | } 33 | 34 | @Override 35 | public String getMsgType() { 36 | return RecvMessageType.EVENT.value(); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "RecvEvent{" + 42 | "eventType='" + eventType + '\'' + 43 | "} " + super.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/customer/CsSession.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.customer; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 客服的会话状态 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 7/11/15 11 | */ 12 | public class CsSession implements Serializable { 13 | 14 | private static final long serialVersionUID = -8380650779042961587L; 15 | 16 | /** 17 | * 用户openId 18 | */ 19 | private String openId; 20 | 21 | /** 22 | * 会话接入时间 23 | */ 24 | private Date createTime; 25 | 26 | public String getOpenId() { 27 | return openId; 28 | } 29 | 30 | public void setOpenId(String openId) { 31 | this.openId = openId; 32 | } 33 | 34 | public Date getCreateTime() { 35 | return createTime; 36 | } 37 | 38 | public void setCreateTime(Date createTime) { 39 | this.createTime = createTime; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "CsSessionStatus{" + 45 | "openId='" + openId + '\'' + 46 | ", createTime=" + createTime + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvMenuEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 菜单事件 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 9/11/15 8 | */ 9 | public class RecvMenuEvent extends RecvEvent { 10 | 11 | /** 12 | * eventType为CLICK时: 与自定义菜单接口中KEY值对应 13 | * eventType为VIEW时: 设置的跳转URL 14 | */ 15 | private String eventKey; 16 | 17 | public RecvMenuEvent(RecvEvent e){ 18 | super(e); 19 | this.eventType = e.eventType; 20 | } 21 | 22 | @Override 23 | public String getEventType() { 24 | if ("CLICK".equalsIgnoreCase(eventType)){ 25 | return RecvEventType.MENU_CLICK.value(); 26 | } 27 | return RecvEventType.MENU_VIEW.value(); 28 | } 29 | 30 | public String getEventKey() { 31 | return eventKey; 32 | } 33 | 34 | public void setEventKey(String eventKey) { 35 | this.eventKey = eventKey; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "RecvMenuEvent{" + 41 | ", eventKey='" + eventKey + '\'' + 42 | "} " + super.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/customer/UserSession.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.customer; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 用户会话状态 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 7/11/15 11 | */ 12 | public class UserSession implements Serializable { 13 | 14 | private static final long serialVersionUID = 2699187929516108564L; 15 | 16 | /** 17 | * 客服帐号(包含域名) 18 | */ 19 | private String kfAccount; 20 | 21 | /** 22 | * 会话结束时间 23 | */ 24 | private Date createTime; 25 | 26 | public String getKfAccount() { 27 | return kfAccount; 28 | } 29 | 30 | public void setKfAccount(String kfAccount) { 31 | this.kfAccount = kfAccount; 32 | } 33 | 34 | public Date getCreateTime() { 35 | return createTime; 36 | } 37 | 38 | public void setCreateTime(Date createTime) { 39 | this.createTime = createTime; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "SessionStatus{" + 45 | "kfAccount='" + kfAccount + '\'' + 46 | ", createTime=" + createTime + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/me/hao0/wechat/XmlWritersTest.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat; 2 | 3 | import me.hao0.wechat.utils.XmlWriters; 4 | import org.junit.Test; 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 6/11/15 11 | */ 12 | public class XmlWritersTest { 13 | 14 | @Test 15 | public void testOneLevel(){ 16 | XmlWriters xmlWriters = XmlWriters.create(); 17 | 18 | xmlWriters.element("ToUserName", "123456") 19 | .element("FromUserName", "me") 20 | .element("CreateTime", System.currentTimeMillis()) 21 | .element("MsgType", "transfer_customer_service"); 22 | 23 | assertNotNull(xmlWriters.build()); 24 | } 25 | 26 | @Test 27 | public void testMultiLevel(){ 28 | XmlWriters xmlWriters = XmlWriters.create(); 29 | 30 | xmlWriters.element("ToUserName", "123456") 31 | .element("FromUserName", "me") 32 | .element("CreateTime", System.currentTimeMillis()) 33 | .element("TransInfo", "KfAccount", "test1@test") 34 | .element("MsgType", "transfer_customer_service"); 35 | 36 | assertNotNull(xmlWriters.build()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleSource.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | /** 6 | * 在获取图文阅读分时数据时才有该字段,代表用户从哪里进入来阅读该图文 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public enum ArticleSource { 12 | 13 | /** 14 | * 会话 15 | */ 16 | SESSION(0), 17 | 18 | /** 19 | * 好友 20 | */ 21 | FRIEND(1), 22 | 23 | /** 24 | * 朋友圈 25 | */ 26 | TIMELINE(2), 27 | 28 | /** 29 | * 腾讯微博 30 | */ 31 | WEIBO(3), 32 | 33 | /** 34 | * 历史消息 35 | */ 36 | HISTORY(4), 37 | 38 | /** 39 | * 图文页右上角菜单 40 | */ 41 | OTHER(5); 42 | 43 | private Integer value; 44 | 45 | private ArticleSource(Integer scope){ 46 | this.value = scope; 47 | } 48 | 49 | public Integer value(){ 50 | return value; 51 | } 52 | 53 | public static ArticleSource from(Integer s){ 54 | for (ArticleSource source : ArticleSource.values()){ 55 | if (Objects.equal(source.value(), s)){ 56 | return source; 57 | } 58 | } 59 | return OTHER; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/user/UserSource.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.user; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | /** 6 | * Author: haolin 7 | * Email: haolin.h0@gmail.com 8 | * Date: 20/11/15 9 | */ 10 | public enum UserSource { 11 | 12 | /** 13 | * 其他: 包括带参数二维码 14 | */ 15 | OTHER(0), 16 | 17 | /** 18 | * 扫二维码 19 | */ 20 | SCAN(3), 21 | 22 | /** 23 | * 名片分享 24 | */ 25 | CARD(17), 26 | 27 | /** 28 | * 代表搜号码(即微信添加朋友页的搜索) 29 | */ 30 | SEARCH_NUMBER(35), 31 | 32 | /** 33 | * 查询微信公众帐号 34 | */ 35 | SEARCH_ACCOUNT(39), 36 | 37 | /** 38 | * 图文页右上角菜单 39 | */ 40 | MENU(43); 41 | 42 | private Integer value; 43 | 44 | private UserSource(Integer scope){ 45 | this.value = scope; 46 | } 47 | 48 | public Integer value(){ 49 | return value; 50 | } 51 | 52 | public static UserSource from(Integer s){ 53 | for (UserSource source : UserSource.values()){ 54 | if (Objects.equal(source.value(), s)){ 55 | return source; 56 | } 57 | } 58 | throw new IllegalArgumentException("非法的用户渠道: " + s); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/exception/WechatException.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.exception; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 微信异常,微信服务器返回错误时抛出的异常 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 5/11/15 10 | * @since 1.0.0 11 | */ 12 | public class WechatException extends RuntimeException { 13 | 14 | /** 15 | * 微信返回的errcode 16 | */ 17 | private Integer code; 18 | 19 | public WechatException(Map errMap) { 20 | super("[" + errMap.get("errcode") + "]" + errMap.get("errmsg")); 21 | code = (Integer)errMap.get("errcode"); 22 | } 23 | 24 | public WechatException() { 25 | super(); 26 | } 27 | 28 | public WechatException(String message) { 29 | super(message); 30 | } 31 | 32 | public WechatException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | public WechatException(Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | protected WechatException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 41 | super(message, cause, enableSuppression, writableStackTrace); 42 | } 43 | 44 | public Integer getCode() { 45 | return code; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/user/UserCumulate.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * 用户累计数据 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 20/11/15 11 | */ 12 | public class UserCumulate implements Serializable { 13 | 14 | private static final long serialVersionUID = -1481652103074271866L; 15 | 16 | /** 17 | * 日期: yyyy-MM-dd 18 | */ 19 | @JsonProperty("ref_date") 20 | private String date; 21 | 22 | /** 23 | * 总用户量 24 | */ 25 | @JsonProperty("cumulate_user") 26 | private Integer cumulateCount; 27 | 28 | public String getDate() { 29 | return date; 30 | } 31 | 32 | public void setDate(String date) { 33 | this.date = date; 34 | } 35 | 36 | public Integer getCumulateCount() { 37 | return cumulateCount; 38 | } 39 | 40 | public void setCumulateCount(Integer cumulateCount) { 41 | this.cumulateCount = cumulateCount; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "UserCumulate{" + 47 | "date='" + date + '\'' + 48 | ", cumulateCount=" + cumulateCount + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/Material.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.DateDeserializer; 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 素材基类 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 12/11/15 14 | */ 15 | public abstract class Material implements Serializable { 16 | 17 | @JsonProperty("media_id") 18 | protected String mediaId; 19 | 20 | @JsonProperty("update_time") 21 | @JsonDeserialize(using = DateDeserializer.class) 22 | protected Date updateTime; 23 | 24 | public String getMediaId() { 25 | return mediaId; 26 | } 27 | 28 | public void setMediaId(String mediaId) { 29 | this.mediaId = mediaId; 30 | } 31 | 32 | public Date getUpdateTime() { 33 | return updateTime; 34 | } 35 | 36 | public void setUpdateTime(Date updateTime) { 37 | this.updateTime = updateTime; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Material{" + 43 | "mediaId='" + mediaId + '\'' + 44 | ", updateTime=" + updateTime + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/user/Group.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.user; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 用户分组 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 7/11/15 10 | */ 11 | public class Group implements Serializable { 12 | 13 | private static final long serialVersionUID = 2140215642384090492L; 14 | 15 | /** 16 | * 微信分配的ID 17 | */ 18 | private Integer id; 19 | 20 | /** 21 | * 名称 22 | */ 23 | private String name; 24 | 25 | /** 26 | * 分组内用户量 27 | */ 28 | private Integer count; 29 | 30 | public Integer getId() { 31 | return id; 32 | } 33 | 34 | public void setId(Integer id) { 35 | this.id = id; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public Integer getCount() { 47 | return count; 48 | } 49 | 50 | public void setCount(Integer count) { 51 | this.count = count; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "Group{" + 57 | "id=" + id + 58 | ", name='" + name + '\'' + 59 | ", count=" + count + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvScanEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 扫码事件 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 9/11/15 8 | */ 9 | public class RecvScanEvent extends RecvEvent { 10 | 11 | /** 12 | * 事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id 13 | */ 14 | private String eventKey; 15 | 16 | /** 17 | * 二维码的ticket,可用来换取二维码图片 18 | */ 19 | private String ticket; 20 | 21 | public RecvScanEvent(RecvEvent e){ 22 | super(e); 23 | this.eventType = e.eventType; 24 | } 25 | 26 | @Override 27 | public String getEventType() { 28 | return RecvEventType.SCAN.value(); 29 | } 30 | 31 | public String getEventKey() { 32 | return eventKey; 33 | } 34 | 35 | public void setEventKey(String eventKey) { 36 | this.eventKey = eventKey; 37 | } 38 | 39 | public String getTicket() { 40 | return ticket; 41 | } 42 | 43 | public void setTicket(String ticket) { 44 | this.ticket = ticket; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "RecvScanEvent{" + 50 | "eventKey='" + eventKey + '\'' + 51 | ", ticket='" + ticket + '\'' + 52 | "} " + super.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleTotalDetail.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Author: haolin 7 | * Email: haolin.h0@gmail.com 8 | * Date: 20/11/15 9 | */ 10 | public class ArticleTotalDetail extends CommonSummary { 11 | 12 | private static final long serialVersionUID = 3233729188265417152L; 13 | 14 | /** 15 | * 统计的日期,ref_date指的是文章群发出日期, 而stat_date是数据统计日期 16 | */ 17 | @JsonProperty("stat_date") 18 | private String statDate; 19 | 20 | /** 21 | * 送达人数,一般约等于总粉丝数(需排除黑名单或其他异常情况下无法收到消息的粉丝) 22 | */ 23 | @JsonProperty("target_user") 24 | private Integer targetUser; 25 | 26 | public String getStatDate() { 27 | return statDate; 28 | } 29 | 30 | public void setStatDate(String statDate) { 31 | this.statDate = statDate; 32 | } 33 | 34 | public Integer getTargetUser() { 35 | return targetUser; 36 | } 37 | 38 | public void setTargetUser(Integer targetUser) { 39 | this.targetUser = targetUser; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "ArticleTotalDetail{" + 45 | "statDate='" + statDate + '\'' + 46 | ", targetUser=" + targetUser + 47 | "} " + super.toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvSubscribeEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 关注事件 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 9/11/15 8 | */ 9 | public class RecvSubscribeEvent extends RecvEvent { 10 | 11 | /** 12 | * 事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id 13 | */ 14 | private String eventKey; 15 | 16 | /** 17 | * 二维码的ticket,可用来换取二维码图片 18 | */ 19 | private String ticket; 20 | 21 | public RecvSubscribeEvent(RecvEvent e){ 22 | super(e); 23 | this.eventType = e.eventType; 24 | } 25 | 26 | @Override 27 | public String getEventType() { 28 | return RecvEventType.SUBSCRIBE.value(); 29 | } 30 | 31 | public String getEventKey() { 32 | return eventKey; 33 | } 34 | 35 | public void setEventKey(String eventKey) { 36 | this.eventKey = eventKey; 37 | } 38 | 39 | public String getTicket() { 40 | return ticket; 41 | } 42 | 43 | public void setTicket(String ticket) { 44 | this.ticket = ticket; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "RecvSubcribeEvent{" + 50 | "eventKey='" + eventKey + '\'' + 51 | ", ticket='" + ticket + '\'' + 52 | "} " + super.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvImageMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 图片消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvImageMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = 3465602607733657276L; 14 | 15 | /** 16 | * 图片链接 17 | */ 18 | private String picUrl; 19 | 20 | /** 21 | * 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。 22 | */ 23 | private String mediaId; 24 | 25 | public RecvImageMessage(RecvMsg m){ 26 | super(m); 27 | this.msgId = m.msgId; 28 | } 29 | 30 | public String getPicUrl() { 31 | return picUrl; 32 | } 33 | 34 | public void setPicUrl(String picUrl) { 35 | this.picUrl = picUrl; 36 | } 37 | 38 | public String getMediaId() { 39 | return mediaId; 40 | } 41 | 42 | public void setMediaId(String mediaId) { 43 | this.mediaId = mediaId; 44 | } 45 | 46 | @Override 47 | public String getMsgType() { 48 | return RecvMessageType.IMAGE.value(); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "RecvImageMessage{" + 54 | "picUrl='" + picUrl + '\'' + 55 | ", mediaId='" + mediaId + '\'' + 56 | "} " + super.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/send/TemplateField.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.send; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 模版消息字段 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 8/11/15 10 | */ 11 | public class TemplateField implements Serializable{ 12 | 13 | private static final long serialVersionUID = 8228081012066368310L; 14 | 15 | /** 16 | * 名称 17 | */ 18 | private String name; 19 | 20 | /** 21 | * 值 22 | */ 23 | private String value; 24 | 25 | /** 26 | * 颜色 27 | */ 28 | private String color; 29 | 30 | public TemplateField(){} 31 | 32 | 33 | public TemplateField(String name, String value, String color) { 34 | this.name = name; 35 | this.value = value; 36 | this.color = color; 37 | } 38 | 39 | public TemplateField(String name, String value) { 40 | this.name = name; 41 | this.value = value; 42 | } 43 | 44 | public String getName() { 45 | return name; 46 | } 47 | 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | 52 | public String getValue() { 53 | return value; 54 | } 55 | 56 | public void setValue(String value) { 57 | this.value = value; 58 | } 59 | 60 | public String getColor() { 61 | return color; 62 | } 63 | 64 | public void setColor(String color) { 65 | this.color = color; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleSummaryHour.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.ArticleSourceDeserializer; 6 | 7 | /** 8 | * 图文统计分时数据 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * Date: 20/11/15 12 | */ 13 | public class ArticleSummaryHour extends CommonSummary { 14 | 15 | private static final long serialVersionUID = -5668724641461918373L; 16 | 17 | /** 18 | * 用户渠道 19 | */ 20 | @JsonProperty("user_source") 21 | @JsonDeserialize(using = ArticleSourceDeserializer.class) 22 | private ArticleSource source; 23 | 24 | /** 25 | * 小时: 包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时 26 | */ 27 | @JsonProperty("ref_hour") 28 | private Integer hour; 29 | 30 | public ArticleSource getSource() { 31 | return source; 32 | } 33 | 34 | public void setSource(ArticleSource source) { 35 | this.source = source; 36 | } 37 | 38 | public Integer getHour() { 39 | return hour; 40 | } 41 | 42 | public void setHour(Integer hour) { 43 | this.hour = hour; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "ArticleSummaryHour{" + 49 | "source=" + source + 50 | ", hour=" + hour + 51 | "} " + super.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/base/AccessToken.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.base; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 访问Token 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 10/11/15 10 | * @since 1.0.0 11 | */ 12 | public class AccessToken implements Serializable { 13 | 14 | private static final long serialVersionUID = 6038499458891708844L; 15 | 16 | /** 17 | * accessToken 18 | */ 19 | private String accessToken; 20 | 21 | /** 22 | * 有效时间(s) 23 | */ 24 | private Integer expire; 25 | 26 | /** 27 | * 过期时刻(ms) 28 | */ 29 | private Long expiredAt; 30 | 31 | public String getAccessToken() { 32 | return accessToken; 33 | } 34 | 35 | public void setAccessToken(String accessToken) { 36 | this.accessToken = accessToken; 37 | } 38 | 39 | public Integer getExpire() { 40 | return expire; 41 | } 42 | 43 | public void setExpire(Integer expire) { 44 | this.expire = expire; 45 | } 46 | 47 | public Long getExpiredAt() { 48 | return expiredAt; 49 | } 50 | 51 | public void setExpiredAt(Long expiredAt) { 52 | this.expiredAt = expiredAt; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "AccessToken{" + 58 | "accessToken='" + accessToken + '\'' + 59 | ", expire=" + expire + 60 | ", expiredAt=" + expiredAt + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/customer/WaitingSession.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.customer; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 未接入会话 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 7/11/15 11 | */ 12 | public class WaitingSession implements Serializable { 13 | 14 | private static final long serialVersionUID = 2699187929516108564L; 15 | 16 | /** 17 | * 用户openId 18 | */ 19 | private String openId; 20 | 21 | /** 22 | * 客服帐号(包含域名) 23 | */ 24 | private String kfAccount; 25 | 26 | /** 27 | * 会话结束时间 28 | */ 29 | private Date createTime; 30 | 31 | public String getOpenId() { 32 | return openId; 33 | } 34 | 35 | public void setOpenId(String openId) { 36 | this.openId = openId; 37 | } 38 | 39 | public String getKfAccount() { 40 | return kfAccount; 41 | } 42 | 43 | public void setKfAccount(String kfAccount) { 44 | this.kfAccount = kfAccount; 45 | } 46 | 47 | public Date getCreateTime() { 48 | return createTime; 49 | } 50 | 51 | public void setCreateTime(Date createTime) { 52 | this.createTime = createTime; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "WaitingSession{" + 58 | "openId='" + openId + '\'' + 59 | ", kfAccount='" + kfAccount + '\'' + 60 | ", createTime=" + createTime + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvVideoMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 视频消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvVideoMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = -3750491257934605285L; 14 | 15 | /** 16 | * 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 17 | */ 18 | private String mediaId; 19 | 20 | /** 21 | * 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 22 | */ 23 | private String thumbMediaId; 24 | 25 | public RecvVideoMessage(RecvMsg m){ 26 | super(m); 27 | this.msgId = m.msgId; 28 | } 29 | 30 | public String getMediaId() { 31 | return mediaId; 32 | } 33 | 34 | public void setMediaId(String mediaId) { 35 | this.mediaId = mediaId; 36 | } 37 | 38 | public String getThumbMediaId() { 39 | return thumbMediaId; 40 | } 41 | 42 | public void setThumbMediaId(String thumbMediaId) { 43 | this.thumbMediaId = thumbMediaId; 44 | } 45 | 46 | @Override 47 | public String getMsgType() { 48 | return RecvMessageType.VIDEO.value(); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "RecvVideoMessage{" + 54 | "mediaId='" + mediaId + '\'' + 55 | ", thumbMediaId='" + thumbMediaId + '\'' + 56 | "} " + super.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvTemplateSendJobFinishEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 模版消息发送任务完成后, 微信服务器会将是否送达成功作为通知 5 | *

6 | * http://mp.weixin.qq.com/wiki/12/bd383158b0f8435c07b8b6bc7cdbac9c.html#.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81 7 | *

8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 9/11/15 11 | */ 12 | public class RecvTemplateSendJobFinishEvent extends RecvEvent { 13 | 14 | /** 15 | * 模版消息送达状态: 16 | *

17 | * 1. 送达成功时:success 18 | * 2. 送达由于用户拒收(用户设置拒绝接收公众号消息)而失败时:failed:user block 19 | * 3. 送达由于其他原因失败时:failed: system failed 20 | *

21 | */ 22 | private String status; 23 | 24 | public RecvTemplateSendJobFinishEvent(RecvEvent e){ 25 | super(e); 26 | this.eventType = e.eventType; 27 | } 28 | 29 | @Override 30 | public String getEventType() { 31 | return RecvEventType.TEMPLATE_SEND_JOB_FINISH.value(); 32 | } 33 | 34 | public String getStatus() { 35 | return status; 36 | } 37 | 38 | public void setStatus(String status) { 39 | this.status = status; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "RecvTemplateSendJobFinishEvent{" + 45 | "status='" + status + '\'' + 46 | "} " + super.toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | server_log_home_IS_UNDEFINED 10 | 11 | # Packages # 12 | ############ 13 | # it's better to unpack these files and commit the raw source 14 | # git has its own built in compression methods 15 | *.7z 16 | *.dmg 17 | *.gz 18 | *.iso 19 | *.jar 20 | *.rar 21 | *.tar 22 | *.zip 23 | 24 | # Logs and databases # 25 | ###################### 26 | *.log 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store* 31 | ehthumbs.db 32 | #Icon? 33 | Thumbs.db 34 | 35 | # Editor Files # 36 | ################ 37 | *~ 38 | *.swp 39 | 40 | # Gradle Files # 41 | ################ 42 | .gradle 43 | 44 | # Build output directies 45 | /target 46 | */target 47 | /build 48 | */build 49 | /generated-sources 50 | */generated-sources 51 | *Thunderbolt/core/generated-sources/* 52 | var 53 | logs 54 | 55 | .idea 56 | # IntelliJ specific files/directories 57 | out 58 | .idea/* 59 | *.ipr 60 | *.iws 61 | *.iml 62 | atlassian-ide-plugin.xml 63 | 64 | # Eclipse specific files/directories 65 | .classpath 66 | .project 67 | .settings 68 | .metadata 69 | .recommenders 70 | 71 | # NetBeans specific files/directories 72 | .nbattrs 73 | *.orig 74 | service-location/service-location/* 75 | service-base/service-base/* 76 | service-coupon/service-coupon/* 77 | service-courier/service-courier/* 78 | service-demo/service-demo/* 79 | service-sms/service-sms/* 80 | service-user/service-user/* 81 | 82 | # src/test/resources/dev.properties 83 | 84 | Demo*.java 85 | 86 | *.versionsBackup 87 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/qrcode/Qrcode.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.qrcode; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class Qrcode implements Serializable { 12 | 13 | private static final long serialVersionUID = -8864173402832984445L; 14 | 15 | /** 16 | * 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。 17 | */ 18 | private String ticket; 19 | 20 | /** 21 | * 有效时间(秒) 22 | */ 23 | @JsonProperty("expire_seconds") 24 | private String expireSeconds; 25 | 26 | /** 27 | * 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片 28 | */ 29 | private String url; 30 | 31 | public String getTicket() { 32 | return ticket; 33 | } 34 | 35 | public void setTicket(String ticket) { 36 | this.ticket = ticket; 37 | } 38 | 39 | public String getExpireSeconds() { 40 | return expireSeconds; 41 | } 42 | 43 | public void setExpireSeconds(String expireSeconds) { 44 | this.expireSeconds = expireSeconds; 45 | } 46 | 47 | public String getUrl() { 48 | return url; 49 | } 50 | 51 | public void setUrl(String url) { 52 | this.url = url; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "Qrcode{" + 58 | "ticket='" + ticket + '\'' + 59 | ", expireSeconds='" + expireSeconds + '\'' + 60 | ", url='" + url + '\'' + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/RecvMessageType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive; 2 | 3 | import me.hao0.wechat.exception.EventException; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * 接收微信服务器的消息类型 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * Date: 8/11/15 12 | */ 13 | public enum RecvMessageType { 14 | 15 | TEXT("text", "文本消息"), 16 | IMAGE("image", "图片消息"), 17 | VOICE("voice", "语音消息"), 18 | VIDEO("video", "视频消息"), 19 | SHORT_VIDEO("shortvideo", "小视频消息"), 20 | LOCATION("location", "地理位置信息"), 21 | LINK("link", "链接信息"), 22 | /** 23 | * 接收到微信服务器的事件消息: 24 | * @see me.hao0.wechat.model.message.receive.event.RecvEventType 25 | */ 26 | EVENT("event", "事件消息"); 27 | 28 | private String value; 29 | 30 | private String desc; 31 | 32 | private RecvMessageType(String value, String desc){ 33 | this.value = value; 34 | this.desc = desc; 35 | } 36 | 37 | public String value(){ 38 | return value; 39 | } 40 | 41 | public String desc(){ 42 | return desc; 43 | } 44 | 45 | public static RecvMessageType from(String type){ 46 | for (RecvMessageType t : RecvMessageType.values()){ 47 | if (Objects.equals(t.value(), type)){ 48 | return t; 49 | } 50 | } 51 | throw new EventException("unknown message type"); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "RecvMessageType{" + 57 | "value='" + value + '\'' + 58 | ", desc='" + desc + '\'' + 59 | '}'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/TempMaterial.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.DateDeserializer; 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 上传临时素材后的返回对象 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 13/11/15 14 | */ 15 | public class TempMaterial implements Serializable { 16 | 17 | private static final long serialVersionUID = -824128825701922924L; 18 | 19 | private MaterialUploadType type; 20 | 21 | @JsonProperty("media_id") 22 | private String mediaId; 23 | 24 | @JsonProperty("created_at") 25 | @JsonDeserialize(using = DateDeserializer.class) 26 | private Date createdAt; 27 | 28 | public MaterialUploadType getType() { 29 | return type; 30 | } 31 | 32 | public void setType(MaterialUploadType type) { 33 | this.type = type; 34 | } 35 | 36 | public String getMediaId() { 37 | return mediaId; 38 | } 39 | 40 | public void setMediaId(String mediaId) { 41 | this.mediaId = mediaId; 42 | } 43 | 44 | public Date getCreatedAt() { 45 | return createdAt; 46 | } 47 | 48 | public void setCreatedAt(Date createdAt) { 49 | this.createdAt = createdAt; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "Media{" + 55 | "type=" + type + 56 | ", mediaId='" + mediaId + '\'' + 57 | ", createdAt=" + createdAt + 58 | '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvLocationEvent.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | /** 4 | * 上报地理位置事件 5 | * Author: haolin 6 | * Email: haolin.h0@gmail.com 7 | * Date: 9/11/15 8 | */ 9 | public class RecvLocationEvent extends RecvEvent { 10 | 11 | /** 12 | * 纬度 13 | */ 14 | private String latitude; 15 | 16 | /** 17 | * 经度 18 | */ 19 | private String longitude; 20 | 21 | /** 22 | * 精度 23 | */ 24 | private String precision; 25 | 26 | public RecvLocationEvent(RecvEvent e){ 27 | super(e); 28 | this.eventType = e.eventType; 29 | } 30 | 31 | @Override 32 | public String getEventType() { 33 | return RecvEventType.LOCATION.value(); 34 | } 35 | 36 | public String getLatitude() { 37 | return latitude; 38 | } 39 | 40 | public void setLatitude(String latitude) { 41 | this.latitude = latitude; 42 | } 43 | 44 | public String getLongitude() { 45 | return longitude; 46 | } 47 | 48 | public void setLongitude(String longitude) { 49 | this.longitude = longitude; 50 | } 51 | 52 | public String getPrecision() { 53 | return precision; 54 | } 55 | 56 | public void setPrecision(String precision) { 57 | this.precision = precision; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "RecvLocationEvent{" + 63 | "latitude='" + latitude + '\'' + 64 | ", longitude='" + longitude + '\'' + 65 | ", precision='" + precision + '\'' + 66 | "} " + super.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/msg/MsgSendDist.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.msg; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * 消息发送分布数据 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 20/11/15 11 | */ 12 | public class MsgSendDist implements Serializable { 13 | 14 | private static final long serialVersionUID = -2677208070764939927L; 15 | 16 | /** 17 | * 日期 18 | */ 19 | @JsonProperty("ref_date") 20 | private String date; 21 | 22 | /** 23 | *当日发送消息量分布的区间: 24 | * 0代表 “0”,1代表“1-5”,2代表“6-10”,3代表“10次以上” 25 | */ 26 | @JsonProperty("count_interval") 27 | private Integer countInterval; 28 | 29 | /** 30 | * 想公众号发送消息的用户数 31 | */ 32 | @JsonProperty("msg_user") 33 | private Integer msgUser; 34 | 35 | public String getDate() { 36 | return date; 37 | } 38 | 39 | public void setDate(String date) { 40 | this.date = date; 41 | } 42 | 43 | public Integer getCountInterval() { 44 | return countInterval; 45 | } 46 | 47 | public void setCountInterval(Integer countInterval) { 48 | this.countInterval = countInterval; 49 | } 50 | 51 | public Integer getMsgUser() { 52 | return msgUser; 53 | } 54 | 55 | public void setMsgUser(Integer msgUser) { 56 | this.msgUser = msgUser; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "MsgSendDist{" + 62 | "date='" + date + '\'' + 63 | ", countInterval=" + countInterval + 64 | ", msgUser=" + msgUser + 65 | '}'; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/event/RecvEventType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.event; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * 接收微信服务器的消息类型 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 8/11/15 10 | */ 11 | public enum RecvEventType { 12 | 13 | UNKNOW("unknown", "未知事件"), 14 | 15 | SUBSCRIBE("subscribe", "关注公众号"), 16 | UN_SUBSCRIBE("unsubscribe", "取消关注公众号"), 17 | /** 18 | * 1. 用户未关注时,进行关注后的事件推送: 19 | */ 20 | SCAN("SCAN", "扫码"), 21 | LOCATION("LOCATION", "上报地理位置信息"), 22 | /** 23 | * 点击菜单拉取消息时的事件推送 24 | */ 25 | MENU_CLICK("CLICK", "点击菜单拉取消息时"), 26 | /** 27 | * 点击菜单跳转链接时的事件推送 28 | */ 29 | MENU_VIEW("VIEW", "点击菜单跳转链接时"), 30 | 31 | /** 32 | * 模版消息发送结果通知事件 33 | */ 34 | TEMPLATE_SEND_JOB_FINISH("TEMPLATESENDJOBFINISH", "模版消息发送任务完成后, 微信服务器会将是否送达成功作为通知"); 35 | 36 | private String value; 37 | 38 | private String desc; 39 | 40 | RecvEventType(String value, String desc){ 41 | this.value = value; 42 | this.desc = desc; 43 | } 44 | 45 | public String value(){ 46 | return value; 47 | } 48 | 49 | public static RecvEventType from(String type){ 50 | 51 | for (RecvEventType t : RecvEventType.values()){ 52 | if (Objects.equals(t.value(), type)){ 53 | return t; 54 | } 55 | } 56 | 57 | return UNKNOW; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "RecvEventType{" + 63 | "value='" + value + '\'' + 64 | ", desc='" + desc + '\'' + 65 | "} " + super.toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/js/Ticket.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.js; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 临时凭证 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 14/11/15 10 | * @see TicketType 11 | */ 12 | public class Ticket implements Serializable { 13 | 14 | private static final long serialVersionUID = 978451551258121101L; 15 | 16 | /** 17 | * 凭证字符串 18 | */ 19 | private String ticket; 20 | 21 | /** 22 | * 凭证类型 23 | */ 24 | private TicketType type; 25 | 26 | /** 27 | * 有效时间(s) 28 | */ 29 | private Integer expire; 30 | 31 | /** 32 | * 过期时刻(ms) 33 | */ 34 | private Long expireAt; 35 | 36 | public String getTicket() { 37 | return ticket; 38 | } 39 | 40 | public void setTicket(String ticket) { 41 | this.ticket = ticket; 42 | } 43 | 44 | public TicketType getType() { 45 | return type; 46 | } 47 | 48 | public void setType(TicketType type) { 49 | this.type = type; 50 | } 51 | 52 | public Integer getExpire() { 53 | return expire; 54 | } 55 | 56 | public void setExpire(Integer expire) { 57 | this.expire = expire; 58 | } 59 | 60 | public Long getExpireAt() { 61 | return expireAt; 62 | } 63 | 64 | public void setExpireAt(Long expireAt) { 65 | this.expireAt = expireAt; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "Ticket{" + 71 | "ticket='" + ticket + '\'' + 72 | ", type=" + type + 73 | ", expire=" + expire + 74 | ", expireAt=" + expireAt + 75 | '}'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleDailySummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * 图文群发每日数据 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 20/11/15 10 | */ 11 | public class ArticleDailySummary extends CommonSummary { 12 | 13 | private static final long serialVersionUID = -7195185645683267419L; 14 | 15 | /** 16 | * 日期: yyyy-MM-dd 17 | */ 18 | @JsonProperty("ref_date") 19 | private String date; 20 | 21 | /** 22 | * 群发消息ID: 23 | 这里的msgid实际上是由msgid 24 | (图文消息id,这也就是群发接口调用后返回的msg_data_id)和index(消息次序索引)组成,例如12003_3, 25 | 其中12003是msgid,即一次群发的消息的id; 26 | 3为index,假设该次群发的图文消息共5个文章(因为可能为多图文),3表示5个中的第3个 27 | */ 28 | @JsonProperty("msgid") 29 | private String msgId; 30 | 31 | /** 32 | * 图文标题 33 | */ 34 | private String title; 35 | 36 | 37 | public String getDate() { 38 | return date; 39 | } 40 | 41 | public void setDate(String date) { 42 | this.date = date; 43 | } 44 | 45 | public String getMsgId() { 46 | return msgId; 47 | } 48 | 49 | public void setMsgId(String msgId) { 50 | this.msgId = msgId; 51 | } 52 | 53 | public String getTitle() { 54 | return title; 55 | } 56 | 57 | public void setTitle(String title) { 58 | this.title = title; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "ArticleSummary{" + 64 | "date='" + date + '\'' + 65 | ", msgId='" + msgId + '\'' + 66 | ", title='" + title + '\'' + 67 | "} " + super.toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvLinkMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 链接消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvLinkMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = -8070100690774814611L; 14 | 15 | /** 16 | * 消息标题 17 | */ 18 | private String title; 19 | 20 | /** 21 | * 消息描述 22 | */ 23 | private String description; 24 | 25 | /** 26 | * 消息链接 27 | */ 28 | private String url; 29 | 30 | public RecvLinkMessage(RecvMsg m){ 31 | super(m); 32 | this.msgId = m.msgId; 33 | } 34 | 35 | public String getTitle() { 36 | return title; 37 | } 38 | 39 | public void setTitle(String title) { 40 | this.title = title; 41 | } 42 | 43 | public String getDescription() { 44 | return description; 45 | } 46 | 47 | public void setDescription(String description) { 48 | this.description = description; 49 | } 50 | 51 | public String getUrl() { 52 | return url; 53 | } 54 | 55 | public void setUrl(String url) { 56 | this.url = url; 57 | } 58 | 59 | @Override 60 | public String getMsgType() { 61 | return RecvMessageType.LINK.value(); 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "RecvLinkMessage{" + 67 | "title='" + title + '\'' + 68 | ", description='" + description + '\'' + 69 | ", url='" + url + '\'' + 70 | "} " + super.toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvVoiceMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 语音消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvVoiceMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = 4578361001225765322L; 14 | 15 | /** 16 | * 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 17 | */ 18 | private String mediaId; 19 | 20 | /** 21 | * 语音格式,如amr,speex等 22 | */ 23 | private String format; 24 | 25 | /** 26 | * 语音识别结果,使用UTF8编码 27 | */ 28 | private String recognition; 29 | 30 | public RecvVoiceMessage(RecvMsg m){ 31 | super(m); 32 | this.msgId = m.msgId; 33 | } 34 | 35 | public String getFormat() { 36 | return format; 37 | } 38 | 39 | public void setFormat(String format) { 40 | this.format = format; 41 | } 42 | 43 | public String getMediaId() { 44 | return mediaId; 45 | } 46 | 47 | public void setMediaId(String mediaId) { 48 | this.mediaId = mediaId; 49 | } 50 | 51 | public String getRecognition() { 52 | return recognition; 53 | } 54 | 55 | public void setRecognition(String recognition) { 56 | this.recognition = recognition; 57 | } 58 | 59 | @Override 60 | public String getMsgType() { 61 | return RecvMessageType.VOICE.value(); 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "RecvVoiceMessage{" + 67 | "mediaId='" + mediaId + '\'' + 68 | ", format='" + format + '\'' + 69 | ", recognition='" + recognition + '\'' + 70 | "} " + super.toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/MaterialCount.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * 素材总数统计 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 12/11/15 11 | */ 12 | public class MaterialCount implements Serializable { 13 | 14 | private static final long serialVersionUID = 8269265985082026069L; 15 | 16 | /** 17 | * 图片数 18 | */ 19 | @JsonProperty("image_count") 20 | private Integer image; 21 | 22 | /** 23 | * 图文数 24 | */ 25 | @JsonProperty("news_count") 26 | private Integer news; 27 | 28 | /** 29 | * 语音数 30 | */ 31 | @JsonProperty("voice_count") 32 | private Integer voice; 33 | 34 | /** 35 | * 视频数 36 | */ 37 | @JsonProperty("video_count") 38 | private Integer video; 39 | 40 | public Integer getImage() { 41 | return image; 42 | } 43 | 44 | public void setImage(Integer image) { 45 | this.image = image; 46 | } 47 | 48 | public Integer getNews() { 49 | return news; 50 | } 51 | 52 | public void setNews(Integer news) { 53 | this.news = news; 54 | } 55 | 56 | public Integer getVoice() { 57 | return voice; 58 | } 59 | 60 | public void setVoice(Integer voice) { 61 | this.voice = voice; 62 | } 63 | 64 | public Integer getVideo() { 65 | return video; 66 | } 67 | 68 | public void setVideo(Integer video) { 69 | this.video = video; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "Count{" + 75 | "image=" + image + 76 | ", news=" + news + 77 | ", voice=" + voice + 78 | ", video=" + video + 79 | '}'; 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/resp/Article.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.resp; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 图文消息对象 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 8/11/15 10 | */ 11 | public class Article implements Serializable { 12 | 13 | private static final long serialVersionUID = -5038606046133238683L; 14 | 15 | private String title; 16 | 17 | private String desc; 18 | 19 | /** 20 | * 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 21 | */ 22 | private String picUrl; 23 | 24 | /** 25 | * 点击图文消息跳转链接 26 | */ 27 | private String url; 28 | 29 | public Article(){} 30 | 31 | public Article(String title, String desc, String picUrl, String url) { 32 | this.title = title; 33 | this.desc = desc; 34 | this.picUrl = picUrl; 35 | this.url = url; 36 | } 37 | 38 | public String getTitle() { 39 | return title; 40 | } 41 | 42 | public void setTitle(String title) { 43 | this.title = title; 44 | } 45 | 46 | public String getDesc() { 47 | return desc; 48 | } 49 | 50 | public void setDesc(String desc) { 51 | this.desc = desc; 52 | } 53 | 54 | public String getPicUrl() { 55 | return picUrl; 56 | } 57 | 58 | public void setPicUrl(String picUrl) { 59 | this.picUrl = picUrl; 60 | } 61 | 62 | public String getUrl() { 63 | return url; 64 | } 65 | 66 | public void setUrl(String url) { 67 | this.url = url; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "ArticleItem{" + 73 | "title='" + title + '\'' + 74 | ", desc='" + desc + '\'' + 75 | ", picUrl='" + picUrl + '\'' + 76 | ", url='" + url + '\'' + 77 | '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/user/UserList.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 用户列表信息 9 | * Created by y27chen on 2016/3/16. 10 | */ 11 | public class UserList { 12 | 13 | @JsonProperty("total") 14 | private Integer total; 15 | 16 | @JsonProperty("count") 17 | private Integer count; 18 | 19 | @JsonProperty("data") 20 | private UserListRecord data; 21 | 22 | @JsonProperty("next_openid") 23 | private String nextOpenId; 24 | 25 | public Integer getTotal() { 26 | return total; 27 | } 28 | 29 | public void setTotal(Integer total) { 30 | this.total = total; 31 | } 32 | 33 | public Integer getCount() { 34 | return count; 35 | } 36 | 37 | public void setCount(Integer count) { 38 | this.count = count; 39 | } 40 | 41 | public String getNextOpenId() { 42 | return nextOpenId; 43 | } 44 | 45 | public void setNextOpenId(String nextOpenId) { 46 | this.nextOpenId = nextOpenId; 47 | } 48 | 49 | public UserListRecord getData() { 50 | return data; 51 | } 52 | 53 | public void setData(UserListRecord data) { 54 | this.data = data; 55 | } 56 | 57 | public class UserListRecord { 58 | @JsonProperty("openid") 59 | private List openId; 60 | 61 | public List getOpenId() { 62 | return openId; 63 | } 64 | 65 | public void setOpenId(List openId) { 66 | this.openId = openId; 67 | } 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "Users{" + 73 | "total=" + total + 74 | ", count=" + count + 75 | ", data=" + data + 76 | ", nextOpenId='" + nextOpenId + '\'' + 77 | '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/msg/RecvLocationMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive.msg; 2 | 3 | import me.hao0.wechat.model.message.receive.RecvMessageType; 4 | 5 | /** 6 | * 地理位置消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvLocationMessage extends RecvMsg { 12 | 13 | private static final long serialVersionUID = 2468731105380952027L; 14 | 15 | /** 16 | * 纬度 17 | */ 18 | private String locationX; 19 | 20 | /** 21 | * 经度 22 | */ 23 | private String locationY; 24 | 25 | /** 26 | * 缩放大小 27 | */ 28 | private Integer scale; 29 | 30 | /** 31 | * 位置信息 32 | */ 33 | private String label; 34 | 35 | public RecvLocationMessage(RecvMsg m){ 36 | super(m); 37 | this.msgId = m.msgId; 38 | } 39 | 40 | @Override 41 | public String getMsgType() { 42 | return RecvMessageType.LOCATION.value(); 43 | } 44 | 45 | public String getLocationX() { 46 | return locationX; 47 | } 48 | 49 | public void setLocationX(String locationX) { 50 | this.locationX = locationX; 51 | } 52 | 53 | public String getLocationY() { 54 | return locationY; 55 | } 56 | 57 | public void setLocationY(String locationY) { 58 | this.locationY = locationY; 59 | } 60 | 61 | public Integer getScale() { 62 | return scale; 63 | } 64 | 65 | public void setScale(Integer scale) { 66 | this.scale = scale; 67 | } 68 | 69 | public String getLabel() { 70 | return label; 71 | } 72 | 73 | public void setLabel(String label) { 74 | this.label = label; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "RecvLocationMessage{" + 80 | "locationX='" + locationX + '\'' + 81 | ", locationY='" + locationY + '\'' + 82 | ", scale=" + scale + 83 | ", label='" + label + '\'' + 84 | "} " + super.toString(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/js/Config.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.js; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 调用JSSDK前需要加载的配置对象(http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html) 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 15/11/15 10 | */ 11 | public class Config implements Serializable { 12 | 13 | private static final long serialVersionUID = -8263857663686622616L; 14 | 15 | /** 16 | * 微信APP ID 17 | */ 18 | private String appId; 19 | 20 | /** 21 | * 时间戳(秒) 22 | */ 23 | private Long timestamp; 24 | 25 | /** 26 | * 随机字符串 27 | */ 28 | private String nonStr; 29 | 30 | /** 31 | * 签名 32 | */ 33 | private String signature; 34 | 35 | public Config(String appId, Long timestamp, String nonStr, String signature) { 36 | this.appId = appId; 37 | this.timestamp = timestamp; 38 | this.nonStr = nonStr; 39 | this.signature = signature; 40 | } 41 | 42 | public Config() { 43 | } 44 | 45 | public String getAppId() { 46 | return appId; 47 | } 48 | 49 | public void setAppId(String appId) { 50 | this.appId = appId; 51 | } 52 | 53 | public Long getTimestamp() { 54 | return timestamp; 55 | } 56 | 57 | public void setTimestamp(Long timestamp) { 58 | this.timestamp = timestamp; 59 | } 60 | 61 | public String getNonStr() { 62 | return nonStr; 63 | } 64 | 65 | public void setNonStr(String nonStr) { 66 | this.nonStr = nonStr; 67 | } 68 | 69 | public String getSignature() { 70 | return signature; 71 | } 72 | 73 | public void setSignature(String signature) { 74 | this.signature = signature; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "Config{" + 80 | "appId='" + appId + '\'' + 81 | ", timestamp=" + timestamp + 82 | ", nonStr='" + nonStr + '\'' + 83 | ", signature='" + signature + '\'' + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleShare.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.ArticleShareSceneDeserializer; 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 图文分享转发数据 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 20/11/15 13 | */ 14 | public class ArticleShare implements Serializable { 15 | 16 | private static final long serialVersionUID = -2590968439236572137L; 17 | 18 | /** 19 | * 日期 20 | */ 21 | @JsonProperty("ref_date") 22 | private String date; 23 | 24 | /** 25 | * 分享场景 26 | */ 27 | @JsonProperty("share_scene") 28 | @JsonDeserialize(using = ArticleShareSceneDeserializer.class) 29 | private ArticleShareScene scene; 30 | 31 | /** 32 | * 分享次数 33 | */ 34 | @JsonProperty("share_count") 35 | private Integer count; 36 | 37 | /** 38 | * 分享人数 39 | */ 40 | @JsonProperty("share_user") 41 | private Integer user; 42 | 43 | public String getDate() { 44 | return date; 45 | } 46 | 47 | public void setDate(String date) { 48 | this.date = date; 49 | } 50 | 51 | public ArticleShareScene getScene() { 52 | return scene; 53 | } 54 | 55 | public void setScene(ArticleShareScene scene) { 56 | this.scene = scene; 57 | } 58 | 59 | public Integer getCount() { 60 | return count; 61 | } 62 | 63 | public void setCount(Integer count) { 64 | this.count = count; 65 | } 66 | 67 | public Integer getUser() { 68 | return user; 69 | } 70 | 71 | public void setUser(Integer user) { 72 | this.user = user; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "ArticleShare{" + 78 | "date='" + date + '\'' + 79 | ", scene=" + scene + 80 | ", count=" + count + 81 | ", user=" + user + 82 | '}'; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/ArticleTotal.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | import java.util.List; 6 | 7 | /** 8 | * 图文群发总数据 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * Date: 20/11/15 12 | */ 13 | public class ArticleTotal implements Serializable { 14 | 15 | private static final long serialVersionUID = -4800022767823366024L; 16 | 17 | /** 18 | * 日期: yyyy-MM-dd 19 | */ 20 | @JsonProperty("ref_date") 21 | private String date; 22 | 23 | /** 24 | * 群发消息ID: 25 | 这里的msgid实际上是由msgid 26 | (图文消息id,这也就是群发接口调用后返回的msg_data_id)和index(消息次序索引)组成,例如12003_3, 27 | 其中12003是msgid,即一次群发的消息的id; 28 | 3为index,假设该次群发的图文消息共5个文章(因为可能为多图文),3表示5个中的第3个 29 | */ 30 | @JsonProperty("msgid") 31 | private String msgId; 32 | 33 | /** 34 | * 图文标题 35 | */ 36 | private String title; 37 | 38 | /** 39 | * 每天对应的数值为该文章到该日为止的总量(而不是当日的量) 40 | */ 41 | private List details; 42 | 43 | public String getDate() { 44 | return date; 45 | } 46 | 47 | public void setDate(String date) { 48 | this.date = date; 49 | } 50 | 51 | public String getMsgId() { 52 | return msgId; 53 | } 54 | 55 | public void setMsgId(String msgId) { 56 | this.msgId = msgId; 57 | } 58 | 59 | public String getTitle() { 60 | return title; 61 | } 62 | 63 | public void setTitle(String title) { 64 | this.title = title; 65 | } 66 | 67 | public List getDetails() { 68 | return details; 69 | } 70 | 71 | public void setDetails(List details) { 72 | this.details = details; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "ArticleTotal{" + 78 | "date='" + date + '\'' + 79 | ", msgId='" + msgId + '\'' + 80 | ", title='" + title + '\'' + 81 | ", details=" + details + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/msg/MsgSendSummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.msg; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.MsgTypeDeserializer; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 消息发送分析 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 20/11/15 14 | */ 15 | public class MsgSendSummary implements Serializable { 16 | 17 | private static final long serialVersionUID = -8877051363122800450L; 18 | 19 | /** 20 | * 日期 21 | */ 22 | @JsonProperty("ref_date") 23 | private String date; 24 | 25 | /** 26 | * 消息类型 27 | */ 28 | @JsonProperty("msg_type") 29 | @JsonDeserialize(using = MsgTypeDeserializer.class) 30 | private MsgType msgType; 31 | 32 | /** 33 | * 向公众号发送了消息的用户数 34 | */ 35 | @JsonProperty("msg_user") 36 | private Integer msgUser; 37 | 38 | /** 39 | * 向公众号发送的消息数 40 | */ 41 | @JsonProperty("msg_count") 42 | private Integer msgCount; 43 | 44 | public String getDate() { 45 | return date; 46 | } 47 | 48 | public void setDate(String date) { 49 | this.date = date; 50 | } 51 | 52 | public MsgType getMsgType() { 53 | return msgType; 54 | } 55 | 56 | public void setMsgType(MsgType msgType) { 57 | this.msgType = msgType; 58 | } 59 | 60 | public Integer getMsgUser() { 61 | return msgUser; 62 | } 63 | 64 | public void setMsgUser(Integer msgUser) { 65 | this.msgUser = msgUser; 66 | } 67 | 68 | public Integer getMsgCount() { 69 | return msgCount; 70 | } 71 | 72 | public void setMsgCount(Integer msgCount) { 73 | this.msgCount = msgCount; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "MsgSendSummary{" + 79 | "date='" + date + '\'' + 80 | ", msgType=" + msgType + 81 | ", msgUser=" + msgUser + 82 | ", msgCount=" + msgCount + 83 | '}'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/receive/RecvMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.receive; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 接收微信服务器的消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 9/11/15 10 | */ 11 | public class RecvMessage implements Serializable { 12 | 13 | /** 14 | * 开发者微信号 15 | */ 16 | protected String toUserName; 17 | 18 | /** 19 | * 用户openId 20 | */ 21 | protected String fromUserName; 22 | 23 | /** 24 | * 消息创建时间 25 | */ 26 | protected Integer createTime; 27 | 28 | /** 29 | * 消息类型: 30 | * @see me.hao0.wechat.model.message.resp.RespMessageType 31 | */ 32 | protected String msgType; 33 | 34 | public RecvMessage(){} 35 | 36 | public RecvMessage(RecvMessage m){ 37 | this.toUserName = m.toUserName; 38 | this.fromUserName = m.fromUserName; 39 | this.createTime = m.createTime; 40 | this.msgType = m.msgType; 41 | } 42 | 43 | public String getToUserName() { 44 | return toUserName; 45 | } 46 | 47 | public void setToUserName(String toUserName) { 48 | this.toUserName = toUserName; 49 | } 50 | 51 | public String getFromUserName() { 52 | return fromUserName; 53 | } 54 | 55 | public void setFromUserName(String fromUserName) { 56 | this.fromUserName = fromUserName; 57 | } 58 | 59 | public Integer getCreateTime() { 60 | return createTime; 61 | } 62 | 63 | public void setCreateTime(Integer createTime) { 64 | this.createTime = createTime; 65 | } 66 | 67 | public void setMsgType(String msgType) { 68 | this.msgType = msgType; 69 | } 70 | 71 | public String getMsgType(){ 72 | return this.msgType; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "RecvMessage{" + 78 | "toUserName='" + toUserName + '\'' + 79 | ", fromUserName='" + fromUserName + '\'' + 80 | ", createTime=" + createTime + 81 | ", msgType='" + msgType + '\'' + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/menu/Menu.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.menu; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 菜单 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 7/11/15 13 | */ 14 | public class Menu implements Serializable { 15 | 16 | private static final long serialVersionUID = 3569890088693211989L; 17 | 18 | /** 19 | * 名称: 20 | * 一级最多4个字,二级最多7个字 21 | */ 22 | private String name; 23 | 24 | /** 25 | * 类型 26 | */ 27 | private String type; 28 | 29 | /** 30 | * 菜单key,当type="click"时 31 | */ 32 | private String key; 33 | 34 | /** 35 | * 菜单url,当type="view"时 36 | */ 37 | private String url; 38 | 39 | /** 40 | * 最多3个一级,5个二级 41 | */ 42 | @JsonProperty("sub_button") 43 | private List children = new ArrayList<>(); 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | 53 | public String getType() { 54 | return type; 55 | } 56 | 57 | public void setType(String type) { 58 | this.type = type; 59 | } 60 | 61 | public String getKey() { 62 | return key; 63 | } 64 | 65 | public void setKey(String key) { 66 | this.key = key; 67 | } 68 | 69 | public String getUrl() { 70 | return url; 71 | } 72 | 73 | public void setUrl(String url) { 74 | this.url = url; 75 | } 76 | 77 | public List getChildren() { 78 | return children; 79 | } 80 | 81 | public void setChildren(List children) { 82 | this.children = children; 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "Menu{" + 88 | "name='" + name + '\'' + 89 | ", type='" + type + '\'' + 90 | ", key='" + key + '\'' + 91 | ", url='" + url + '\'' + 92 | ", children=" + children + 93 | '}'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/send/SendPreviewMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.send; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 发送预览消息 7 | * Author: haolin 8 | * Email: haolin.h0@gmail.com 9 | * Date: 8/11/15 10 | */ 11 | public class SendPreviewMessage implements Serializable { 12 | 13 | private static final long serialVersionUID = -5227718099852463553L; 14 | 15 | /** 16 | * 消息类型: 17 | * @see SendMessageType 18 | */ 19 | private SendMessageType type; 20 | 21 | /** 22 | * 用户openId 23 | */ 24 | private String openId; 25 | 26 | /** 27 | * 文本消息内容,当type为文本时 28 | */ 29 | private String content; 30 | 31 | /** 32 | * 用于群发的消息的media_id,当type为语音,图文,视频,图片时 33 | */ 34 | private String mediaId; 35 | 36 | /** 37 | * 卡券ID,当type为卡券时 38 | */ 39 | private String cardId; 40 | 41 | public String getOpenId() { 42 | return openId; 43 | } 44 | 45 | public void setOpenId(String openId) { 46 | this.openId = openId; 47 | } 48 | 49 | public SendMessageType getType() { 50 | return type; 51 | } 52 | 53 | public void setType(SendMessageType type) { 54 | this.type = type; 55 | } 56 | 57 | public String getContent() { 58 | return content; 59 | } 60 | 61 | public void setContent(String content) { 62 | this.content = content; 63 | } 64 | 65 | public String getMediaId() { 66 | return mediaId; 67 | } 68 | 69 | public void setMediaId(String mediaId) { 70 | this.mediaId = mediaId; 71 | } 72 | 73 | public String getCardId() { 74 | return cardId; 75 | } 76 | 77 | public void setCardId(String cardId) { 78 | this.cardId = cardId; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "SendPreviewMessage{" + 84 | "type=" + type + 85 | ", openId='" + openId + '\'' + 86 | ", content='" + content + '\'' + 87 | ", mediaId='" + mediaId + '\'' + 88 | ", cardId='" + cardId + '\'' + 89 | '}'; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/customer/MsgRecord.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.customer; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * 客服聊天记录 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 7/11/15 11 | */ 12 | public class MsgRecord implements Serializable { 13 | 14 | private static final long serialVersionUID = -4343547752472563821L; 15 | 16 | /** 17 | * 客服帐号 18 | */ 19 | private String worker; 20 | 21 | /** 22 | * 用户openid 23 | */ 24 | private String openid; 25 | 26 | /** 27 | * 客服操作码: 28 | 1000 创建未接入会话 29 | 1001 接入会话 30 | 1002 主动发起会话 31 | 1003 转接会话 32 | 1004 关闭会话 33 | 1005 抢接会话 34 | 2001 公众号收到消息 35 | 2002 客服发送消息 36 | 2003 客服收到消息 37 | */ 38 | private String opercode; 39 | 40 | /** 41 | * 操作时间 42 | */ 43 | private Date time; 44 | 45 | /** 46 | * 聊天记录 47 | */ 48 | private String text; 49 | 50 | public String getWorker() { 51 | return worker; 52 | } 53 | 54 | public void setWorker(String worker) { 55 | this.worker = worker; 56 | } 57 | 58 | public String getOpenid() { 59 | return openid; 60 | } 61 | 62 | public void setOpenid(String openid) { 63 | this.openid = openid; 64 | } 65 | 66 | public String getOpercode() { 67 | return opercode; 68 | } 69 | 70 | public void setOpercode(String opercode) { 71 | this.opercode = opercode; 72 | } 73 | 74 | public Date getTime() { 75 | return time; 76 | } 77 | 78 | public void setTime(Date time) { 79 | this.time = time; 80 | } 81 | 82 | public String getText() { 83 | return text; 84 | } 85 | 86 | public void setText(String text) { 87 | this.text = text; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "CsMsgRecord{" + 93 | "worker='" + worker + '\'' + 94 | ", openid='" + openid + '\'' + 95 | ", opercode='" + opercode + '\'' + 96 | ", time=" + time + 97 | ", text='" + text + '\'' + 98 | '}'; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Component.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import java.io.InputStream; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | 7 | /** 8 | * 微信组件需要继承该类 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * Date: 18/11/15 12 | * @since 1.4.0 13 | */ 14 | public abstract class Component { 15 | 16 | protected Wechat wechat; 17 | 18 | /** 19 | * 加载accessToken 20 | * @return accessToken 21 | */ 22 | protected String loadAccessToken(){ 23 | return wechat.loadAccessToken(); 24 | } 25 | 26 | /** 27 | * POST请求 28 | * @param url URL 29 | * @param params 请求参数 30 | * @return Map结果集 31 | */ 32 | protected Map doPost(String url, Map params){ 33 | return wechat.doPost(url, params); 34 | } 35 | 36 | /** 37 | * POST请求 38 | * @param url URL 39 | * @param body 请求body 40 | * @return Map结果集 41 | */ 42 | protected Map doPost(String url, String body){ 43 | return wechat.doPost(url, body); 44 | } 45 | 46 | /** 47 | * GET请求 48 | * @param url URL 49 | * @return Map结果集 50 | */ 51 | protected Map doGet(String url){ 52 | return wechat.doGet(url); 53 | } 54 | 55 | /** 56 | * 上传请求 57 | * @param url URL 58 | * @param fieldName 上传字段名 59 | * @param fileName 上传文件名 60 | * @param input 输入流 61 | * @return Map结果集 62 | */ 63 | protected Map doUpload(String url, String fieldName, String fileName, InputStream input){ 64 | return wechat.doUpload(url, fieldName, fileName, input, Collections.emptyMap()); 65 | } 66 | 67 | /** 68 | * 上传请求 69 | * @param url URL 70 | * @param fieldName 上传字段名 71 | * @param fileName 上传文件名 72 | * @param input 输入流 73 | * @param params 其他参数 74 | * @return Map结果集 75 | */ 76 | protected Map doUpload(String url, String fieldName, String fileName, InputStream input, Map params){ 77 | return wechat.doUpload(url, fieldName, fileName, input, params); 78 | } 79 | 80 | /** 81 | * 异步执行 82 | * @param af 执行函数 83 | * @param 范型结果 84 | */ 85 | protected void doAsync(AsyncFunction af){ 86 | wechat.doAsync(af); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/interfaces/InterfaceSummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.interfaces; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * 接口分析数据 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 20/11/15 11 | */ 12 | public class InterfaceSummary implements Serializable { 13 | 14 | private static final long serialVersionUID = 6383545997391007323L; 15 | 16 | @JsonProperty("ref_date") 17 | private String date; 18 | 19 | /** 20 | * 通过服务器配置地址获得消息后,被动回复用户消息的次数 21 | */ 22 | @JsonProperty("callback_count") 23 | private Integer callbackCount; 24 | 25 | /** 26 | * 上述动作的失败次数 27 | */ 28 | @JsonProperty("fail_count") 29 | private Integer failCount; 30 | 31 | /** 32 | * 总耗时,除以callbackCount即为平均耗时 33 | */ 34 | @JsonProperty("total_time_cost") 35 | private Integer totalTime; 36 | 37 | /** 38 | * 最大耗时 39 | */ 40 | @JsonProperty("max_time_cost") 41 | private Integer maxTime; 42 | 43 | public String getDate() { 44 | return date; 45 | } 46 | 47 | public void setDate(String date) { 48 | this.date = date; 49 | } 50 | 51 | public Integer getCallbackCount() { 52 | return callbackCount; 53 | } 54 | 55 | public void setCallbackCount(Integer callbackCount) { 56 | this.callbackCount = callbackCount; 57 | } 58 | 59 | public Integer getFailCount() { 60 | return failCount; 61 | } 62 | 63 | public void setFailCount(Integer failCount) { 64 | this.failCount = failCount; 65 | } 66 | 67 | public Integer getTotalTime() { 68 | return totalTime; 69 | } 70 | 71 | public void setTotalTime(Integer totalTime) { 72 | this.totalTime = totalTime; 73 | } 74 | 75 | public Integer getMaxTime() { 76 | return maxTime; 77 | } 78 | 79 | public void setMaxTime(Integer maxTime) { 80 | this.maxTime = maxTime; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return "InterfaceSummary{" + 86 | "date='" + date + '\'' + 87 | ", callbackCount=" + callbackCount + 88 | ", failCount=" + failCount + 89 | ", totalTime=" + totalTime + 90 | ", maxTime=" + maxTime + 91 | '}'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/menu/MenuType.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.menu; 2 | 3 | /** 4 | * 菜单类型 5 | */ 6 | public enum MenuType { 7 | 8 | /** 9 | * 点击推事件: 10 | * 微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互; 11 | */ 12 | CLICK("click", "点击推事件"), 13 | 14 | /** 15 | * 跳转URL: 16 | * 用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。 17 | */ 18 | VIEW("view", "跳转URL"), 19 | 20 | /** 21 | * 扫码推事件: 22 | * 用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。 23 | */ 24 | SCAN_CODE("scancode_push", "扫码推事件"), 25 | 26 | /** 27 | * 扫码推事件且弹出“消息接收中”提示框: 28 | * 用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。 29 | */ 30 | SCAN_CODE_WAIT_MSG("scancode_waitmsg", "扫码推事件且弹出“消息接收中”提示框"), 31 | 32 | /** 33 | * 弹出系统拍照发图: 34 | * 用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。 35 | */ 36 | PIC_SYS_PHOTO("pic_sysphoto", "弹出系统拍照发图"), 37 | 38 | /** 39 | * 弹出拍照或者相册发图: 40 | * 用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。 41 | */ 42 | PIC_PHOTO_OR_ALBUM("pic_photo_or_album", "弹出拍照或者相册发图"), 43 | 44 | /** 45 | * 弹出微信相册发图器: 46 | * 用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。 47 | */ 48 | PIC_WEIXIN("pic_weixin", "弹出微信相册发图器"), 49 | 50 | /** 51 | * 弹出地理位置选择器: 52 | * 用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。 53 | */ 54 | LOCATION_SELECT("location_select", "弹出地理位置选择器"), 55 | 56 | /** 57 | * 下发消息(除文本消息): 58 | * 用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。 59 | */ 60 | MEDIA_ID("media_id", "下发消息"), 61 | 62 | /** 63 | * 跳转图文消息URL: 64 | * 用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。 65 | */ 66 | VIEW_LIMITED("view_limited", "跳转图文消息URL"); 67 | 68 | public String value; 69 | 70 | public String desc; 71 | 72 | private MenuType(String value, String desc){ 73 | this.value = value; 74 | this.desc = desc; 75 | } 76 | 77 | public String value(){ 78 | return value; 79 | } 80 | 81 | public String desc(){ 82 | return desc; 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/user/UserSummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.UserSourceDeserializer; 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 用户增量数据 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 20/11/15 13 | */ 14 | public class UserSummary implements Serializable { 15 | 16 | private static final long serialVersionUID = -6612438038581745613L; 17 | 18 | /** 19 | * 日期: yyyy-MM-dd 20 | */ 21 | @JsonProperty("ref_date") 22 | private String date; 23 | 24 | /** 25 | * 用户的渠道 26 | * @see UserSource 27 | */ 28 | @JsonProperty("user_source") 29 | @JsonDeserialize(using = UserSourceDeserializer.class) 30 | private UserSource source; 31 | 32 | /** 33 | * 新增的用户数量 34 | */ 35 | @JsonProperty("new_user") 36 | private Integer newCount; 37 | 38 | /** 39 | * 取消关注的用户数量,new_user减去cancel_user即为净增用户数量 40 | */ 41 | @JsonProperty("cancel_user") 42 | private Integer cancelCount; 43 | 44 | public String getDate() { 45 | return date; 46 | } 47 | 48 | public void setDate(String date) { 49 | this.date = date; 50 | } 51 | 52 | public UserSource getSource() { 53 | return source; 54 | } 55 | 56 | public void setSource(UserSource source) { 57 | this.source = source; 58 | } 59 | 60 | public Integer getNewCount() { 61 | return newCount; 62 | } 63 | 64 | public void setNewCount(Integer newCount) { 65 | this.newCount = newCount; 66 | } 67 | 68 | public Integer getCancelCount() { 69 | return cancelCount; 70 | } 71 | 72 | public void setCancelCount(Integer cancelCount) { 73 | this.cancelCount = cancelCount; 74 | } 75 | 76 | /** 77 | * 获取用户增量(负数表示取消关注的人多于新增的人) 78 | * newUser - cancelUser 79 | * @return 用户增量 80 | */ 81 | public Integer getIncrement(){ 82 | return newCount - cancelCount; 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "UserSummary{" + 88 | "date='" + date + '\'' + 89 | ", source=" + source + 90 | ", newCount=" + newCount + 91 | ", cancelCount=" + cancelCount + 92 | '}'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/WechatBuilder.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import me.hao0.wechat.loader.AccessTokenLoader; 4 | import me.hao0.wechat.loader.TicketLoader; 5 | import java.util.concurrent.ExecutorService; 6 | import static me.hao0.common.util.Preconditions.*; 7 | 8 | /** 9 | * 微信组件库配置构建器 10 | * Author: haolin 11 | * Email: haolin.h0@gmail.com 12 | * Date: 14/11/15 13 | * @since 1.4.0 14 | */ 15 | public final class WechatBuilder { 16 | 17 | private Wechat wechat; 18 | 19 | /** 20 | * 创建一个WechatBuilder 21 | * @param appId 微信appId 22 | * @param appSecret 微信appSecret 23 | * @return a builder 24 | */ 25 | public static WechatBuilder newBuilder(String appId, String appSecret){ 26 | checkNotNullAndEmpty(appId, "appId"); 27 | checkNotNullAndEmpty(appSecret, "appSecret"); 28 | 29 | WechatBuilder builder = new WechatBuilder(); 30 | builder.wechat = new Wechat(appId, appSecret); 31 | return builder; 32 | } 33 | 34 | /** 35 | * 配置微信APP令牌(Token) 36 | * @param token 微信APP令牌(Token) 37 | * @return this 38 | */ 39 | public WechatBuilder token(String token){ 40 | checkNotNullAndEmpty(token, "token"); 41 | wechat.appToken = token; 42 | return this; 43 | } 44 | 45 | /** 46 | * 配置加密消息的Key 47 | * @param msgKey 加密消息的Key 48 | * @return this 49 | */ 50 | public WechatBuilder msgKey(String msgKey){ 51 | checkNotNullAndEmpty(msgKey, "msgKey"); 52 | wechat.msgKey = msgKey; 53 | return this; 54 | } 55 | 56 | /** 57 | * 配置accessToken加载器 58 | * @param accessTokenLoader accessToken加载器 59 | * @return return this 60 | */ 61 | public WechatBuilder accessTokenLoader(AccessTokenLoader accessTokenLoader){ 62 | checkNotNull(accessTokenLoader, "accessTokenLoader can't be null"); 63 | wechat.tokenLoader = accessTokenLoader; 64 | return this; 65 | } 66 | 67 | /** 68 | * 配置ticket加载器 69 | * @param ticketLoader ticket加载器 70 | * @return this 71 | */ 72 | public WechatBuilder ticketLoader(TicketLoader ticketLoader){ 73 | checkNotNull(ticketLoader, "ticketLoader can't be null"); 74 | wechat.ticketLoader = ticketLoader; 75 | return this; 76 | } 77 | 78 | /** 79 | * 设置ExecutorService,用于异步调用 80 | * @param executor 异步执行器 81 | * @return this 82 | */ 83 | public WechatBuilder executor(ExecutorService executor){ 84 | checkNotNull(executor, "executor can't be null"); 85 | wechat.executor = executor; 86 | return this; 87 | } 88 | 89 | /** 90 | * 返回最终配置好的Wechat对象 91 | * @return Wechat对象 92 | */ 93 | public Wechat build(){ 94 | return wechat; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/user/UserInfo.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 用户信息(未关注,但已手动授权) 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | */ 12 | public class UserInfo implements Serializable { 13 | 14 | @JsonProperty("openid") 15 | private String openId; 16 | 17 | @JsonProperty("nickname") 18 | private String nickName; 19 | 20 | /** 21 | * 0未知,1男,2女 22 | */ 23 | private Integer sex; 24 | 25 | private String city; 26 | 27 | private String province; 28 | 29 | private String country; 30 | 31 | @JsonProperty("headimgurl") 32 | private String headImgUrl; 33 | 34 | @JsonProperty("unionid") 35 | private String unionId; 36 | 37 | /** 38 | * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) 39 | */ 40 | private String[] privilege; 41 | 42 | public String getOpenId() { 43 | return openId; 44 | } 45 | 46 | public void setOpenId(String openId) { 47 | this.openId = openId; 48 | } 49 | 50 | public String getNickName() { 51 | return nickName; 52 | } 53 | 54 | public void setNickName(String nickName) { 55 | this.nickName = nickName; 56 | } 57 | 58 | public Integer getSex() { 59 | return sex; 60 | } 61 | 62 | public void setSex(Integer sex) { 63 | this.sex = sex; 64 | } 65 | 66 | public String getCity() { 67 | return city; 68 | } 69 | 70 | public void setCity(String city) { 71 | this.city = city; 72 | } 73 | 74 | public String getProvince() { 75 | return province; 76 | } 77 | 78 | public void setProvince(String province) { 79 | this.province = province; 80 | } 81 | 82 | public String getCountry() { 83 | return country; 84 | } 85 | 86 | public void setCountry(String country) { 87 | this.country = country; 88 | } 89 | 90 | public String getHeadImgUrl() { 91 | return headImgUrl; 92 | } 93 | 94 | public void setHeadImgUrl(String headImgUrl) { 95 | this.headImgUrl = headImgUrl; 96 | } 97 | 98 | public String getUnionId() { 99 | return unionId; 100 | } 101 | 102 | public void setUnionId(String unionId) { 103 | this.unionId = unionId; 104 | } 105 | 106 | public String[] getPrivilege() { 107 | return privilege; 108 | } 109 | 110 | public void setPrivilege(String[] privilege) { 111 | this.privilege = privilege; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return "UserInfo{" + 117 | "openId='" + openId + '\'' + 118 | ", nickName='" + nickName + '\'' + 119 | ", sex=" + sex + 120 | ", city='" + city + '\'' + 121 | ", province='" + province + '\'' + 122 | ", country='" + country + '\'' + 123 | ", headImgUrl='" + headImgUrl + '\'' + 124 | ", unionId='" + unionId + '\'' + 125 | ", privilege=" + Arrays.toString(privilege) + 126 | '}'; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/data/article/CommonSummary.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.data.article; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * 基本统计 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 20/11/15 11 | */ 12 | public abstract class CommonSummary implements Serializable { 13 | 14 | /** 15 | * 图文页(点击群发图文卡片进入的页面)的阅读人数 16 | */ 17 | @JsonProperty("int_page_read_user") 18 | private Integer readUser; 19 | 20 | /** 21 | * 图文页的阅读次数 22 | */ 23 | @JsonProperty("int_page_read_count") 24 | private Integer readCount; 25 | 26 | /** 27 | * 原文页(点击图文页“阅读原文”进入的页面)的阅读人数,无原文页时此处数据为0 28 | */ 29 | @JsonProperty("ori_page_read_user") 30 | private Integer originReadUser; 31 | 32 | /** 33 | * 原文页的阅读次数 34 | */ 35 | @JsonProperty("ori_page_read_count") 36 | private Integer originReadCount; 37 | 38 | /** 39 | * 分享的人数 40 | */ 41 | @JsonProperty("share_user") 42 | private Integer shareUser; 43 | 44 | /** 45 | * 分享的次数 46 | */ 47 | @JsonProperty("share_count") 48 | private Integer shareCount; 49 | 50 | /** 51 | * 收藏人数 52 | */ 53 | @JsonProperty("add_to_fav_user") 54 | private Integer favUser; 55 | 56 | /** 57 | * 收藏次数 58 | */ 59 | @JsonProperty("add_to_fav_count") 60 | private Integer favCount; 61 | 62 | public Integer getReadCount() { 63 | return readCount; 64 | } 65 | 66 | public void setReadCount(Integer readCount) { 67 | this.readCount = readCount; 68 | } 69 | 70 | public Integer getOriginReadUser() { 71 | return originReadUser; 72 | } 73 | 74 | public void setOriginReadUser(Integer originReadUser) { 75 | this.originReadUser = originReadUser; 76 | } 77 | 78 | public Integer getOriginReadCount() { 79 | return originReadCount; 80 | } 81 | 82 | public void setOriginReadCount(Integer originReadCount) { 83 | this.originReadCount = originReadCount; 84 | } 85 | 86 | public Integer getShareUser() { 87 | return shareUser; 88 | } 89 | 90 | public void setShareUser(Integer shareUser) { 91 | this.shareUser = shareUser; 92 | } 93 | 94 | public Integer getShareCount() { 95 | return shareCount; 96 | } 97 | 98 | public void setShareCount(Integer shareCount) { 99 | this.shareCount = shareCount; 100 | } 101 | 102 | public Integer getFavUser() { 103 | return favUser; 104 | } 105 | 106 | public void setFavUser(Integer favUser) { 107 | this.favUser = favUser; 108 | } 109 | 110 | public Integer getFavCount() { 111 | return favCount; 112 | } 113 | 114 | public void setFavCount(Integer favCount) { 115 | this.favCount = favCount; 116 | } 117 | 118 | @Override 119 | public String toString() { 120 | return "CommonSummary{" + 121 | "readUser=" + readUser + 122 | ", readCount=" + readCount + 123 | ", originReadUser=" + originReadUser + 124 | ", originReadCount=" + originReadCount + 125 | ", shareUser=" + shareUser + 126 | ", shareCount=" + shareCount + 127 | ", favUser=" + favUser + 128 | ", favCount=" + favCount + 129 | '}'; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/material/NewsContentItem.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.material; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 图文素材类 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * Date: 12/11/15 12 | */ 13 | public class NewsContentItem implements Serializable { 14 | 15 | private static final long serialVersionUID = 8483540691949616866L; 16 | 17 | /** 18 | * 图文消息的标题 19 | */ 20 | private String title; 21 | 22 | /** 23 | * 图文消息的封面图片素材id(必须是永久mediaID) 24 | */ 25 | @JsonProperty("thumb_media_id") 26 | private String thumbMediaId; 27 | 28 | /** 29 | * 是否显示封面,0为false,即不显示,1为true,即显示 30 | */ 31 | @JsonProperty("show_cover_pic") 32 | private Integer showCoverPic; 33 | 34 | /** 35 | * 作者 36 | */ 37 | private String author; 38 | 39 | /** 40 | * 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空 41 | */ 42 | private String digest; 43 | 44 | /** 45 | * 内容 46 | */ 47 | private String content; 48 | 49 | /** 50 | * URL 51 | */ 52 | private String url; 53 | 54 | /** 55 | * 图文消息的原文地址,即点击“阅读原文”后的URL 56 | */ 57 | @JsonProperty("content_source_url") 58 | private String contentSourceUrl; 59 | 60 | public String getTitle() { 61 | return title; 62 | } 63 | 64 | public void setTitle(String title) { 65 | this.title = title; 66 | } 67 | 68 | public String getThumbMediaId() { 69 | return thumbMediaId; 70 | } 71 | 72 | public void setThumbMediaId(String thumbMediaId) { 73 | this.thumbMediaId = thumbMediaId; 74 | } 75 | 76 | public Integer getShowCoverPic() { 77 | return showCoverPic; 78 | } 79 | 80 | public void setShowCoverPic(Integer showCoverPic) { 81 | this.showCoverPic = showCoverPic; 82 | } 83 | 84 | public String getAuthor() { 85 | return author; 86 | } 87 | 88 | public void setAuthor(String author) { 89 | this.author = author; 90 | } 91 | 92 | public String getDigest() { 93 | return digest; 94 | } 95 | 96 | public void setDigest(String digest) { 97 | this.digest = digest; 98 | } 99 | 100 | public String getContent() { 101 | return content; 102 | } 103 | 104 | public void setContent(String content) { 105 | this.content = content; 106 | } 107 | 108 | public String getUrl() { 109 | return url; 110 | } 111 | 112 | public void setUrl(String url) { 113 | this.url = url; 114 | } 115 | 116 | public String getContentSourceUrl() { 117 | return contentSourceUrl; 118 | } 119 | 120 | public void setContentSourceUrl(String contentSourceUrl) { 121 | this.contentSourceUrl = contentSourceUrl; 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "NewsContentItem{" + 127 | "title='" + title + '\'' + 128 | ", thumbMediaId='" + thumbMediaId + '\'' + 129 | ", showCoverPic=" + showCoverPic + 130 | ", author='" + author + '\'' + 131 | ", digest='" + digest + '\'' + 132 | ", content='" + content + '\'' + 133 | ", url='" + url + '\'' + 134 | ", contentSourceUrl='" + contentSourceUrl + '\'' + 135 | '}'; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/base/AuthAccessToken.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.base; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 用户同意授权后,通过code获取的访问Token 9 | * Author: haolin 10 | * Email: haolin.h0@gmail.com 11 | * @since 1.9.1 12 | *

13 | * 参考链接 14 | *

15 | */ 16 | public class AuthAccessToken implements Serializable { 17 | 18 | private static final long serialVersionUID = 7082882275191271333L; 19 | 20 | /** 21 | * accessToken 22 | */ 23 | @JsonProperty("access_token") 24 | private String accessToken; 25 | 26 | /** 27 | * 用户刷新access_token 28 | */ 29 | @JsonProperty("refresh_token") 30 | private String refreshToken; 31 | 32 | /** 33 | * 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID 34 | */ 35 | @JsonProperty("openid") 36 | private String openId; 37 | 38 | /** 39 | * 用户授权的作用域,使用逗号(,)分隔 40 | */ 41 | private String scope; 42 | 43 | /** 44 | * 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 45 | *

46 | * 详见 47 | *

48 | */ 49 | @JsonProperty("unionid") 50 | private String unionId; 51 | 52 | /** 53 | * 有效时间(s) 54 | */ 55 | @JsonProperty("expires_in") 56 | private Integer expire; 57 | 58 | /** 59 | * 过期时刻(ms) 60 | */ 61 | private Long expiredAt; 62 | 63 | public String getAccessToken() { 64 | return accessToken; 65 | } 66 | 67 | public void setAccessToken(String accessToken) { 68 | this.accessToken = accessToken; 69 | } 70 | 71 | public String getRefreshToken() { 72 | return refreshToken; 73 | } 74 | 75 | public void setRefreshToken(String refreshToken) { 76 | this.refreshToken = refreshToken; 77 | } 78 | 79 | public String getOpenId() { 80 | return openId; 81 | } 82 | 83 | public void setOpenId(String openId) { 84 | this.openId = openId; 85 | } 86 | 87 | public String getScope() { 88 | return scope; 89 | } 90 | 91 | public void setScope(String scope) { 92 | this.scope = scope; 93 | } 94 | 95 | public String getUnionId() { 96 | return unionId; 97 | } 98 | 99 | public void setUnionId(String unionId) { 100 | this.unionId = unionId; 101 | } 102 | 103 | public Integer getExpire() { 104 | return expire; 105 | } 106 | 107 | public void setExpire(Integer expire) { 108 | this.expire = expire; 109 | } 110 | 111 | public Long getExpiredAt() { 112 | return expiredAt; 113 | } 114 | 115 | public void setExpiredAt(Long expiredAt) { 116 | this.expiredAt = expiredAt; 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return "AuthAccessToken{" + 122 | "accessToken='" + accessToken + '\'' + 123 | ", refreshToken='" + refreshToken + '\'' + 124 | ", openId='" + openId + '\'' + 125 | ", scope='" + scope + '\'' + 126 | ", unionId='" + unionId + '\'' + 127 | ", expire=" + expire + 128 | ", expiredAt=" + expiredAt + 129 | '}'; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/MenuBuilder.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import me.hao0.wechat.model.menu.Menu; 5 | import me.hao0.wechat.model.menu.MenuType; 6 | import me.hao0.common.json.Jsons; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * 菜单构建器 12 | * Author: haolin 13 | * Email: haolin.h0@gmail.com 14 | * Date: 18/11/15 15 | * @since 1.4.0 16 | */ 17 | public final class MenuBuilder { 18 | 19 | @JsonProperty("button") 20 | private List menus = new ArrayList<>(); 21 | 22 | private MenuBuilder() { 23 | } 24 | 25 | public static MenuBuilder newBuilder() { 26 | return new MenuBuilder(); 27 | } 28 | 29 | /** 30 | * 创建一个一级菜单 31 | * 32 | * @param m 菜单对象 33 | * @return this 34 | */ 35 | public MenuBuilder menu(Menu m) { 36 | menus.add(m); 37 | return this; 38 | } 39 | 40 | /** 41 | * 创建一个CLICK一级菜单 42 | * 43 | * @param name 名称 44 | * @param key 键 45 | * @return this 46 | */ 47 | public MenuBuilder click(String name, String key) { 48 | Menu m = newClickMenu(name, key); 49 | menus.add(m); 50 | return this; 51 | } 52 | 53 | /** 54 | * 创建一个CLICK二级菜单 55 | * 56 | * @param parent 父级菜单 57 | * @param name 名称 58 | * @param key 键 59 | * @return this 60 | */ 61 | public MenuBuilder click(Menu parent, String name, String key) { 62 | Menu m = newClickMenu(name, key); 63 | parent.getChildren().add(m); 64 | return this; 65 | } 66 | 67 | /** 68 | * 创建一个VIEW一级菜单 69 | * 70 | * @param name 名称 71 | * @param url 链接 72 | * @return this 73 | */ 74 | public MenuBuilder view(String name, String url) { 75 | Menu m = newViewMenu(name, url); 76 | menus.add(m); 77 | return this; 78 | } 79 | 80 | /** 81 | * 创建一个VIEW二级菜单 82 | * 83 | * @param parent 父级菜单 84 | * @param name 名称 85 | * @param url 链接 86 | * @return this 87 | */ 88 | public MenuBuilder view(Menu parent, String name, String url) { 89 | Menu m = newViewMenu(name, url); 90 | parent.getChildren().add(m); 91 | return this; 92 | } 93 | 94 | /** 95 | * 创建一个VIEW菜单 96 | * 97 | * @param name 名称 98 | * @param url 链接 99 | * @return Menu对象 100 | */ 101 | public Menu newViewMenu(String name, String url) { 102 | Menu m = new Menu(); 103 | m.setName(name); 104 | m.setType(MenuType.VIEW.value()); 105 | m.setUrl(url); 106 | return m; 107 | } 108 | 109 | /** 110 | * 创建一个CLICK菜单 111 | * 112 | * @param name 名称 113 | * @param key 键 114 | * @return Menu对象 115 | */ 116 | public Menu newClickMenu(String name, String key) { 117 | Menu m = new Menu(); 118 | m.setName(name); 119 | m.setType(MenuType.CLICK.value()); 120 | m.setKey(key); 121 | return m; 122 | } 123 | 124 | /** 125 | * 创建一个一级菜单 126 | * 127 | * @param name 名称 128 | * @return Menu对象 129 | */ 130 | public Menu newParentMenu(String name) { 131 | Menu m = new Menu(); 132 | m.setName(name); 133 | return m; 134 | } 135 | 136 | /** 137 | * 返回菜单的json数据 138 | * 139 | * @return 菜单json数据 140 | */ 141 | public String build() { 142 | return Jsons.EXCLUDE_EMPTY.toJson(this); 143 | } 144 | } -------------------------------------------------------------------------------- /src/test/java/me/hao0/wechat/FuturesTest.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat; 2 | 3 | import com.google.common.util.concurrent.AsyncFunction; 4 | import com.google.common.util.concurrent.FutureCallback; 5 | import com.google.common.util.concurrent.Futures; 6 | import com.google.common.util.concurrent.ListenableFuture; 7 | import com.google.common.util.concurrent.ListeningExecutorService; 8 | import com.google.common.util.concurrent.MoreExecutors; 9 | import org.junit.Test; 10 | import java.util.List; 11 | import java.util.concurrent.Callable; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.concurrent.Executors; 14 | 15 | /** 16 | * Author: haolin 17 | * Email: haolin.h0@gmail.com 18 | * Date: 17/11/15 19 | */ 20 | public class FuturesTest { 21 | 22 | @Test 23 | public void testGuavaFuture1() throws ExecutionException, InterruptedException { 24 | 25 | ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); 26 | 27 | ListenableFuture future1 = service.submit(new Callable() { 28 | public Integer call() throws InterruptedException { 29 | Thread.sleep(1000); 30 | System.out.println("call future 1."); 31 | return 1; 32 | } 33 | }); 34 | 35 | ListenableFuture future2 = service.submit(new Callable() { 36 | public Integer call() throws InterruptedException { 37 | Thread.sleep(1000); 38 | System.out.println("call future 2."); 39 | // throw new RuntimeException("----call future 2."); 40 | return 2; 41 | } 42 | }); 43 | 44 | final ListenableFuture allFutures = Futures.allAsList(future1, future2); 45 | 46 | final ListenableFuture transform = Futures.transform(allFutures, new AsyncFunction, Boolean>() { 47 | @Override 48 | public ListenableFuture apply(List results) throws Exception { 49 | return Futures.immediateFuture(String.format("success future:%d", results.size())); 50 | } 51 | }); 52 | 53 | Futures.addCallback(transform, new FutureCallback() { 54 | 55 | public void onSuccess(Object result) { 56 | System.out.println(result.getClass()); 57 | System.out.printf("success with: %s%n", result); 58 | } 59 | 60 | public void onFailure(Throwable thrown) { 61 | System.out.printf("onFailure%s%n", thrown.getMessage()); 62 | } 63 | }); 64 | 65 | System.out.println("main..."); 66 | 67 | System.out.println(transform.get()); 68 | } 69 | 70 | @Test 71 | public void testGuavaFuture2() throws ExecutionException, InterruptedException { 72 | 73 | ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); 74 | 75 | ListenableFuture future1 = service.submit(new Callable() { 76 | public Integer call() throws InterruptedException { 77 | Thread.sleep(1000); 78 | System.out.println("call future 1."); 79 | return 1; 80 | } 81 | }); 82 | 83 | 84 | Futures.addCallback(future1, new FutureCallback() { 85 | 86 | public void onSuccess(Object result) { 87 | System.out.println(result.getClass()); 88 | System.out.printf("success with: %s%n", result); 89 | } 90 | 91 | public void onFailure(Throwable thrown) { 92 | System.out.printf("onFailure%s%n", thrown.getMessage()); 93 | } 94 | }); 95 | 96 | System.out.println("main..."); 97 | 98 | future1.get(); 99 | 100 | System.out.println("main end"); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/user/User.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import me.hao0.wechat.serializer.DateDeserializer; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | /** 11 | * 用户信息 12 | * Author: haolin 13 | * Email: haolin.h0@gmail.com 14 | * Date: 7/11/15 15 | */ 16 | public class User implements Serializable { 17 | 18 | /** 19 | * 0未关注,1已关注 20 | */ 21 | private Integer subscribe; 22 | 23 | @JsonProperty("openid") 24 | private String openId; 25 | 26 | @JsonProperty("nickname") 27 | private String nickName; 28 | 29 | /** 30 | * 0未知,1男,2女 31 | */ 32 | private Integer sex; 33 | 34 | private String city; 35 | 36 | private String province; 37 | 38 | private String country; 39 | 40 | @JsonProperty("headimgurl") 41 | private String headImgUrl; 42 | 43 | @JsonProperty("subscribe_time") 44 | @JsonDeserialize(using = DateDeserializer.class) 45 | private Date subscribeTime; 46 | 47 | @JsonProperty("unionid") 48 | private String unionId; 49 | 50 | private String remark; 51 | 52 | @JsonProperty("groupid") 53 | private Integer groupId; 54 | 55 | public Integer getSubscribe() { 56 | return subscribe; 57 | } 58 | 59 | public void setSubscribe(Integer subscribe) { 60 | this.subscribe = subscribe; 61 | } 62 | 63 | public String getOpenId() { 64 | return openId; 65 | } 66 | 67 | public void setOpenId(String openId) { 68 | this.openId = openId; 69 | } 70 | 71 | public String getNickName() { 72 | return nickName; 73 | } 74 | 75 | public void setNickName(String nickName) { 76 | this.nickName = nickName; 77 | } 78 | 79 | public Integer getSex() { 80 | return sex; 81 | } 82 | 83 | public void setSex(Integer sex) { 84 | this.sex = sex; 85 | } 86 | 87 | public String getCity() { 88 | return city; 89 | } 90 | 91 | public void setCity(String city) { 92 | this.city = city; 93 | } 94 | 95 | public String getProvince() { 96 | return province; 97 | } 98 | 99 | public void setProvince(String province) { 100 | this.province = province; 101 | } 102 | 103 | public String getCountry() { 104 | return country; 105 | } 106 | 107 | public void setCountry(String country) { 108 | this.country = country; 109 | } 110 | 111 | public String getHeadImgUrl() { 112 | return headImgUrl; 113 | } 114 | 115 | public void setHeadImgUrl(String headImgUrl) { 116 | this.headImgUrl = headImgUrl; 117 | } 118 | 119 | public Date getSubscribeTime() { 120 | return subscribeTime; 121 | } 122 | 123 | public void setSubscribeTime(Date subscribeTime) { 124 | this.subscribeTime = subscribeTime; 125 | } 126 | 127 | public String getUnionId() { 128 | return unionId; 129 | } 130 | 131 | public void setUnionId(String unionId) { 132 | this.unionId = unionId; 133 | } 134 | 135 | public String getRemark() { 136 | return remark; 137 | } 138 | 139 | public void setRemark(String remark) { 140 | this.remark = remark; 141 | } 142 | 143 | public Integer getGroupId() { 144 | return groupId; 145 | } 146 | 147 | public void setGroupId(Integer groupId) { 148 | this.groupId = groupId; 149 | } 150 | 151 | @Override 152 | public String toString() { 153 | return "User{" + 154 | "subscribe=" + subscribe + 155 | ", openId='" + openId + '\'' + 156 | ", nickName='" + nickName + '\'' + 157 | ", sex=" + sex + 158 | ", city='" + city + '\'' + 159 | ", province='" + province + '\'' + 160 | ", country='" + country + '\'' + 161 | ", headImgUrl='" + headImgUrl + '\'' + 162 | ", subscribeTime=" + subscribeTime + 163 | ", unionId=" + unionId + 164 | ", remark='" + remark + '\'' + 165 | ", groupId=" + groupId + 166 | '}'; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/model/message/send/SendMessage.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.model.message.send; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** 7 | * 发送消息 8 | * Author: haolin 9 | * Email: haolin.h0@gmail.com 10 | * Date: 8/11/15 11 | */ 12 | public class SendMessage implements Serializable { 13 | 14 | private static final long serialVersionUID = 8803986913007718602L; 15 | 16 | /** 17 | * 消息类型: 18 | * @see SendMessageType 19 | */ 20 | private SendMessageType type; 21 | 22 | /** 23 | * 发送范围 24 | * @see SendMessageScope 25 | */ 26 | private SendMessageScope scope; 27 | 28 | /** 29 | * 分组群发消息时 30 | * 分组ID 31 | */ 32 | private Integer groupId; 33 | 34 | /** 35 | * 分组群发消息时 36 | * 用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户,选择false可根据group_id发送给指定群组的用户 37 | */ 38 | private Boolean isToAll = Boolean.FALSE; 39 | 40 | /** 41 | * OpenID列表群发 42 | * 用户openId列表,与groupId和isToAll排斥 43 | */ 44 | private List openIds; 45 | 46 | /** 47 | * 文本消息内容,当type为文本时 48 | */ 49 | private String content; 50 | 51 | /** 52 | * 用于群发的消息的media_id,当type为语音,图文,视频,图片时 53 | */ 54 | private String mediaId; 55 | 56 | /** 57 | * 卡券ID,当type为卡券时 58 | */ 59 | private String cardId; 60 | 61 | /** 62 | * 视频缩略图的媒体ID 63 | */ 64 | private String thumbMediaId; 65 | 66 | /** 67 | * 消息的标题 68 | */ 69 | private String title; 70 | 71 | /** 72 | * 消息的描述 73 | */ 74 | private String description; 75 | 76 | public SendMessageType getType() { 77 | return type; 78 | } 79 | 80 | public void setType(SendMessageType type) { 81 | this.type = type; 82 | } 83 | 84 | public SendMessageScope getScope() { 85 | return scope; 86 | } 87 | 88 | public void setScope(SendMessageScope scope) { 89 | this.scope = scope; 90 | } 91 | 92 | public Integer getGroupId() { 93 | return groupId; 94 | } 95 | 96 | public void setGroupId(Integer groupId) { 97 | this.groupId = groupId; 98 | } 99 | 100 | public Boolean getIsToAll() { 101 | return isToAll; 102 | } 103 | 104 | public void setIsToAll(Boolean isToAll) { 105 | this.isToAll = isToAll; 106 | } 107 | 108 | public List getOpenIds() { 109 | return openIds; 110 | } 111 | 112 | public void setOpenIds(List openIds) { 113 | this.openIds = openIds; 114 | } 115 | 116 | public String getContent() { 117 | return content; 118 | } 119 | 120 | public void setContent(String content) { 121 | this.content = content; 122 | } 123 | 124 | public String getMediaId() { 125 | return mediaId; 126 | } 127 | 128 | public void setMediaId(String mediaId) { 129 | this.mediaId = mediaId; 130 | } 131 | 132 | public String getCardId() { 133 | return cardId; 134 | } 135 | 136 | public void setCardId(String cardId) { 137 | this.cardId = cardId; 138 | } 139 | 140 | public String getThumbMediaId() { 141 | return thumbMediaId; 142 | } 143 | 144 | public void setThumbMediaId(String thumbMediaId) { 145 | this.thumbMediaId = thumbMediaId; 146 | } 147 | 148 | public String getTitle() { 149 | return title; 150 | } 151 | 152 | public void setTitle(String title) { 153 | this.title = title; 154 | } 155 | 156 | public String getDescription() { 157 | return description; 158 | } 159 | 160 | public void setDescription(String description) { 161 | this.description = description; 162 | } 163 | 164 | @Override 165 | public String toString() { 166 | return "SendMessage{" + 167 | "type=" + type + 168 | ", groupId=" + groupId + 169 | ", isToAll=" + isToAll + 170 | ", content='" + content + '\'' + 171 | ", mediaId='" + mediaId + '\'' + 172 | ", cardId='" + cardId + '\'' + 173 | ", thumbMediaId='" + thumbMediaId + '\'' + 174 | ", title='" + title + '\'' + 175 | ", description='" + description + '\'' + 176 | '}'; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/JsSdks.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.hash.Hashing; 5 | import me.hao0.wechat.model.js.Config; 6 | import me.hao0.wechat.model.js.Ticket; 7 | import me.hao0.wechat.model.js.TicketType; 8 | import java.util.Map; 9 | import static me.hao0.common.util.Preconditions.*; 10 | 11 | /** 12 | * JS-SDK组件 13 | * Author: haolin 14 | * Email: haolin.h0@gmail.com 15 | * Date: 18/11/15 16 | * @since 1.4.0 17 | */ 18 | public final class JsSdks extends Component { 19 | 20 | /** 21 | * 获取Ticket 22 | */ 23 | private static final String TICKET_GET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="; 24 | 25 | JsSdks(){} 26 | 27 | /** 28 | * 获取临时凭证 29 | * @param type 凭证类型 30 | * @see me.hao0.wechat.model.js.TicketType 31 | * @param cb 回调 32 | */ 33 | public void getTicket(final TicketType type, final Callback cb){ 34 | getTicket(loadAccessToken(), type, cb); 35 | } 36 | 37 | /** 38 | * 获取临时凭证 39 | * @param type 凭证类型 40 | * @see me.hao0.wechat.model.js.TicketType 41 | * @return Ticket对象,或抛WechatException 42 | */ 43 | public Ticket getTicket(TicketType type){ 44 | return getTicket(loadAccessToken(), type); 45 | } 46 | 47 | /** 48 | * 获取临时凭证 49 | * @param accessToken accessToken 50 | * @param type 凭证类型 51 | * @see me.hao0.wechat.model.js.TicketType 52 | * @param cb 回调 53 | */ 54 | public void getTicket(final String accessToken, final TicketType type, final Callback cb){ 55 | doAsync(new AsyncFunction(cb) { 56 | @Override 57 | public Ticket execute() { 58 | return getTicket(accessToken, type); 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * 获取临时凭证 65 | * @param accessToken accessToken 66 | * @param type 凭证类型 67 | * @see me.hao0.wechat.model.js.TicketType 68 | * @return Ticket对象,或抛WechatException 69 | */ 70 | public Ticket getTicket(String accessToken, TicketType type){ 71 | checkNotNullAndEmpty(accessToken, "accessToken"); 72 | checkNotNull(type, "ticket type can't be null"); 73 | 74 | String url = TICKET_GET + accessToken + "&type=" + type.type(); 75 | Map resp = doGet(url); 76 | Ticket t = new Ticket(); 77 | t.setTicket((String)resp.get("ticket")); 78 | Integer expire = (Integer)resp.get("expires_in"); 79 | t.setExpire(expire); 80 | t.setExpireAt(System.currentTimeMillis() + expire * 1000); 81 | t.setType(type); 82 | 83 | return t; 84 | } 85 | 86 | /** 87 | * 获取JSSDK配置信息 88 | * @param nonStr 随机字符串 89 | * @param url 调用JSSDK的页面URL全路径(去除#后的) 90 | * @return Config对象 91 | */ 92 | public Config getConfig(String nonStr, String url){ 93 | return getConfig(wechat.loadTicket(TicketType.JSAPI), nonStr, url); 94 | } 95 | 96 | /** 97 | * 获取JSSDK配置信息 98 | * @param jsApiTicket jsapi凭证 99 | * @param nonStr 随机字符串 100 | * @param url 调用JSSDK的页面URL全路径(去除#后的) 101 | * @return Config对象 102 | */ 103 | public Config getConfig(String jsApiTicket, String nonStr, String url){ 104 | return getConfig(jsApiTicket, nonStr, System.currentTimeMillis() / 1000, url); 105 | } 106 | 107 | /** 108 | * 获取JSSDK配置信息 109 | * @param nonStr 随机字符串 110 | * @param timestamp 时间戳(s) 111 | * @param url 调用JSSDK的页面URL全路径(去除#后的) 112 | * @return Config对象 113 | */ 114 | public Config getConfig(String nonStr, Long timestamp, String url){ 115 | return getConfig(wechat.loadTicket(TicketType.JSAPI), nonStr, timestamp, url); 116 | } 117 | 118 | /** 119 | * 获取JSSDK调用前的配置信息 120 | * @param jsApiTicket jsapi凭证 121 | * @param nonStr 随机字符串 122 | * @param timestamp 时间戳(s) 123 | * @param url 调用JSSDK的页面URL全路径(去除#后的) 124 | * @return Config对象 125 | */ 126 | public Config getConfig(String jsApiTicket, String nonStr, Long timestamp, String url){ 127 | checkNotNullAndEmpty(jsApiTicket, "jsApiTicket"); 128 | checkNotNullAndEmpty(nonStr, "nonStr"); 129 | checkNotNull(timestamp, "timestamp can't be null"); 130 | 131 | String signStr = "jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s"; 132 | signStr = String.format(signStr, jsApiTicket, nonStr, timestamp, url); 133 | String sign = Hashing.sha1().hashString(signStr, Charsets.UTF_8).toString().toLowerCase(); 134 | return new Config(wechat.getAppId(), timestamp, nonStr, sign); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Menus.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import me.hao0.wechat.model.menu.Menu; 5 | import me.hao0.common.json.Jsons; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import static me.hao0.common.util.Preconditions.*; 10 | 11 | /** 12 | * 菜单组件 13 | * Author: haolin 14 | * Email: haolin.h0@gmail.com 15 | * Date: 18/11/15 16 | * @since 1.4.0 17 | */ 18 | public final class Menus extends Component { 19 | 20 | /** 21 | * 查询菜单 22 | */ 23 | private static final String GET = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token="; 24 | 25 | /** 26 | * 创建菜单 27 | */ 28 | private static final String CREATE = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="; 29 | 30 | /** 31 | * 删除菜单 32 | */ 33 | private static final String DELETE = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token="; 34 | 35 | private static final JavaType ARRAY_LIST_MENU_TYPE = Jsons.DEFAULT.createCollectionType(ArrayList.class, Menu.class); 36 | 37 | Menus(){} 38 | 39 | /** 40 | * 查询菜单 41 | * @return 菜单列表 42 | */ 43 | public List get(){ 44 | return get(loadAccessToken()); 45 | } 46 | 47 | /** 48 | * 查询菜单 49 | * @param cb 回调 50 | */ 51 | public void get(Callback> cb){ 52 | get(loadAccessToken(), cb); 53 | } 54 | 55 | /** 56 | * 查询菜单 57 | * @param accessToken accessToken 58 | * @param cb 回调 59 | */ 60 | public void get(final String accessToken, Callback> cb){ 61 | doAsync(new AsyncFunction>(cb) { 62 | @Override 63 | public List execute() { 64 | return get(accessToken); 65 | } 66 | }); 67 | } 68 | 69 | /** 70 | * 查询菜单 71 | * @param accessToken accessToken 72 | * @return 菜单列表 73 | */ 74 | public List get(String accessToken){ 75 | checkNotNullAndEmpty(accessToken, "accessToken"); 76 | 77 | String url = GET + accessToken; 78 | Map resp = doGet(url); 79 | String jsonMenu = Jsons.DEFAULT.toJson(((Map) resp.get("menu")).get("button")); 80 | return Jsons.EXCLUDE_DEFAULT.fromJson(jsonMenu, ARRAY_LIST_MENU_TYPE); 81 | } 82 | 83 | /** 84 | * 创建菜单 85 | * @param jsonMenu 菜单json 86 | * @return 创建成功返回true,或抛WechatException 87 | */ 88 | public Boolean create(String jsonMenu){ 89 | return create(loadAccessToken(), jsonMenu); 90 | } 91 | 92 | /** 93 | * 创建菜单 94 | * @param accessToken 访问token 95 | * @param jsonMenu 菜单json 96 | * @param cb 回调 97 | */ 98 | public void create(final String accessToken, final String jsonMenu, Callback cb){ 99 | doAsync(new AsyncFunction(cb) { 100 | @Override 101 | public Boolean execute() { 102 | return create(accessToken, jsonMenu); 103 | } 104 | }); 105 | } 106 | 107 | /** 108 | * 创建菜单 109 | * @param jsonMenu 菜单json 110 | * @param cb 回调 111 | */ 112 | public void create(final String jsonMenu, Callback cb){ 113 | create(loadAccessToken(), jsonMenu, cb); 114 | } 115 | 116 | /** 117 | * 创建菜单 118 | * @param accessToken 访问token 119 | * @param jsonMenu 菜单json 120 | * @return 创建成功返回true,或抛WechatException 121 | */ 122 | public Boolean create(String accessToken, String jsonMenu){ 123 | checkNotNullAndEmpty(accessToken, "accessToken"); 124 | checkNotNullAndEmpty(jsonMenu, "jsonMenu"); 125 | 126 | String url = CREATE + accessToken; 127 | doPost(url, jsonMenu); 128 | return Boolean.TRUE; 129 | } 130 | 131 | /** 132 | * 删除菜单 133 | * @return 删除成功返回true,或抛WechatException 134 | */ 135 | public Boolean delete(){ 136 | return delete(loadAccessToken()); 137 | } 138 | 139 | /** 140 | * 删除菜单 141 | * @param cb 回调 142 | */ 143 | public void delete(Callback cb){ 144 | delete(loadAccessToken(), cb); 145 | } 146 | 147 | /** 148 | * 删除菜单 149 | * @param accessToken accessToken 150 | * @param cb 回调 151 | */ 152 | public void delete(final String accessToken, Callback cb){ 153 | doAsync(new AsyncFunction(cb) { 154 | @Override 155 | public Boolean execute() { 156 | return delete(accessToken); 157 | } 158 | }); 159 | } 160 | 161 | /** 162 | * 删除菜单 163 | * @param accessToken accessToken 164 | * @return 删除成功返回true,或抛WechatException 165 | */ 166 | public Boolean delete(String accessToken){ 167 | checkNotNullAndEmpty(accessToken, "accessToken"); 168 | 169 | String url = DELETE + accessToken; 170 | doGet(url); 171 | return Boolean.TRUE; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.hao0 8 | wechat 9 | 1.9.4 10 | 11 | wechat 12 | A Lightweight Wechat Component 13 | https://github.com/ihaolin/wechat 14 | 15 | 16 | 17 | 18 | me.hao0 19 | common 20 | 1.1.3 21 | 22 | 23 | 24 | com.google.guava 25 | guava 26 | 19.0 27 | 28 | 29 | 30 | junit 31 | junit 32 | 4.11 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-compiler-plugin 43 | 2.3.2 44 | 45 | 1.7 46 | 1.7 47 | utf8 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.codehaus.mojo 57 | findbugs-maven-plugin 58 | 3.0.3 59 | 60 | true 61 | target/site 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | release 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-source-plugin 75 | 2.2.1 76 | 77 | 78 | package 79 | 80 | jar-no-fork 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-javadoc-plugin 89 | 2.10.3 90 | 91 | 92 | package 93 | 94 | jar 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-gpg-plugin 103 | 1.6 104 | 105 | 106 | verify 107 | 108 | sign 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | oss 118 | https://oss.sonatype.org/content/repositories/snapshots/ 119 | 120 | 121 | oss 122 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | The MIT License (MIT) 131 | https://opensource.org/licenses/MIT 132 | 133 | 134 | 135 | 136 | 137 | haolin 138 | haolin.h0@gmail.com 139 | 140 | 141 | 142 | 143 | scm:git:git@github.com:ihaolin/wechat.git 144 | scm:git:git@github.com:ihaolin/wechat.git 145 | git@github.com:ihaolin/wechat.git 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/utils/XmlWriters.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.utils; 2 | 3 | import me.hao0.wechat.exception.XmlException; 4 | import java.io.Serializable; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | /** 10 | * 一个简陋的微信XML构建器 11 | * Author: haolin 12 | * Email: haolin.h0@gmail.com 13 | * Date: 6/11/15 14 | */ 15 | public class XmlWriters { 16 | 17 | List es = new ArrayList<>(); 18 | 19 | private XmlWriters(){} 20 | 21 | public static XmlWriters create(){ 22 | return new XmlWriters(); 23 | } 24 | 25 | public XmlWriters element(String name, String text){ 26 | E e = new TextE(name, text); 27 | es.add(e); 28 | return this; 29 | } 30 | 31 | public XmlWriters element(String name, Number number){ 32 | E e = new NumberE(name, number); 33 | es.add(e); 34 | return this; 35 | } 36 | 37 | public XmlWriters element(String parentName, String childName, String childText){ 38 | return element(parentName, new TextE(childName, childText)); 39 | } 40 | 41 | public XmlWriters element(String parentName, String childName, Number childNumber){ 42 | return element(parentName, new NumberE(childName, childNumber)); 43 | } 44 | 45 | /** 46 | * 构建包含多个子元素的元素 47 | * @param parentName 父元素标签名 48 | * @param childPairs childName1, childValue1, childName2, childValu2, ...,长度必读为2的倍数 49 | * @return this 50 | */ 51 | public XmlWriters element(String parentName, Object... childPairs){ 52 | if (childPairs.length % 2 != 0){ 53 | throw new XmlException("var args's length must % 2 = 0"); 54 | } 55 | E parent = new TextE(parentName, null); 56 | List children = new ArrayList<>(); 57 | E child; 58 | for (int i=0; i children){ 79 | E e = new TextE(parentName, null); 80 | e.children = children; 81 | es.add(e); 82 | return this; 83 | } 84 | 85 | /** 86 | * 构建包含多个子元素的元素 87 | * @param parentName 父元素标签名 88 | * @param childPairs childName1, childValue1, childName2, childValu2, ...,长度必读为2的倍数 89 | * @return an element 90 | */ 91 | public E newElement(String parentName, Object... childPairs){ 92 | E parent = new TextE(parentName, null); 93 | List children = new ArrayList<>(); 94 | E child; 95 | for (int i=0; i"); 116 | 117 | if (es != null && es.size() > 0){ 118 | for (E e : es){ 119 | xml.append(e.render()); 120 | } 121 | } 122 | 123 | xml.append(""); 124 | 125 | return xml.toString(); 126 | } 127 | 128 | public static abstract class E { 129 | 130 | String name; 131 | 132 | Object text; 133 | 134 | List children; 135 | 136 | public E(String name, Object text) { 137 | this.name = name; 138 | this.text = text; 139 | } 140 | 141 | protected abstract String render(); 142 | } 143 | 144 | public static final class TextE extends E { 145 | 146 | public TextE(String name, Serializable content) { 147 | super(name, content); 148 | } 149 | 150 | @Override 151 | protected String render() { 152 | StringBuilder content = new StringBuilder(); 153 | content.append("<").append(name).append(">"); 154 | 155 | if (text != null){ 156 | content.append(""); 157 | } 158 | 159 | if (children != null && children.size() > 0){ 160 | for (E child : children){ 161 | content.append(child.render()); 162 | } 163 | } 164 | 165 | content.append(""); 166 | return content.toString(); 167 | } 168 | } 169 | 170 | public static final class NumberE extends E { 171 | 172 | public NumberE(String name, Serializable content) { 173 | super(name, content); 174 | } 175 | 176 | @Override 177 | protected String render() { 178 | StringBuilder content = new StringBuilder(); 179 | content.append("<").append(name).append(">") 180 | .append(text) 181 | .append(""); 182 | return content.toString(); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wechat[![Build Status](https://travis-ci.org/ihaolin/wechat.svg?branch=master)](https://travis-ci.org/ihaolin/wechat) 2 | 3 | 轻量的微信公众号组件(A Lightweight Wechat Component) 4 | --- 5 | 6 | + 包引入 7 | 8 | ```xml 9 | 10 | me.hao0 11 | wechat 12 | 1.9.3 13 | 14 | ``` 15 | 16 | + 依赖包,注意引入项目时是否需要**exclude**: 17 | 18 | ```xml 19 | 20 | me.hao0 21 | common 22 | 1.1.3 23 | 24 | 25 | 26 | com.google.guava 27 | guava 28 | 19.0 29 | 30 | ``` 31 | 32 | + 业务系统与微信公众号交互图阐述: 33 | 34 | ![](flow.png) 35 | 36 | + API基本用法: 37 | 38 | ```java 39 | Wechat wechat = 40 | WechatBuilder.newBuilder("appId", "appSecret") 41 | .conf1() // 其他可选配置 42 | ... 43 | .build(); 44 | // 同步调用 45 | wechat.module().api(); 46 | 47 | // 异步调用 48 | wechat.module().api(Callback); 49 | ``` 50 | 51 | + Wechat已实现以下组件: 52 | 53 | + 基础: ```base()``` 54 | + 用户: ```user()``` 55 | + 菜单: ```menu()``` 56 | + 多客服: ```cs()``` 57 | + 消息: ```msg()``` 58 | + 二维码: ```qr()``` 59 | + 素材: ```material()``` 60 | + JS调用相关: ```js()``` 61 | + 数据统计: ```data()``` 62 | 63 | + API使用文档见[这里](API.md) 64 | 65 | + 组件扩展: 如果想自己扩展组件,可以继承``Component``,调用``register``: 66 | 67 | ```java 68 | public class MyComponent extends Component { 69 | // ... 70 | } 71 | MyComponent myComp = new MyComponent(); 72 | wechat.register(myComp); 73 | ``` 74 | 75 | + **AccessToken管理**: 76 | 77 | > 由于微信服务器限制**AccessToken**请求次数,并且频繁请求**AccessToken**并不是一个明智之举,需要将获取的**AccessToken**保存下来,待过期时,再去请求新的**AccessToken**,所以以上API均提供了无accessToken版本,如: 78 | 79 | ```java 80 | List ip(); 81 | List ip(String accessToken); 82 | ``` 83 | 84 | + 实现```AccessTokenLoader```: 85 | 86 | ```java 87 | public interface AccessTokenLoader { 88 | 89 | /** 90 | * 获取accessToken 91 | * @return accessToken,若""或NULL会重新从微信获取accessToken,并触发refresh方法 92 | */ 93 | String get(); 94 | 95 | /** 96 | * 刷新accessToken,实现时需要保存一段时间,以免频繁从微信服务器获取 97 | * @param token 从微信获取的新AccessToken 98 | */ 99 | void refresh(AccessToken token); 100 | } 101 | ``` 102 | 103 | + 默认的AccessTokenLoader实现(**生产环境不推荐使用**): 104 | 105 | ```java 106 | public class DefaultAccessTokenLoader implements AccessTokenLoader { 107 | 108 | private volatile AccessToken validToken; 109 | 110 | @Override 111 | public String get() { 112 | return (validToken == null 113 | || Strings.isNullOrEmpty(validToken.getAccessToken()) 114 | || System.currentTimeMillis() > validToken.getExpiredAt()) ? null : validToken.getAccessToken(); 115 | } 116 | 117 | @Override 118 | public void refresh(AccessToken token) { 119 | validToken = token; 120 | } 121 | } 122 | ``` 123 | 124 | + **Ticket管理**: 同AccessToken类似,需自己实现接口``TicketLoader``: 125 | 126 | ```java 127 | public interface TicketLoader { 128 | 129 | /** 130 | * 获取Ticket 131 | * @param type ticket类型 132 | * @see me.hao0.wechat.model.js.TicketType 133 | * @return 有效的ticket,若返回""或null,则重新从微信请求Ticket,并触发refresh方法 134 | */ 135 | String get(TicketType type); 136 | 137 | /** 138 | * 刷新Ticket 139 | * @param ticket 从微信获取的新Ticket 140 | */ 141 | void refresh(Ticket ticket); 142 | } 143 | ``` 144 | 145 | + 默认的TicketLoader实现(**生产环境不推荐使用**): 146 | 147 | ```java 148 | public class DefaultTicketLoader implements TicketLoader { 149 | 150 | private final Map tickets = new ConcurrentHashMap<>(); 151 | 152 | @Override 153 | public String get(TicketType type) { 154 | Ticket t = tickets.get(type); 155 | return (t == null 156 | || Strings.isNullOrEmpty(t.getTicket()) 157 | || System.currentTimeMillis() > t.getExpireAt()) ? null : t.getTicket(); 158 | } 159 | 160 | @Override 161 | public void refresh(Ticket ticket) { 162 | tickets.put(ticket.getType(), ticket); 163 | } 164 | } 165 | ``` 166 | 167 | + 具体例子,可见[测试用例](https://github.com/ihaolin/wechat/blob/master/src/test/java/me/hao0/wechat/WechatTests.java)。 168 | 169 | + 历史版本 170 | 171 | + 1.0.0: 172 | 173 | * 基础功能实现。 174 | 175 | + 1.1.0: 176 | 177 | * 实现代码简化,个别类访问权限修改; 178 | * 实现MATERIAL组件。 179 | 180 | + 1.2.0: 181 | 182 | * 废弃~~``Wechat.newWechat``~~构建方法,替换为``WechatBuilder``方式。 183 | * ``*Loader``设置过期时刻。 184 | * 实现JSSDK组件。 185 | 186 | + 1.3.0: 187 | 188 | + 引入[guava](https://github.com/google/guava)。 189 | + API支持异步调用。 190 | 191 | + 1.4.0: 192 | 193 | + 组件懒加载。 194 | + 改变组件访问方式,由~~变量~~到**方法**。 195 | 196 | + 1.5.0: 197 | 198 | + 上传客服头像。 199 | + 消息转发客服接口移至消息模块。 200 | + 实现DATA组件。 201 | 202 | + 1.6.0: 203 | 204 | + 将通用工具类移入[common](https://github.com/ihaolin/common)组件。 205 | 206 | + 1.6.1: 207 | 208 | + 文档完善,类访问权限控制; 209 | + 更新最新[common](https://github.com/ihaolin/common)包。 210 | 211 | + 1.6.2: 212 | 213 | + 参数严格校验。 214 | 215 | + 1.6.3: 216 | 217 | + 简化校验。 218 | 219 | + 1.6.4: 220 | 221 | + 更新最新[common](https://github.com/ihaolin/common)包。 222 | 223 | + 1.6.5 224 | 225 | + 升级[common](https://github.com/ihaolin/common),去掉kfAccount校验。 226 | 227 | + 1.6.6 228 | 229 | + 修复respNews参数校验。 230 | 231 | + 1.6.7 232 | 233 | + 消息群发兼容msgId为Int时。 234 | 235 | + 1.6.8 236 | 237 | + 修复消息被动回复问题。 238 | 239 | + 1.6.9 240 | 241 | + 修复微信菜单事件类型判断。 242 | 243 | + 1.6.10 244 | 245 | + 修复User.unionId为String类型。 246 | 247 | + 1.7.0 248 | 249 | + 增加获取用户列表的接口``Users.getUsers()``。 250 | 251 | + 1.8.0 252 | 253 | + 增加通过场景字符串获取永久二维码的接口``QrCodes.getPermQrcodeBySceneStr()``。 254 | 255 | + 1.9.0 256 | 257 | + 增加获取未关注公众号用户的信息接口; 258 | + 升级common,guava。 259 | 260 | + 1.9.1 261 | 262 | + 修复用户授权accessToken获取; 263 | + 废弃~~Bases.openId~~,替换为Bases.authAccessToken。 264 | 265 | + 1.9.2 266 | 267 | + 增加模版消息事件类型`RecvTemplateSendJobFinishEvent`; 268 | + 兼容在接收到微信新增的事件消息时,不作抛错处理,而是返回`RecvUnknownEvent`。 269 | 270 | + 1.9.3 271 | 272 | + 升级common包到`1.1.3`,修复`XmlReaders`线程安全问题。 273 | 274 | + 微信相关文档 275 | 276 | + [公众号接口权限说明](http://mp.weixin.qq.com/wiki/8/71e1908fa08e67c6251ebdd78fd6b6b4.html) 277 | + [接口频率限制说明](http://mp.weixin.qq.com/wiki/0/2e2239fa5f49388d5b5136ecc8e0e440.html) 278 | + [接口返回码说明](http://mp.weixin.qq.com/wiki/17/fa4e1434e57290788bde25603fa2fcbd.html) 279 | + [报警排查指引](http://mp.weixin.qq.com/wiki/13/8348156d0e25c9e27b21462322d41149.html) 280 | 281 | ## 有事请烧钱 282 | 283 | + 支付宝: 284 | 285 | 286 | 287 | + 微信: 288 | 289 | 290 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Bases.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import me.hao0.common.json.Jsons; 4 | import me.hao0.wechat.exception.WechatException; 5 | import me.hao0.wechat.model.base.AccessToken; 6 | import me.hao0.wechat.model.base.AuthAccessToken; 7 | import me.hao0.wechat.model.base.AuthType; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URLEncoder; 10 | import java.util.List; 11 | import java.util.Map; 12 | import static me.hao0.common.util.Preconditions.*; 13 | 14 | /** 15 | * 基础组件 16 | * Author: haolin 17 | * Email: haolin.h0@gmail.com 18 | * Date: 18/11/15 19 | * @since 1.4.0 20 | */ 21 | public final class Bases extends Component { 22 | 23 | /** 24 | * 授权 25 | */ 26 | private static final String AUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?"; 27 | 28 | /** 29 | * 获取accessToken(调用其他公众号接口需要) 30 | */ 31 | private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; 32 | 33 | /** 34 | * 获取accessToken(用户同意授权后,获取用户信息前,需要该accessToken,有别于上面的accessToken) 35 | *

36 | * 参考链接 37 | *

38 | */ 39 | private static final String AUTH_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code"; 40 | 41 | /** 42 | * 获取微信服务器的IP地址列表 43 | */ 44 | private static final String WX_IP_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token="; 45 | 46 | Bases(){} 47 | 48 | /** 49 | * 构建授权跳转URL(静默授权,仅获取用户openId,不包括个人信息) 50 | * @param redirectUrl 授权后的跳转URL(我方服务器URL) 51 | * @return 微信授权跳转URL 52 | */ 53 | public String authUrl(String redirectUrl) { 54 | return authUrl(redirectUrl, Boolean.TRUE); 55 | } 56 | 57 | /** 58 | * 构建授权跳转URL 59 | * @param redirectUrl 授权后的跳转URL(我方服务器URL) 60 | * @param quiet 是否静默: true: 仅获取openId,false: 获取openId和个人信息(需用户手动确认) 61 | * @return 微信授权跳转URL 62 | */ 63 | public String authUrl(String redirectUrl, Boolean quiet) { 64 | try { 65 | checkNotNullAndEmpty(redirectUrl, "redirectUrl"); 66 | redirectUrl = URLEncoder.encode(redirectUrl, "utf-8"); 67 | return AUTH_URL + 68 | "appid=" + wechat.getAppId() + 69 | "&redirect_uri=" + redirectUrl + 70 | "&response_type=code&scope=" + 71 | (quiet ? AuthType.BASE.scope() : AuthType.USER_INFO.scope()) 72 | + "&state=1#wechat_redirect"; 73 | } catch (UnsupportedEncodingException e) { 74 | throw new WechatException(e); 75 | } 76 | } 77 | 78 | /** 79 | * 获取用户openId 80 | * @param code 用户授权的code 81 | * @param cb 回调 82 | * @see #authAccessToken(String) 83 | */ 84 | public void openId(final String code, final Callback cb){ 85 | doAsync(new AsyncFunction(cb) { 86 | @Override 87 | public String execute() { 88 | return openId(code); 89 | } 90 | }); 91 | } 92 | 93 | /** 94 | * 获取用户openId 95 | * @param code 用户授权的code 96 | * @return 用户的openId,或抛WechatException 97 | * @see #authAccessToken(String) 98 | */ 99 | public String openId(String code){ 100 | checkNotNullAndEmpty(code, "code"); 101 | String url = AUTH_ACCESS_TOKEN_URL + 102 | "&appid=" + wechat.getAppId() + 103 | "&secret=" + wechat.getAppSecret() + 104 | "&code=" + code; 105 | 106 | Map resp = doGet(url); 107 | 108 | return (String)resp.get("openid"); 109 | } 110 | 111 | /** 112 | * 获取accessToken(应该尽量临时保存一个地方,每隔一段时间来获取) 113 | * @param cb 回调 114 | */ 115 | public void accessToken(final Callback cb){ 116 | doAsync(new AsyncFunction(cb) { 117 | @Override 118 | public AccessToken execute() { 119 | return accessToken(); 120 | } 121 | }); 122 | } 123 | 124 | /** 125 | * 获取accessToken(应该尽量临时保存一个地方,每隔一段时间来获取) 126 | * @return accessToken,或抛WechatException 127 | */ 128 | public AccessToken accessToken(){ 129 | String url = ACCESS_TOKEN_URL + "&appid=" + wechat.getAppId() + "&secret=" + wechat.getAppSecret(); 130 | 131 | Map resp = doGet(url); 132 | AccessToken token = new AccessToken(); 133 | token.setAccessToken((String)resp.get("access_token")); 134 | Integer expire = (Integer)resp.get("expires_in"); 135 | token.setExpire(expire); 136 | token.setExpiredAt(System.currentTimeMillis() + expire * 1000); 137 | 138 | return token; 139 | } 140 | 141 | /** 142 | * 获取用户授权的accessToken(与上面不同,该accessToken用于在用户同意授权后,获取用户信息) 143 | * @param code 用户同意授权后返回的code 144 | * @param cb 回调函数 145 | */ 146 | public void authAccessToken(final String code, final Callback cb){ 147 | doAsync(new AsyncFunction(cb) { 148 | @Override 149 | public AuthAccessToken execute() { 150 | return authAccessToken(code); 151 | } 152 | }); 153 | } 154 | 155 | /** 156 | * 获取用户授权的accessToken(与上面不同,该accessToken用于在用户同意授权后,获取用户信息) 157 | * @param code 用户同意授权后返回的code 158 | * @return accessToken,或抛WechatException 159 | */ 160 | public AuthAccessToken authAccessToken(String code){ 161 | 162 | String url = AUTH_ACCESS_TOKEN_URL + 163 | "&appid=" + wechat.getAppId() + 164 | "&secret=" + wechat.getAppSecret() + 165 | "&code=" + code; 166 | 167 | Map resp = doGet(url); 168 | AuthAccessToken token = Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), AuthAccessToken.class); 169 | token.setExpiredAt(System.currentTimeMillis() + token.getExpire() * 1000); 170 | 171 | return token; 172 | } 173 | 174 | /** 175 | * 获取微信服务器IP列表 176 | * @return 微信服务器IP列表,或抛WechatException 177 | */ 178 | public List ip(){ 179 | return ip(loadAccessToken()); 180 | } 181 | 182 | /** 183 | * 获取微信服务器IP列表 184 | * @param cb 回调 185 | */ 186 | public void ip(Callback> cb){ 187 | ip(loadAccessToken(), cb); 188 | } 189 | 190 | /** 191 | * 获取微信服务器IP列表 192 | * @param accessToken accessToken 193 | * @param cb 回调 194 | */ 195 | public void ip(final String accessToken, Callback> cb){ 196 | doAsync(new AsyncFunction>(cb) { 197 | @Override 198 | public List execute() { 199 | return ip(accessToken); 200 | } 201 | }); 202 | } 203 | 204 | /** 205 | * 获取微信服务器IP列表 206 | * @param accessToken accessToken 207 | * @return 微信服务器IP列表,或抛WechatException 208 | */ 209 | @SuppressWarnings("unchecked") 210 | public List ip(String accessToken){ 211 | checkNotNullAndEmpty(accessToken, "accessToken"); 212 | String url = WX_IP_URL + accessToken; 213 | Map resp = doGet(url); 214 | return (List)resp.get("ip_list"); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Wechat.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.google.common.base.Strings; 5 | import com.google.common.cache.CacheBuilder; 6 | import com.google.common.cache.CacheLoader; 7 | import com.google.common.cache.LoadingCache; 8 | import me.hao0.common.http.Http; 9 | import me.hao0.common.json.Jsons; 10 | import me.hao0.common.util.Fields; 11 | import me.hao0.wechat.exception.WechatException; 12 | import me.hao0.wechat.loader.AccessTokenLoader; 13 | import me.hao0.wechat.loader.DefaultAccessTokenLoader; 14 | import me.hao0.wechat.loader.DefaultTicketLoader; 15 | import me.hao0.wechat.loader.TicketLoader; 16 | import me.hao0.wechat.model.base.AccessToken; 17 | import me.hao0.wechat.model.js.Ticket; 18 | import me.hao0.wechat.model.js.TicketType; 19 | import java.io.InputStream; 20 | import java.lang.reflect.Field; 21 | import java.util.Map; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | import java.util.concurrent.ThreadFactory; 25 | 26 | /** 27 | * 微信核心组件库 28 | * Author: haolin 29 | * Email: haolin.h0@gmail.com 30 | * Date: 5/11/15 31 | * @since 1.0.0 32 | */ 33 | public final class Wechat { 34 | 35 | /** 36 | * 微信APP ID 37 | */ 38 | private String appId; 39 | 40 | /** 41 | * 微信APP 密钥 42 | */ 43 | private String appSecret; 44 | 45 | /** 46 | * 微信APP (令牌)Token 47 | */ 48 | String appToken; 49 | 50 | /** 51 | * 消息加密Key 52 | */ 53 | String msgKey; 54 | 55 | /** 56 | * AccessToken加载器 57 | */ 58 | AccessTokenLoader tokenLoader = DEFAULT_ACCESS_TOKEN_LOADER; 59 | 60 | /** 61 | * Ticket加载器 62 | */ 63 | TicketLoader ticketLoader = DEFAULT_TICKET_LOADER; 64 | 65 | /** 66 | * 异步执行器 67 | */ 68 | ExecutorService executor = DEFAULT_EXECUTOR; 69 | 70 | /** 71 | * 微信错误码变量 72 | */ 73 | private final String ERROR_CODE = "errcode"; 74 | 75 | private static final String BASES = "me.hao0.wechat.core.Bases"; 76 | 77 | private static final String USERS = "me.hao0.wechat.core.Users"; 78 | 79 | private static final String MENUS = "me.hao0.wechat.core.Menus"; 80 | 81 | private static final String CUSTOMER_SERVICES = "me.hao0.wechat.core.CustomerServices"; 82 | 83 | private static final String MESSAGES = "me.hao0.wechat.core.Messages"; 84 | 85 | private static final String QRCODES = "me.hao0.wechat.core.QrCodes"; 86 | 87 | private static final String MATERIALS = "me.hao0.wechat.core.Materials"; 88 | 89 | private static final String DATAS = "me.hao0.wechat.core.Datas"; 90 | 91 | private static final String JSSDKS = "me.hao0.wechat.core.JsSdks"; 92 | 93 | private static final AccessTokenLoader DEFAULT_ACCESS_TOKEN_LOADER = new DefaultAccessTokenLoader(); 94 | 95 | private static final DefaultTicketLoader DEFAULT_TICKET_LOADER = new DefaultTicketLoader(); 96 | 97 | private static final JavaType MAP_STRING_OBJ_TYPE = Jsons.DEFAULT.createCollectionType(Map.class, String.class, Object.class); 98 | 99 | private static final ExecutorService DEFAULT_EXECUTOR = Executors.newFixedThreadPool( 100 | Runtime.getRuntime().availableProcessors() + 1, new ThreadFactory() { 101 | @Override 102 | public Thread newThread(Runnable r) { 103 | Thread t = new Thread(r); 104 | t.setName("wechat"); 105 | return t; 106 | } 107 | }); 108 | 109 | private LoadingCache components = 110 | CacheBuilder.newBuilder().build(new CacheLoader() { 111 | @Override 112 | public Component load(String classFullName) throws Exception { 113 | Class clazz = Class.forName(classFullName); 114 | Object comp = clazz.newInstance(); 115 | injectWechat(clazz, comp); 116 | return (Component)comp; 117 | } 118 | }); 119 | 120 | Wechat(String appId, String appSecret){ 121 | this.appId = appId; 122 | this.appSecret = appSecret; 123 | } 124 | 125 | public String getAppId() { 126 | return appId; 127 | } 128 | 129 | public String getAppSecret() { 130 | return appSecret; 131 | } 132 | 133 | public String getAppToken() { 134 | return appToken; 135 | } 136 | 137 | public String getMsgKey() { 138 | return msgKey; 139 | } 140 | 141 | public Bases base(){ 142 | return (Bases)components.getUnchecked(BASES); 143 | } 144 | 145 | public CustomerServices cs(){ 146 | return (CustomerServices)components.getUnchecked(CUSTOMER_SERVICES); 147 | } 148 | 149 | public Menus menu(){ 150 | return (Menus)components.getUnchecked(MENUS); 151 | } 152 | 153 | public Users user(){ 154 | return (Users)components.getUnchecked(USERS); 155 | } 156 | 157 | public Messages msg(){ 158 | return (Messages)components.getUnchecked(MESSAGES); 159 | } 160 | 161 | public QrCodes qr(){ 162 | return (QrCodes)components.getUnchecked(QRCODES); 163 | } 164 | 165 | public Materials material(){ 166 | return (Materials)components.getUnchecked(MATERIALS); 167 | } 168 | 169 | public Datas data(){ 170 | return (Datas)components.getUnchecked(DATAS); 171 | } 172 | 173 | public JsSdks js(){ 174 | return (JsSdks)components.getUnchecked(JSSDKS); 175 | } 176 | 177 | private void injectWechat(Class clazz, Object comp) throws NoSuchFieldException { 178 | Field wechat = clazz.getSuperclass().getDeclaredField("wechat"); 179 | Fields.put(comp, wechat, this); 180 | } 181 | 182 | /** 183 | * 注册组件 184 | * @param component 组件对象 185 | * @param 范型 186 | */ 187 | public void register(T component){ 188 | try { 189 | injectWechat(component.getClass(), component); 190 | } catch (NoSuchFieldException e) { 191 | throw new WechatException(e); 192 | } 193 | } 194 | 195 | /** 196 | * 关闭异步执行器(不再支持异步执行) 197 | */ 198 | public void destroy(){ 199 | if (executor.isShutdown()){ 200 | executor.shutdown(); 201 | } 202 | } 203 | 204 | String loadAccessToken(){ 205 | String accessToken = tokenLoader.get(); 206 | if (Strings.isNullOrEmpty(accessToken)){ 207 | AccessToken token = base().accessToken(); 208 | tokenLoader.refresh(token); 209 | accessToken = token.getAccessToken(); 210 | } 211 | return accessToken; 212 | } 213 | 214 | String loadTicket(TicketType type){ 215 | String ticket = ticketLoader.get(type); 216 | if (Strings.isNullOrEmpty(ticket)){ 217 | Ticket t = js().getTicket(type); 218 | ticketLoader.refresh(t); 219 | ticket = t.getTicket(); 220 | } 221 | return ticket; 222 | } 223 | 224 | Map doPost(String url, Map params) { 225 | String body = null; 226 | if (params != null && !params.isEmpty()){ 227 | body = Jsons.DEFAULT.toJson(params); 228 | } 229 | return doPost(url, body); 230 | } 231 | 232 | Map doPost(String url, String body) { 233 | Http http = Http.post(url); 234 | if (!Strings.isNullOrEmpty(body)){ 235 | http.body(body); 236 | } 237 | Map resp = http.request(MAP_STRING_OBJ_TYPE); 238 | Integer errcode = (Integer)resp.get(ERROR_CODE); 239 | if (errcode != null && errcode != 0){ 240 | throw new WechatException(resp); 241 | } 242 | return resp; 243 | } 244 | 245 | Map doGet(String url) { 246 | return doGet(url, null); 247 | } 248 | 249 | Map doGet(String url, Map params) { 250 | Http http = Http.get(url); 251 | if (params != null && params.size() > 0){ 252 | http.body(Jsons.DEFAULT.toJson(params)); 253 | } 254 | Map resp = http.request(MAP_STRING_OBJ_TYPE); 255 | Integer errcode = (Integer)resp.get(ERROR_CODE); 256 | if (errcode != null && errcode != 0){ 257 | throw new WechatException(resp); 258 | } 259 | return resp; 260 | } 261 | 262 | Map doUpload(String url, String fieldName, String fileName, InputStream input, Map params){ 263 | String json = Http.upload(url, fieldName, fileName, input, params); 264 | Map resp = Jsons.DEFAULT.fromJson(json, MAP_STRING_OBJ_TYPE); 265 | Integer errcode = (Integer)resp.get(ERROR_CODE); 266 | if (errcode != null && errcode != 0){ 267 | throw new WechatException(resp); 268 | } 269 | return resp; 270 | } 271 | 272 | void doAsync(final AsyncFunction f){ 273 | executor.submit(new Runnable() { 274 | @Override 275 | public void run() { 276 | try { 277 | T res = f.execute(); 278 | f.cb.onSuccess(res); 279 | } catch (Exception e){ 280 | f.cb.onFailure(e); 281 | } 282 | } 283 | }); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/QrCodes.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import me.hao0.wechat.exception.WechatException; 6 | import me.hao0.wechat.model.qrcode.Qrcode; 7 | import me.hao0.wechat.model.qrcode.QrcodeType; 8 | import me.hao0.common.json.Jsons; 9 | import me.hao0.common.util.Strings; 10 | 11 | import java.io.UnsupportedEncodingException; 12 | import java.net.URLEncoder; 13 | import java.util.Map; 14 | 15 | import static me.hao0.common.util.Preconditions.*; 16 | 17 | /** 18 | * 二维码组件 19 | * Author: haolin 20 | * Email: haolin.h0@gmail.com 21 | * Date: 18/11/15 22 | * @since 1.4.0 23 | */ 24 | public final class QrCodes extends Component { 25 | 26 | /** 27 | * 获取Ticket 28 | */ 29 | private static final String TICKET_GET = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="; 30 | 31 | /** 32 | * 显示二维码链接 33 | */ 34 | private static final String SHOW_QRCODE = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; 35 | 36 | /** 37 | * 将原长链接通过此接口转成短链接 38 | */ 39 | private static final String LONG_TO_SHORT = "https://api.weixin.qq.com/cgi-bin/shorturl?access_token="; 40 | 41 | QrCodes(){} 42 | 43 | /** 44 | * 获取临时二维码 45 | * @param sceneId 业务场景ID,32位非0整型 46 | * @param expire 该二维码有效时间,以秒为单位。 最大不超过604800(即7天) 47 | * @return 临时二维码链接,或抛WechatException 48 | */ 49 | public String getTempQrcode(String sceneId, Integer expire){ 50 | return getTempQrcode(loadAccessToken(), sceneId, expire); 51 | } 52 | 53 | /** 54 | * 获取临时二维码 55 | * @param sceneId 业务场景ID,32位非0整型 56 | * @param expire 该二维码有效时间,以秒为单位。 最大不超过604800(即7天) 57 | * @param cb 回调 58 | */ 59 | public void getTempQrcode(final String sceneId, final Integer expire, Callback cb){ 60 | getTempQrcode(loadAccessToken(), sceneId, expire, cb); 61 | } 62 | 63 | /** 64 | * 获取临时二维码 65 | * @param accessToken accessToken 66 | * @param sceneId 业务场景ID,32位非0整型 67 | * @param expire 该二维码有效时间,以秒为单位。 最大不超过604800(即7天) 68 | * @param cb 回调 69 | */ 70 | public void getTempQrcode(final String accessToken, final String sceneId, final Integer expire, Callback cb){ 71 | doAsync(new AsyncFunction(cb) { 72 | @Override 73 | public String execute() { 74 | return getTempQrcode(accessToken, sceneId, expire); 75 | } 76 | }); 77 | } 78 | 79 | /** 80 | * 获取临时二维码 81 | * @param accessToken accessToken 82 | * @param sceneId 业务场景ID,32位非0整型 83 | * @param expire 该二维码有效时间,以秒为单位。 最大不超过604800(即7天) 84 | * @return 临时二维码链接,或抛WechatException 85 | */ 86 | public String getTempQrcode(String accessToken, String sceneId, Integer expire){ 87 | checkNotNullAndEmpty(accessToken, "accessToken"); 88 | checkNotNullAndEmpty(sceneId, "sceneId"); 89 | checkArgument(expire != null && expire > 0, "expire must > 0"); 90 | 91 | String url = TICKET_GET + accessToken; 92 | Map params = buildQrcodeParams(sceneId, null, QrcodeType.QR_SCENE); 93 | params.put("expire_seconds", expire); 94 | 95 | Map resp = doPost(url, params); 96 | Qrcode qr = Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), Qrcode.class); 97 | return showQrcode(qr.getTicket()); 98 | } 99 | 100 | /** 101 | * 获取永久二维码 102 | * @param sceneId 业务场景ID,最大值为100000(目前参数只支持1--100000) 103 | * @return 永久二维码链接,或抛WechatException 104 | */ 105 | public String getPermQrcode(String sceneId){ 106 | return getPermQrcode(loadAccessToken(), sceneId); 107 | } 108 | 109 | /** 110 | * 获取永久二维码 111 | * @param sceneId 业务场景ID,最大值为100000(目前参数只支持1--100000) 112 | * @param cb 回调 113 | */ 114 | public void getPermQrcode(final String sceneId, Callback cb){ 115 | getPermQrcode(loadAccessToken(), sceneId, cb); 116 | } 117 | 118 | /** 119 | * 获取永久二维码 120 | * @param accessToken accessToken 121 | * @param sceneId 业务场景ID,最大值为100000(目前参数只支持1--100000) 122 | * @param cb 回调 123 | */ 124 | public void getPermQrcode(final String accessToken, final String sceneId, Callback cb){ 125 | doAsync(new AsyncFunction(cb) { 126 | @Override 127 | public String execute() { 128 | return getPermQrcode(accessToken, sceneId); 129 | } 130 | }); 131 | } 132 | 133 | /** 134 | * 获取永久二维码 135 | * @param accessToken accessToken 136 | * @param sceneId 业务场景ID,最大值为100000(目前参数只支持1--100000) 137 | * @return 永久二维码链接,或抛WechatException 138 | */ 139 | public String getPermQrcode(String accessToken, String sceneId){ 140 | checkNotNullAndEmpty(accessToken, "accessToken"); 141 | checkNotNullAndEmpty(sceneId, "sceneId"); 142 | 143 | String url = TICKET_GET + accessToken; 144 | Map params = buildQrcodeParams(sceneId, null, QrcodeType.QR_LIMIT_SCENE); 145 | 146 | Map resp = doPost(url, params); 147 | Qrcode qr = Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), Qrcode.class); 148 | 149 | return showQrcode(qr.getTicket()); 150 | } 151 | 152 | 153 | /** 154 | * 获取永久二维码 155 | * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64,仅永久二维码支持此字段 156 | * @return 永久二维码链接,或抛WechatException 157 | */ 158 | public String getPermQrcodeBySceneStr(String sceneStr){ 159 | return getPermQrcodeBySceneStr(loadAccessToken(), sceneStr); 160 | } 161 | /** 162 | * 获取永久二维码 163 | * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64,仅永久二维码支持此字段 164 | * @param cb 回调 165 | */ 166 | public void getPermQrcodeBySceneStr(final String sceneStr, Callback cb){ 167 | getPermQrcodeBySceneStr(loadAccessToken(), sceneStr, cb); 168 | } 169 | 170 | /** 171 | * 根据场景字符串获取永久二维码 172 | * @param accessToken accessToken 173 | * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64,仅永久二维码支持此字段 174 | * @param cb 回调 175 | */ 176 | public void getPermQrcodeBySceneStr(final String accessToken, final String sceneStr, Callback cb){ 177 | doAsync(new AsyncFunction(cb) { 178 | @Override 179 | public String execute() { 180 | return getPermQrcodeBySceneStr(accessToken, sceneStr); 181 | } 182 | }); 183 | } 184 | 185 | /** 186 | * 获取永久二维码 187 | * @param accessToken accessToken 188 | * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64,仅永久二维码支持此字段 189 | * @return 永久二维码链接,或抛WechatException 190 | */ 191 | public String getPermQrcodeBySceneStr(String accessToken, String sceneStr){ 192 | checkNotNullAndEmpty(accessToken, "accessToken"); 193 | checkNotNullAndEmpty(sceneStr, "sceneStr"); 194 | 195 | String url = TICKET_GET + accessToken; 196 | Map params = buildQrcodeParams(null, sceneStr, QrcodeType.QR_LIMIT_STR_SCENE); 197 | 198 | Map resp = doPost(url, params); 199 | Qrcode qr = Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), Qrcode.class); 200 | 201 | return showQrcode(qr.getTicket()); 202 | } 203 | 204 | /** 205 | * 生成二维码参数,首先尝试使用 sceneId,再使用sceneStr 206 | * @param sceneId 场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000) 207 | * @param sceneStr 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64,仅永久二维码支持此字段 208 | * @param type 二维码类型,QR_SCENE为临时,QR_LIMIT_SCENE为永久,QR_LIMIT_STR_SCENE为永久的字符串参数值 209 | * @return 二维码参数 210 | */ 211 | private Map buildQrcodeParams(String sceneId, String sceneStr, QrcodeType type) { 212 | Map params = Maps.newHashMapWithExpectedSize(2); 213 | params.put("action_name", type.value()); 214 | 215 | Map sceneMap = Maps.newHashMapWithExpectedSize(1); 216 | if (!Strings.isNullOrEmpty(sceneId)) { 217 | sceneMap.put("scene_id", sceneId); 218 | }else if (!Strings.isNullOrEmpty(sceneStr)) { 219 | sceneMap.put("scene_str", sceneStr); 220 | } 221 | 222 | Map scene = Maps.newHashMapWithExpectedSize(1); 223 | scene.put("scene", sceneMap); 224 | 225 | params.put("action_info", scene); 226 | return params; 227 | } 228 | 229 | /** 230 | * 获取二维码链接 231 | * @param ticket 二维码的ticket 232 | * @return 二维码链接,或抛WechatException 233 | */ 234 | private String showQrcode(String ticket){ 235 | try { 236 | return SHOW_QRCODE + URLEncoder.encode(ticket, "UTF-8"); 237 | } catch (UnsupportedEncodingException e) { 238 | throw new WechatException(e); 239 | } 240 | } 241 | 242 | /** 243 | * 将二维码长链接转换为端链接,生成二维码将大大提升扫码速度和成功率 244 | * @param longUrl 长链接 245 | * @return 短链接,或抛WechatException 246 | */ 247 | public String shortUrl(String longUrl){ 248 | return shortUrl(loadAccessToken(), longUrl); 249 | } 250 | 251 | /** 252 | * 将二维码长链接转换为端链接,生成二维码将大大提升扫码速度和成功率 253 | * @param longUrl 长链接 254 | * @param cb 回调 255 | */ 256 | public void shortUrl(final String longUrl, Callback cb){ 257 | shortUrl(longUrl, longUrl, cb); 258 | } 259 | 260 | /** 261 | * 将二维码长链接转换为端链接,生成二维码将大大提升扫码速度和成功率 262 | * @param accessToken accessToken 263 | * @param longUrl 长链接 264 | * @param cb 回调 265 | */ 266 | public void shortUrl(final String accessToken, final String longUrl, Callback cb){ 267 | doAsync(new AsyncFunction(cb) { 268 | @Override 269 | public String execute() { 270 | return shortUrl(accessToken, longUrl); 271 | } 272 | }); 273 | } 274 | 275 | /** 276 | * 将二维码长链接转换为端链接,生成二维码将大大提升扫码速度和成功率 277 | * @param accessToken accessToken 278 | * @param longUrl 长链接 279 | * @return 短链接,或抛WechatException 280 | */ 281 | public String shortUrl(String accessToken, String longUrl){ 282 | checkNotNullAndEmpty(accessToken, "accessToken"); 283 | checkNotNullAndEmpty(longUrl, "longUrl"); 284 | 285 | String url = LONG_TO_SHORT + accessToken; 286 | Map params = Maps.newHashMapWithExpectedSize(2); 287 | params.put("action", "long2short"); 288 | params.put("long_url", longUrl); 289 | 290 | Map resp = doPost(url, params); 291 | return (String)resp.get("short_url"); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/main/java/me/hao0/wechat/core/Users.java: -------------------------------------------------------------------------------- 1 | package me.hao0.wechat.core; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.google.common.collect.Maps; 5 | import me.hao0.common.json.Jsons; 6 | import me.hao0.common.util.Strings; 7 | import me.hao0.wechat.model.base.AuthAccessToken; 8 | import me.hao0.wechat.model.user.Group; 9 | import me.hao0.wechat.model.user.User; 10 | import me.hao0.wechat.model.user.UserInfo; 11 | import me.hao0.wechat.model.user.UserList; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import static me.hao0.common.util.Preconditions.checkArgument; 16 | import static me.hao0.common.util.Preconditions.checkNotNullAndEmpty; 17 | 18 | /** 19 | * 用户组件 20 | * Author: haolin 21 | * Email: haolin.h0@gmail.com 22 | * Date: 18/11/15 23 | * 24 | * @since 1.4.0 25 | */ 26 | public final class Users extends Component { 27 | 28 | /** 29 | * 创建用户分组 30 | */ 31 | private static final String CREATE_GROUP = "https://api.weixin.qq.com/cgi-bin/groups/create?access_token="; 32 | 33 | /** 34 | * 获取用户分组列表 35 | */ 36 | private static final String GET_GROUP = "https://api.weixin.qq.com/cgi-bin/groups/get?access_token="; 37 | 38 | /** 39 | * 删除分组 40 | */ 41 | private static final String DELETE_GROUP = "https://api.weixin.qq.com/cgi-bin/groups/delete?access_token="; 42 | 43 | /** 44 | * 更新分组名称 45 | */ 46 | private static final String UPDATE_GROUP = "https://api.weixin.qq.com/cgi-bin/groups/update?access_token="; 47 | 48 | /** 49 | * 获取用户所在分组 50 | */ 51 | private static final String GROUP_OF_USER = "https://api.weixin.qq.com/cgi-bin/groups/getid?access_token="; 52 | 53 | /** 54 | * 移动用户所在组 55 | */ 56 | private static final String MOVE_USER_GROUP = "https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token="; 57 | 58 | /** 59 | * 拉取用户信息(用户已关注) 60 | */ 61 | private static final String GET_USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info?lang=zh_CN&access_token="; 62 | 63 | /** 64 | * 获取用户信息(用户未关注,但用户已手动同意授权) 65 | */ 66 | private static final String GET_USER_INFO_AUTHED = "https://api.weixin.qq.com/sns/userinfo?lang=zh_CN&access_token="; 67 | 68 | /** 69 | * 拉取用户列表信息 70 | */ 71 | private static final String GET_USERS_INFO = "https://api.weixin.qq.com/cgi-bin/user/get?access_token="; 72 | 73 | /** 74 | * 备注用户 75 | */ 76 | private static final String REMARK_USER = "https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token="; 77 | 78 | private static final JavaType ARRAY_LIST_GROUP_TYPE = Jsons.DEFAULT.createCollectionType(ArrayList.class, Group.class); 79 | 80 | Users() { 81 | } 82 | 83 | /** 84 | * 创建用户分组 85 | * 86 | * @param name 名称 87 | * @return 分组ID,或抛WechatException 88 | */ 89 | public Integer createGroup(String name) { 90 | return createGroup(loadAccessToken(), name); 91 | } 92 | 93 | /** 94 | * 创建用户分组 95 | * 96 | * @param name 名称 97 | * @param cb 回调 98 | */ 99 | public void createGroup(final String name, Callback cb) { 100 | createGroup(loadAccessToken(), name, cb); 101 | } 102 | 103 | /** 104 | * 创建用户分组 105 | * 106 | * @param accessToken accessToken 107 | * @param name 名称 108 | * @param cb 回调 109 | */ 110 | public void createGroup(final String accessToken, final String name, Callback cb) { 111 | doAsync(new AsyncFunction(cb) { 112 | @Override 113 | public Integer execute() { 114 | return createGroup(accessToken, name); 115 | } 116 | }); 117 | } 118 | 119 | /** 120 | * 创建用户分组 121 | * 122 | * @param accessToken accessToken 123 | * @param name 名称 124 | * @return 分组ID,或抛WechatException 125 | */ 126 | public Integer createGroup(String accessToken, String name) { 127 | checkNotNullAndEmpty(accessToken, "accessToken"); 128 | checkNotNullAndEmpty(name, "name"); 129 | 130 | String url = CREATE_GROUP + accessToken; 131 | Map params = Maps.newHashMapWithExpectedSize(1); 132 | Group g = new Group(); 133 | g.setName(name); 134 | params.put("group", g); 135 | 136 | Map resp = doPost(url, params); 137 | return (Integer) ((Map) resp.get("group")).get("id"); 138 | } 139 | 140 | /** 141 | * 获取所有分组列表 142 | * 143 | * @return 分组列表,或抛WechatException 144 | */ 145 | public List getGroup() { 146 | return getGroup(loadAccessToken()); 147 | } 148 | 149 | /** 150 | * 获取所有分组列表 151 | * 152 | * @param accessToken accessToken 153 | * @param cb 回调 154 | */ 155 | public void getGroup(final String accessToken, Callback> cb) { 156 | doAsync(new AsyncFunction>(cb) { 157 | @Override 158 | public List execute() { 159 | return getGroup(accessToken); 160 | } 161 | }); 162 | } 163 | 164 | /** 165 | * 获取所有分组列表 166 | * 167 | * @param accessToken accessToken 168 | * @return 分组列表,或抛WechatException 169 | */ 170 | public List getGroup(String accessToken) { 171 | checkNotNullAndEmpty(accessToken, "accessToken"); 172 | 173 | String url = GET_GROUP + accessToken; 174 | Map resp = doGet(url); 175 | return Jsons.EXCLUDE_DEFAULT 176 | .fromJson(Jsons.DEFAULT.toJson(resp.get("groups")), ARRAY_LIST_GROUP_TYPE); 177 | } 178 | 179 | /** 180 | * 删除分组 181 | * 182 | * @param id 分组ID 183 | * @return 删除成功返回true,或抛WechatException 184 | */ 185 | public Boolean deleteGroup(Integer id) { 186 | return deleteGroup(loadAccessToken(), id); 187 | } 188 | 189 | /** 190 | * 删除分组 191 | * 192 | * @param id 分组ID 193 | * @param cb 回调 194 | */ 195 | public void deleteGroup(final Integer id, Callback cb) { 196 | deleteGroup(loadAccessToken(), id, cb); 197 | } 198 | 199 | /** 200 | * 删除分组 201 | * 202 | * @param accessToken accessToken 203 | * @param id 分组ID 204 | * @param cb 回调 205 | */ 206 | public void deleteGroup(final String accessToken, final Integer id, Callback cb) { 207 | doAsync(new AsyncFunction(cb) { 208 | @Override 209 | public Boolean execute() { 210 | return deleteGroup(accessToken, id); 211 | } 212 | }); 213 | } 214 | 215 | /** 216 | * 删除分组 217 | * 218 | * @param accessToken accessToken 219 | * @param id 分组ID 220 | * @return 删除成功返回true,或抛WechatException 221 | */ 222 | public Boolean deleteGroup(String accessToken, Integer id) { 223 | checkNotNullAndEmpty(accessToken, "accessToken"); 224 | checkArgument(id != null && id > 0, "id must > 0"); 225 | 226 | String url = DELETE_GROUP + accessToken; 227 | Group g = new Group(); 228 | g.setId(id); 229 | Map params = Maps.newHashMapWithExpectedSize(1); 230 | params.put("group", g); 231 | 232 | doPost(url, params); 233 | return Boolean.TRUE; 234 | } 235 | 236 | /** 237 | * 更新分组名称 238 | * 239 | * @param id 分组ID 240 | * @param newName 分组新名称 241 | * @return 更新成功返回true,或抛WechatException 242 | */ 243 | public Boolean updateGroup(Integer id, String newName) { 244 | return updateGroup(loadAccessToken(), id, newName); 245 | } 246 | 247 | /** 248 | * 更新分组名称 249 | * 250 | * @param id 分组ID 251 | * @param newName 分组新名称 252 | * @param cb 回调 253 | */ 254 | public void updateGroup(final Integer id, final String newName, Callback cb) { 255 | updateGroup(loadAccessToken(), id, newName, cb); 256 | } 257 | 258 | /** 259 | * 更新分组名称 260 | * 261 | * @param accessToken accessToken 262 | * @param id 分组ID 263 | * @param newName 分组新名称 264 | * @param cb 回调 265 | */ 266 | public void updateGroup(final String accessToken, final Integer id, final String newName, Callback cb) { 267 | doAsync(new AsyncFunction(cb) { 268 | @Override 269 | public Boolean execute() { 270 | return updateGroup(accessToken, id, newName); 271 | } 272 | }); 273 | } 274 | 275 | /** 276 | * 更新分组名称 277 | * 278 | * @param accessToken accessToken 279 | * @param id 分组ID 280 | * @param newName 分组新名称 281 | * @return 更新成功返回true,或抛WechatException 282 | */ 283 | public Boolean updateGroup(String accessToken, Integer id, String newName) { 284 | checkNotNullAndEmpty(accessToken, "accessToken"); 285 | checkArgument(id != null && id > 0, "id must > 0"); 286 | checkNotNullAndEmpty(newName, "group name"); 287 | 288 | String url = UPDATE_GROUP + accessToken; 289 | Group g = new Group(); 290 | g.setId(id); 291 | g.setName(newName); 292 | Map params = Maps.newHashMapWithExpectedSize(1); 293 | params.put("group", g); 294 | 295 | doPost(url, params); 296 | return Boolean.TRUE; 297 | } 298 | 299 | /** 300 | * 获取用户所在组 301 | * 302 | * @param openId 用户openId 303 | * @return 组ID,或抛WechatException 304 | */ 305 | public Integer getUserGroup(String openId) { 306 | return getUserGroup(loadAccessToken(), openId); 307 | } 308 | 309 | /** 310 | * 获取用户所在组 311 | * 312 | * @param openId 用户openId 313 | * @param cb 回调 314 | */ 315 | public void getUserGroup(final String openId, Callback cb) { 316 | getUserGroup(loadAccessToken(), openId, cb); 317 | } 318 | 319 | /** 320 | * 获取用户所在组 321 | * 322 | * @param accessToken accessToken 323 | * @param openId 用户openId 324 | * @param cb 回调 325 | */ 326 | public void getUserGroup(final String accessToken, final String openId, Callback cb) { 327 | doAsync(new AsyncFunction(cb) { 328 | @Override 329 | public Integer execute() { 330 | return getUserGroup(accessToken, openId); 331 | } 332 | }); 333 | } 334 | 335 | /** 336 | * 获取用户所在组 337 | * 338 | * @param accessToken accessToken 339 | * @param openId 用户openId 340 | * @return 组ID,或抛WechatException 341 | */ 342 | public Integer getUserGroup(String accessToken, String openId) { 343 | checkNotNullAndEmpty(accessToken, "accessToken"); 344 | checkNotNullAndEmpty(openId, "openId"); 345 | 346 | String url = GROUP_OF_USER + accessToken; 347 | Map params = Maps.newHashMapWithExpectedSize(1); 348 | params.put("openid", openId); 349 | 350 | Map resp = doPost(url, params); 351 | return (Integer) resp.get("groupid"); 352 | } 353 | 354 | /** 355 | * 移动用户所在组 356 | * 357 | * @param openId 用户openId 358 | * @param groupId 新组ID 359 | * @return 移动成功返回true,或抛WechatException 360 | */ 361 | public Boolean mvUserGroup(String openId, Integer groupId) { 362 | return mvUserGroup(loadAccessToken(), openId, groupId); 363 | } 364 | 365 | /** 366 | * 移动用户所在组 367 | * 368 | * @param openId 用户openId 369 | * @param groupId 新组ID 370 | * @param cb 回调 371 | */ 372 | public void mvUserGroup(final String openId, final Integer groupId, Callback cb) { 373 | mvUserGroup(loadAccessToken(), openId, groupId, cb); 374 | } 375 | 376 | /** 377 | * 移动用户所在组 378 | * 379 | * @param accessToken accessToken 380 | * @param openId 用户openId 381 | * @param groupId 新组ID 382 | * @param cb 回调 383 | */ 384 | public void mvUserGroup(final String accessToken, final String openId, final Integer groupId, Callback cb) { 385 | doAsync(new AsyncFunction(cb) { 386 | @Override 387 | public Boolean execute() { 388 | return mvUserGroup(accessToken, openId, groupId); 389 | } 390 | }); 391 | } 392 | 393 | /** 394 | * 移动用户所在组 395 | * 396 | * @param accessToken accessToken 397 | * @param openId 用户openId 398 | * @param groupId 新组ID 399 | * @return 移动成功返回true,或抛WechatException 400 | */ 401 | public Boolean mvUserGroup(String accessToken, String openId, Integer groupId) { 402 | checkNotNullAndEmpty(accessToken, "accessToken"); 403 | checkNotNullAndEmpty(openId, "openId"); 404 | checkArgument(groupId != null && groupId > 0, "groupId must > 0"); 405 | 406 | String url = MOVE_USER_GROUP + accessToken; 407 | Map params = Maps.newHashMapWithExpectedSize(2); 408 | params.put("openid", openId); 409 | params.put("to_groupid", groupId); 410 | 411 | doPost(url, params); 412 | return Boolean.TRUE; 413 | } 414 | 415 | /** 416 | * 拉取用户信息(若用户未关注,且未授权,将拉取不了信息) 417 | * 418 | * @param openId 用户openId 419 | * @return 用户信息,或抛WechatException 420 | */ 421 | public User getUser(String openId) { 422 | return getUser(loadAccessToken(), openId); 423 | } 424 | 425 | /** 426 | * 拉取用户信息(若用户未关注,且未授权,将拉取不了信息) 427 | * 428 | * @param openId 用户openId 429 | * @param cb 回调 430 | */ 431 | public void getUser(final String openId, Callback cb) { 432 | getUser(loadAccessToken(), openId, cb); 433 | } 434 | 435 | /** 436 | * 拉取用户信息(若用户未关注,且未授权,将拉取不了信息) 437 | * 438 | * @param accessToken accessToken 439 | * @param openId 用户openId 440 | * @param cb 回调 441 | */ 442 | public void getUser(final String accessToken, final String openId, Callback cb) { 443 | doAsync(new AsyncFunction(cb) { 444 | @Override 445 | public User execute() { 446 | return getUser(accessToken, openId); 447 | } 448 | }); 449 | } 450 | 451 | /** 452 | * 拉取用户信息(若用户未关注,且未授权,将拉取不了信息) 453 | * 454 | * @param accessToken accessToken 455 | * @param openId 用户openId 456 | * @return 用户信息,或抛WechatException 457 | */ 458 | public User getUser(String accessToken, String openId) { 459 | checkNotNullAndEmpty(accessToken, "accessToken"); 460 | checkNotNullAndEmpty(openId, "openId"); 461 | 462 | String url = GET_USER_INFO + accessToken + "&openid=" + openId; 463 | Map resp = doGet(url); 464 | 465 | return Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), User.class); 466 | } 467 | 468 | /** 469 | * 获取用户信息(用户未关注,但已手动同意授权,并通过code获取到授权accessToken) 470 | * @param authAccessToken 用户手动同意授权后,通过code获取的accessToken 471 | * @return 用户信息,或抛WechatException 472 | * @see Bases#authAccessToken(String) 473 | */ 474 | public UserInfo getUserInfo(AuthAccessToken authAccessToken) { 475 | 476 | String url = GET_USER_INFO_AUTHED + authAccessToken.getAccessToken() + "&openid=" + authAccessToken.getOpenId(); 477 | Map resp = doGet(url); 478 | 479 | return Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), UserInfo.class); 480 | } 481 | 482 | /** 483 | * 拉取用户列表信息 484 | * 485 | * @param nextOpenId nextOpenId 486 | * @return 用户列表 487 | */ 488 | public UserList getUsers(String nextOpenId) { 489 | return getUsers(loadAccessToken(), nextOpenId); 490 | } 491 | 492 | /** 493 | * 拉取用户列表信息 494 | * 495 | * @param nextOpenId nextOpenId 496 | * @param cb 回调 497 | */ 498 | public void getUsers(String nextOpenId, Callback cb) { 499 | getUsers(loadAccessToken(), nextOpenId, cb); 500 | } 501 | 502 | /** 503 | * 拉取用户列表信息 504 | * 505 | * @param accessToken accessToken 506 | * @param nextOpenId nextOpenId 507 | * @param cb 回调 508 | */ 509 | public void getUsers(final String accessToken, final String nextOpenId, Callback cb) { 510 | doAsync(new AsyncFunction(cb) { 511 | @Override 512 | public UserList execute() throws Exception { 513 | return getUsers(accessToken, nextOpenId); 514 | } 515 | }); 516 | } 517 | 518 | /** 519 | * 拉取用户列表信息 520 | * 521 | * @param accessToken accessToken 522 | * @param nextOpenId 第一个拉取的OPENID,不填默认从头开始拉取 523 | * @return 用户列表,或抛WechatExeption 524 | */ 525 | public UserList getUsers(String accessToken, String nextOpenId) { 526 | checkNotNullAndEmpty(accessToken, "accessToken"); 527 | String url = GET_USERS_INFO + accessToken; 528 | 529 | if (!Strings.isNullOrEmpty(nextOpenId)) 530 | url += ("&next_openid=" + nextOpenId); 531 | 532 | Map resp = doGet(url); 533 | 534 | return Jsons.DEFAULT.fromJson(Jsons.DEFAULT.toJson(resp), UserList.class); 535 | } 536 | 537 | /** 538 | * 备注用户 539 | * 540 | * @param openId 用户openId 541 | * @param remark 备注 542 | * @return 备注成功返回true,或抛WechatException 543 | */ 544 | public Boolean remarkUser(String openId, String remark) { 545 | return remarkUser(loadAccessToken(), openId, remark); 546 | } 547 | 548 | /** 549 | * 备注用户 550 | * 551 | * @param openId 用户openId 552 | * @param remark 备注 553 | * @param cb 回调 554 | */ 555 | public void remarkUser(final String openId, final String remark, Callback cb) { 556 | remarkUser(loadAccessToken(), openId, remark, cb); 557 | } 558 | 559 | /** 560 | * 备注用户 561 | * 562 | * @param accessToken accessToken 563 | * @param openId 用户openId 564 | * @param remark 备注 565 | * @param cb 回调 566 | */ 567 | public void remarkUser(final String accessToken, final String openId, final String remark, Callback cb) { 568 | doAsync(new AsyncFunction(cb) { 569 | @Override 570 | public Boolean execute() { 571 | return remarkUser(accessToken, openId, remark); 572 | } 573 | }); 574 | } 575 | 576 | /** 577 | * 备注用户 578 | * 579 | * @param accessToken accessToken 580 | * @param openId 用户openId 581 | * @param remark 备注 582 | * @return 备注成功返回true,或抛WechatException 583 | */ 584 | public Boolean remarkUser(String accessToken, String openId, String remark) { 585 | checkNotNullAndEmpty(accessToken, "accessToken"); 586 | checkNotNullAndEmpty(openId, "openId"); 587 | checkNotNullAndEmpty(remark, "remark"); 588 | 589 | String url = REMARK_USER + accessToken; 590 | 591 | Map params = Maps.newHashMapWithExpectedSize(2); 592 | params.put("openid", openId); 593 | params.put("remark", remark); 594 | 595 | doPost(url, params); 596 | return Boolean.TRUE; 597 | } 598 | } 599 | --------------------------------------------------------------------------------