@@ -6,10 +6,10 @@ | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>3.1.0</version> | |||
<version>3.2.0</version> | |||
<packaging>pom</packaging> | |||
<name>Weixin Java Tools - Parent</name> | |||
<description>微信公众号、企业号上级POM</description> | |||
<description>微信开发Java SDK</description> | |||
<url>https://github.com/wechat-group/weixin-java-tools</url> | |||
<licenses> | |||
@@ -83,20 +83,6 @@ | |||
<module name="EmptyLineSeparator"> | |||
<property name="allowNoEmptyLineBetweenFields" value="true"/> | |||
</module> | |||
<module name="SeparatorWrap"> | |||
<property name="id" value="SeparatorWrapDot"/> | |||
<property name="tokens" value="DOT"/> | |||
<property name="option" value="nl"/> | |||
</module> | |||
<module name="SeparatorWrap"> | |||
<property name="id" value="SeparatorWrapComma"/> | |||
<property name="tokens" value="COMMA"/> | |||
<property name="option" value="EOL"/> | |||
</module> | |||
<module name="SeparatorWrap"> | |||
<property name="id" value="SeparatorWrapMethodRef"/> | |||
<property name="option" value="nl"/> | |||
</module> | |||
<module name="PackageName"> | |||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> | |||
<message key="name.invalidPattern" | |||
@@ -156,7 +142,7 @@ | |||
<module name="VariableDeclarationUsageDistance"/> | |||
<module name="CustomImportOrder"> | |||
<property name="customImportOrderRules" | |||
value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> | |||
value="SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###STATIC"/> | |||
<property name="specialImportsRegExp" value="^javax\."/> | |||
<property name="standardPackageRegExp" value="^java\."/> | |||
<property name="sortImportsInGroupAlphabetically" value="true"/> | |||
@@ -7,12 +7,12 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>3.1.0</version> | |||
<version>3.2.0</version> | |||
</parent> | |||
<artifactId>weixin-java-common</artifactId> | |||
<name>Weixin Java Tools - Common</name> | |||
<description>微信公众号、企业号Java SDK Common</description> | |||
<description>微信开发Java SDK公共模块</description> | |||
<dependencies> | |||
<dependency> | |||
@@ -6,7 +6,7 @@ import java.util.Map; | |||
/** | |||
* 微信开发所使用到的常量类. | |||
* | |||
* @author chanjarster & binarywang | |||
* @author Daniel Qian & binarywang | |||
*/ | |||
public class WxConsts { | |||
/** | |||
@@ -82,7 +82,7 @@ public class WxConsts { | |||
/** | |||
* 小程序卡片(要求小程序与公众号已关联) | |||
*/ | |||
public static final String MINIPROGRAMPAGE="miniprogrampage"; | |||
public static final String MINIPROGRAMPAGE = "miniprogrampage"; | |||
} | |||
/** | |||
@@ -151,14 +151,106 @@ public class WxConsts { | |||
public static final String CLICK = "CLICK"; | |||
public static final String VIEW = "VIEW"; | |||
public static final String MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH"; | |||
/** | |||
* 扫码推事件的事件推送 | |||
*/ | |||
public static final String SCANCODE_PUSH = "scancode_push"; | |||
/** | |||
* 扫码推事件且弹出“消息接收中”提示框的事件推送. | |||
*/ | |||
public static final String SCANCODE_WAITMSG = "scancode_waitmsg"; | |||
/** | |||
* 弹出系统拍照发图的事件推送. | |||
*/ | |||
public static final String PIC_SYSPHOTO = "pic_sysphoto"; | |||
/** | |||
* 弹出拍照或者相册发图的事件推送. | |||
*/ | |||
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||
/** | |||
* 弹出微信相册发图器的事件推送. | |||
*/ | |||
public static final String PIC_WEIXIN = "pic_weixin"; | |||
/** | |||
* 弹出地理位置选择器的事件推送. | |||
*/ | |||
public static final String LOCATION_SELECT = "location_select"; | |||
public static final String TEMPLATE_SEND_JOB_FINISH = "TEMPLATESENDJOBFINISH"; | |||
public static final String ENTER_AGENT = "enter_agent"; | |||
/** | |||
* 微信小店 订单付款通知. | |||
*/ | |||
public static final String MERCHANT_ORDER = "merchant_order"; | |||
/** | |||
* 卡券事件:卡券通过审核 | |||
*/ | |||
public static final String CARD_PASS_CHECK = "card_pass_check"; | |||
/** | |||
* 卡券事件:卡券未通过审核 | |||
*/ | |||
public static final String CARD_NOT_PASS_CHECK = "card_not_pass_check"; | |||
/** | |||
* 卡券事件:用户领取卡券 | |||
*/ | |||
public static final String CARD_USER_GET_CARD = "user_get_card"; | |||
/** | |||
* 卡券事件:用户转赠卡券 | |||
*/ | |||
public static final String CARD_USER_GIFTING_CARD = "user_gifting_card"; | |||
/** | |||
* 卡券事件:用户核销卡券 | |||
*/ | |||
public static final String CARD_USER_CONSUME_CARD = "user_consume_card"; | |||
/** | |||
* 卡券事件:用户通过卡券的微信买单完成时推送 | |||
*/ | |||
public static final String CARD_USER_PAY_FROM_PAY_CELL = "user_pay_from_pay_cell"; | |||
/** | |||
* 卡券事件:用户提交会员卡开卡信息 | |||
*/ | |||
public static final String CARD_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info"; | |||
/** | |||
* 卡券事件:用户打开查看卡券 | |||
*/ | |||
public static final String CARD_USER_VIEW_CARD = "user_view_card"; | |||
/** | |||
* 卡券事件:用户删除卡券 | |||
*/ | |||
public static final String CARD_USER_DEL_CARD = "user_del_card"; | |||
/** | |||
* 卡券事件:用户在卡券里点击查看公众号进入会话时(需要用户已经关注公众号) | |||
*/ | |||
public static final String CARD_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card"; | |||
/** | |||
* 卡券事件:当用户的会员卡积分余额发生变动时 | |||
*/ | |||
public static final String CARD_UPDATE_MEMBER_CARD = "update_member_card"; | |||
/** | |||
* 卡券事件:当某个card_id的初始库存数大于200且当前库存小于等于100时,用户尝试领券会触发发送事件给商户,事件每隔12h发送一次 | |||
*/ | |||
public static final String CARD_SKU_REMIND = "card_sku_remind"; | |||
/** | |||
* 卡券事件:当商户朋友的券券点发生变动时 | |||
*/ | |||
public static final String CARD_PAY_ORDER = "card_pay_order"; | |||
} | |||
/** | |||
@@ -230,10 +322,16 @@ public class WxConsts { | |||
* 不弹出授权页面,直接跳转,只能获取用户openid. | |||
*/ | |||
public static final String SNSAPI_BASE = "snsapi_base"; | |||
/** | |||
* 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息. | |||
*/ | |||
public static final String SNSAPI_USERINFO = "snsapi_userinfo"; | |||
/** | |||
* 手动授权,可获取成员的详细信息,包含手机、邮箱。只适用于企业微信或企业号. | |||
*/ | |||
public static final String SNSAPI_PRIVATEINFO = "snsapi_privateinfo"; | |||
} | |||
/** | |||
@@ -252,4 +350,5 @@ public class WxConsts { | |||
public static final String IMAGE = "image"; | |||
public static final String VIDEO = "video"; | |||
} | |||
} |
@@ -4,6 +4,8 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* WxErrorException处理器. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public interface WxErrorExceptionHandler { | |||
@@ -5,6 +5,8 @@ package me.chanjar.weixin.common.api; | |||
* 消息重复检查器. | |||
* 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 | |||
* </pre> | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public interface WxMessageDuplicateChecker { | |||
@@ -9,6 +9,8 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||
* 默认消息重复检查器. | |||
* 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒 | |||
* </pre> | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker { | |||
@@ -1,10 +1,15 @@ | |||
package me.chanjar.weixin.common.bean; | |||
import java.io.Serializable; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* access token. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@Data | |||
public class WxAccessToken implements Serializable { | |||
private static final long serialVersionUID = 8709719312922168909L; | |||
@@ -1,10 +1,12 @@ | |||
package me.chanjar.weixin.common.bean; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import java.io.Serializable; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import lombok.Data; | |||
/** | |||
* 卡券Api签名. | |||
* | |||
@@ -35,7 +37,6 @@ public class WxCardApiSignature implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
} |
@@ -1,13 +1,21 @@ | |||
package me.chanjar.weixin.common.bean; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.NoArgsConstructor; | |||
/** | |||
* jspai signature. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@Data | |||
@Builder | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
public class WxJsapiSignature implements Serializable { | |||
private static final long serialVersionUID = -1116808193154384804L; | |||
@@ -1,9 +1,5 @@ | |||
package me.chanjar.weixin.common.bean.menu; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.io.Serializable; | |||
@@ -11,6 +7,12 @@ import java.nio.charset.StandardCharsets; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
/** | |||
* 菜单(公众号和企业号共用的). | |||
* | |||
@@ -47,7 +49,7 @@ public class WxMenu implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
} |
@@ -1,13 +1,20 @@ | |||
package me.chanjar.weixin.common.bean.menu; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
/** | |||
* menu button. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@Data | |||
public class WxMenuButton implements Serializable { | |||
private static final long serialVersionUID = -1070939403109776555L; | |||
@@ -76,7 +83,7 @@ public class WxMenuButton implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
} |
@@ -1,11 +1,18 @@ | |||
package me.chanjar.weixin.common.bean.menu; | |||
import java.io.Serializable; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import java.io.Serializable; | |||
/** | |||
* menu rule. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@Data | |||
public class WxMenuRule implements Serializable { | |||
private static final long serialVersionUID = -4587181819499286670L; | |||
@@ -24,6 +31,6 @@ public class WxMenuRule implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
} |
@@ -1,15 +1,22 @@ | |||
package me.chanjar.weixin.common.bean.result; | |||
import java.io.Serializable; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@Data | |||
public class WxMediaUploadResult implements Serializable { | |||
private static final long serialVersionUID = 330834334738622341L; | |||
private String url; | |||
private String type; | |||
private String mediaId; | |||
private String thumbMediaId; | |||
@@ -21,7 +28,7 @@ public class WxMediaUploadResult implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
} |
@@ -1,5 +1,9 @@ | |||
package me.chanjar.weixin.common.session; | |||
/** | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public interface InternalSession { | |||
/** | |||
@@ -1,5 +1,8 @@ | |||
package me.chanjar.weixin.common.session; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public interface InternalSessionManager { | |||
/** | |||
@@ -1,11 +1,18 @@ | |||
package me.chanjar.weixin.common.session; | |||
import me.chanjar.weixin.common.util.res.StringManager; | |||
import java.util.*; | |||
import java.util.Collections; | |||
import java.util.Enumeration; | |||
import java.util.HashSet; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import me.chanjar.weixin.common.util.res.StringManager; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class StandardSession implements WxSession, InternalSession { | |||
/** | |||
@@ -2,6 +2,9 @@ package me.chanjar.weixin.common.session; | |||
import java.util.Enumeration; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class StandardSessionFacade implements WxSession { | |||
/** | |||
@@ -1,15 +1,18 @@ | |||
package me.chanjar.weixin.common.session; | |||
import me.chanjar.weixin.common.util.res.StringManager; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import me.chanjar.weixin.common.util.res.StringManager; | |||
/** | |||
* 基于内存的session manager | |||
* 基于内存的session manager. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | |||
@@ -19,6 +19,8 @@ package me.chanjar.weixin.common.session; | |||
/** | |||
* An exception that indicates the maximum number of active sessions has been | |||
* reached and the server is refusing to create any new sessions. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class TooManyActiveSessionsException | |||
extends IllegalStateException { | |||
@@ -2,6 +2,9 @@ package me.chanjar.weixin.common.session; | |||
import java.util.Enumeration; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public interface WxSession { | |||
Object getAttribute(String name); | |||
@@ -1,5 +1,8 @@ | |||
package me.chanjar.weixin.common.session; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public interface WxSessionManager { | |||
/** | |||
@@ -0,0 +1,40 @@ | |||
package me.chanjar.weixin.common.util; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.codec.binary.Hex; | |||
import javax.crypto.Mac; | |||
import javax.crypto.spec.SecretKeySpec; | |||
import java.security.InvalidKeyException; | |||
import java.security.NoSuchAlgorithmException; | |||
/** | |||
* <pre> | |||
* 签名工具类 | |||
* Created by BinaryWang on 2018/7/11. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Slf4j | |||
public class SignUtils { | |||
/** | |||
* HmacSHA256 签名算法 | |||
* | |||
* @param message 签名数据 | |||
* @param key 签名密钥 | |||
*/ | |||
public static String createHmacSha256Sign(String message, String key) { | |||
try { | |||
Mac sha256 = Mac.getInstance("HmacSHA256"); | |||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256"); | |||
sha256.init(secretKeySpec); | |||
byte[] bytes = sha256.doFinal(message.getBytes()); | |||
return Hex.encodeHexString(bytes).toUpperCase(); | |||
} catch (NoSuchAlgorithmException | InvalidKeyException e) { | |||
SignUtils.log.error(e.getMessage(), e); | |||
} | |||
return null; | |||
} | |||
} |
@@ -1,64 +0,0 @@ | |||
package me.chanjar.weixin.common.util; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
/** | |||
* <pre> | |||
* 自定义的ToString方法,用于产生去掉空值属性的字符串 | |||
* Created by Binary Wang on 2016-10-27. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class ToStringUtils { | |||
public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle(); | |||
/** | |||
* 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串 | |||
* | |||
* @param obj | |||
*/ | |||
public static String toSimpleString(Object obj) { | |||
String toStringResult = ToStringBuilder.reflectionToString(obj, THE_STYLE); | |||
String[] split = toStringResult.split(SimpleMultiLineToStringStyle.LINE_SEPARATOR); | |||
StringBuilder result = new StringBuilder(); | |||
for (String string : split) { | |||
if (string.endsWith(SimpleMultiLineToStringStyle.NULL_TEXT)) { | |||
continue; | |||
} | |||
result.append(string + SimpleMultiLineToStringStyle.LINE_SEPARATOR); | |||
} | |||
if (result.length() == 0) { | |||
return ""; | |||
} | |||
//如果没有非空的属性,就输出 <all null properties> | |||
if (StringUtils.countMatches(result, SimpleMultiLineToStringStyle.LINE_SEPARATOR) == 2) { | |||
return result.toString().split(SimpleMultiLineToStringStyle.LINE_SEPARATOR)[0] | |||
+ "<all null values>]"; | |||
} | |||
return result.deleteCharAt(result.length() - 1).toString(); | |||
} | |||
private static class SimpleMultiLineToStringStyle extends ToStringStyle { | |||
private static final long serialVersionUID = 4645306494220335355L; | |||
private static final String LINE_SEPARATOR = "\n"; | |||
private static final String NULL_TEXT = "<null>"; | |||
public SimpleMultiLineToStringStyle() { | |||
super(); | |||
this.setContentStart("["); | |||
this.setFieldSeparator(LINE_SEPARATOR + " "); | |||
this.setFieldSeparatorAtStart(true); | |||
this.setContentEnd(LINE_SEPARATOR + "]"); | |||
this.setNullText(NULL_TEXT); | |||
this.setUseShortClassName(true); | |||
this.setUseIdentityHashCode(false); | |||
} | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
package me.chanjar.weixin.common.util.http.apache; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.annotation.NotThreadSafe; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
@@ -236,9 +237,10 @@ public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder { | |||
if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { | |||
// 使用代理服务器 需要用户认证的代理服务器 | |||
CredentialsProvider provider = new BasicCredentialsProvider(); | |||
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort) | |||
, new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); | |||
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), | |||
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); | |||
this.httpClientBuilder.setDefaultCredentialsProvider(provider); | |||
this.httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort)); | |||
} | |||
if (StringUtils.isNotBlank(this.userAgent)) { | |||
@@ -1,6 +1,7 @@ | |||
package me.chanjar.weixin.common.util.http.apache; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.annotation.NotThreadSafe; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
@@ -226,29 +227,26 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
.setConnectionManager(connectionManager) | |||
.setConnectionManagerShared(true) | |||
.setSSLSocketFactory(this.buildSSLConnectionSocketFactory()) | |||
.setDefaultRequestConfig( | |||
RequestConfig.custom() | |||
.setSocketTimeout(this.soTimeout) | |||
.setConnectTimeout(this.connectionTimeout) | |||
.setConnectionRequestTimeout(this.connectionRequestTimeout) | |||
.build() | |||
) | |||
.setRetryHandler(this.httpRequestRetryHandler); | |||
if (StringUtils.isNotBlank(this.httpProxyHost) | |||
&& StringUtils.isNotBlank(this.httpProxyUsername)) { | |||
.setDefaultRequestConfig(RequestConfig.custom() | |||
.setSocketTimeout(this.soTimeout) | |||
.setConnectTimeout(this.connectionTimeout) | |||
.setConnectionRequestTimeout(this.connectionRequestTimeout) | |||
.build() | |||
).setRetryHandler(this.httpRequestRetryHandler); | |||
if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { | |||
// 使用代理服务器 需要用户认证的代理服务器 | |||
CredentialsProvider provider = new BasicCredentialsProvider(); | |||
provider.setCredentials( | |||
new AuthScope(this.httpProxyHost, this.httpProxyPort), | |||
new UsernamePasswordCredentials(this.httpProxyUsername, | |||
this.httpProxyPassword)); | |||
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), | |||
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); | |||
httpClientBuilder.setDefaultCredentialsProvider(provider); | |||
httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort)); | |||
} | |||
if (StringUtils.isNotBlank(this.userAgent)) { | |||
httpClientBuilder.setUserAgent(this.userAgent); | |||
} | |||
this.closeableHttpClient = httpClientBuilder.build(); | |||
prepared.set(true); | |||
} | |||
@@ -1,10 +1,14 @@ | |||
package me.chanjar.weixin.common.util.json; | |||
import com.google.gson.*; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import java.lang.reflect.Type; | |||
import com.google.gson.JsonDeserializationContext; | |||
import com.google.gson.JsonDeserializer; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParseException; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
@@ -12,22 +16,25 @@ public class WxMediaUploadResultAdapter implements JsonDeserializer<WxMediaUploa | |||
@Override | |||
public WxMediaUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
WxMediaUploadResult uploadResult = new WxMediaUploadResult(); | |||
JsonObject uploadResultJsonObject = json.getAsJsonObject(); | |||
WxMediaUploadResult result = new WxMediaUploadResult(); | |||
JsonObject jsonObject = json.getAsJsonObject(); | |||
if (jsonObject.get("url") != null && !jsonObject.get("url").isJsonNull()) { | |||
result.setUrl(GsonHelper.getAsString(jsonObject.get("url"))); | |||
} | |||
if (uploadResultJsonObject.get("type") != null && !uploadResultJsonObject.get("type").isJsonNull()) { | |||
uploadResult.setType(GsonHelper.getAsString(uploadResultJsonObject.get("type"))); | |||
if (jsonObject.get("type") != null && !jsonObject.get("type").isJsonNull()) { | |||
result.setType(GsonHelper.getAsString(jsonObject.get("type"))); | |||
} | |||
if (uploadResultJsonObject.get("media_id") != null && !uploadResultJsonObject.get("media_id").isJsonNull()) { | |||
uploadResult.setMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("media_id"))); | |||
if (jsonObject.get("media_id") != null && !jsonObject.get("media_id").isJsonNull()) { | |||
result.setMediaId(GsonHelper.getAsString(jsonObject.get("media_id"))); | |||
} | |||
if (uploadResultJsonObject.get("thumb_media_id") != null && !uploadResultJsonObject.get("thumb_media_id").isJsonNull()) { | |||
uploadResult.setThumbMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("thumb_media_id"))); | |||
if (jsonObject.get("thumb_media_id") != null && !jsonObject.get("thumb_media_id").isJsonNull()) { | |||
result.setThumbMediaId(GsonHelper.getAsString(jsonObject.get("thumb_media_id"))); | |||
} | |||
if (uploadResultJsonObject.get("created_at") != null && !uploadResultJsonObject.get("created_at").isJsonNull()) { | |||
uploadResult.setCreatedAt(GsonHelper.getAsPrimitiveLong(uploadResultJsonObject.get("created_at"))); | |||
if (jsonObject.get("created_at") != null && !jsonObject.get("created_at").isJsonNull()) { | |||
result.setCreatedAt(GsonHelper.getAsPrimitiveLong(jsonObject.get("created_at"))); | |||
} | |||
return uploadResult; | |||
return result; | |||
} | |||
} |
@@ -7,7 +7,7 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>3.1.0</version> | |||
<version>3.2.0</version> | |||
</parent> | |||
<artifactId>weixin-java-cp</artifactId> | |||
@@ -0,0 +1,129 @@ | |||
package me.chanjar.weixin.cp; | |||
/** | |||
* <pre> | |||
* 企业微信常量 | |||
* Created by Binary Wang on 2018/8/25. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxCpConsts { | |||
/** | |||
* 企业微信端推送过来的事件类型. | |||
* 参考文档:https://work.weixin.qq.com/api/doc#12974 | |||
*/ | |||
public static class EventType { | |||
/** | |||
* 成员关注事件. | |||
*/ | |||
public static final String SUBSCRIBE = "subscribe"; | |||
/** | |||
* 成员取消关注事件. | |||
*/ | |||
public static final String UNSUBSCRIBE = "unsubscribe"; | |||
/** | |||
* 进入应用事件. | |||
*/ | |||
public static final String ENTER_AGENT = "enter_agent"; | |||
/** | |||
* 上报地理位置. | |||
*/ | |||
public static final String LOCATION = "LOCATION"; | |||
/** | |||
* 异步任务完成事件推送. | |||
*/ | |||
public static final String BATCH_JOB_RESULT = "batch_job_result"; | |||
/** | |||
* 企业微信通讯录变更事件. | |||
*/ | |||
public static final String CHANGE_CONTACT = "change_contact"; | |||
/** | |||
* 点击菜单拉取消息的事件推送. | |||
*/ | |||
public static final String CLICK = "click"; | |||
/** | |||
* 点击菜单跳转链接的事件推送. | |||
*/ | |||
public static final String VIEW = "view"; | |||
/** | |||
* 扫码推事件的事件推送. | |||
*/ | |||
public static final String SCANCODE_PUSH = "scancode_push"; | |||
/** | |||
* 扫码推事件且弹出“消息接收中”提示框的事件推送. | |||
*/ | |||
public static final String SCANCODE_WAITMSG = "scancode_waitmsg"; | |||
/** | |||
* 弹出系统拍照发图的事件推送. | |||
*/ | |||
public static final String PIC_SYSPHOTO = "pic_sysphoto"; | |||
/** | |||
* 弹出拍照或者相册发图的事件推送. | |||
*/ | |||
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||
/** | |||
* 弹出微信相册发图器的事件推送. | |||
*/ | |||
public static final String PIC_WEIXIN = "pic_weixin"; | |||
/** | |||
* 弹出地理位置选择器的事件推送. | |||
*/ | |||
public static final String LOCATION_SELECT = "location_select"; | |||
} | |||
/** | |||
* 企业微信通讯录变更事件. | |||
*/ | |||
public static class ContactChangeType { | |||
/** | |||
* 新增成员事件. | |||
*/ | |||
public static final String CREATE_USER = "create_user"; | |||
/** | |||
* 更新成员事件. | |||
*/ | |||
public static final String UPDATE_USER = "update_user"; | |||
/** | |||
* 删除成员事件. | |||
*/ | |||
public static final String DELETE_USER = "delete_user"; | |||
/** | |||
* 新增部门事件. | |||
*/ | |||
public static final String CREATE_PARTY = "create_party"; | |||
/** | |||
* 更新部门事件. | |||
*/ | |||
public static final String UPDATE_PARTY = "update_party"; | |||
/** | |||
* 删除部门事件. | |||
*/ | |||
public static final String DELETE_PARTY = "delete_party"; | |||
/** | |||
* 标签成员变更事件. | |||
*/ | |||
public static final String UPDATE_TAG = "update_tag"; | |||
} | |||
} |
@@ -3,9 +3,12 @@ package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.bean.WxCpAgent; | |||
import java.util.List; | |||
/** | |||
* <pre> | |||
* 管理企业号应用 | |||
* 文档地址:https://work.weixin.qq.com/api/doc#10087 | |||
* Created by huansinho on 2018/4/13. | |||
* </pre> | |||
* | |||
@@ -17,7 +20,7 @@ public interface WxCpAgentService { | |||
* <pre> | |||
* 获取企业号应用信息 | |||
* 该API用于获取企业号某个应用的基本信息,包括头像、昵称、帐号类型、认证类型、可见范围等信息 | |||
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=获取企业号应用 | |||
* 详情请见: https://work.weixin.qq.com/api/doc#10087 | |||
* </pre> | |||
* | |||
* @param agentId 企业应用的id | |||
@@ -25,4 +28,25 @@ public interface WxCpAgentService { | |||
*/ | |||
WxCpAgent get(Integer agentId) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 设置应用. | |||
* 仅企业可调用,可设置当前凭证对应的应用;第三方不可调用。 | |||
* 详情请见: https://work.weixin.qq.com/api/doc#10088 | |||
* </pre> | |||
* | |||
* @param agentInfo 应用信息 | |||
*/ | |||
void set(WxCpAgent agentInfo) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获取应用列表. | |||
* 企业仅可获取当前凭证对应的应用;第三方仅可获取被授权的应用。 | |||
* 详情请见: https://work.weixin.qq.com/api/doc#11214 | |||
* </pre> | |||
* | |||
*/ | |||
List<WxCpAgent> list() throws WxErrorException; | |||
} |
@@ -9,17 +9,20 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* <pre> | |||
* 媒体管理接口 | |||
* 媒体管理接口. | |||
* Created by BinaryWang on 2017/6/24. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface WxCpMediaService { | |||
String MEDIA_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||
String MEDIA_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type="; | |||
String IMG_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg"; | |||
/** | |||
* <pre> | |||
* 上传多媒体文件 | |||
* 上传多媒体文件. | |||
* 上传的多媒体文件有格式和大小限制,如下: | |||
* 图片(image): 1M,支持JPG格式 | |||
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||
@@ -36,6 +39,8 @@ public interface WxCpMediaService { | |||
throws WxErrorException, IOException; | |||
/** | |||
* 上传多媒体文件. | |||
* | |||
* @param mediaType 媒体类型 | |||
* @param file 文件对象 | |||
* @see #upload(String, String, InputStream) | |||
@@ -44,7 +49,7 @@ public interface WxCpMediaService { | |||
/** | |||
* <pre> | |||
* 下载多媒体文件 | |||
* 下载多媒体文件. | |||
* 根据微信文档,视频文件下载不了,会返回null | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||
* </pre> | |||
@@ -54,4 +59,17 @@ public interface WxCpMediaService { | |||
*/ | |||
File download(String mediaId) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 上传图片. | |||
* 上传图片得到图片URL,该URL永久有效 | |||
* 返回的图片URL,仅能用于图文消息(mpnews)正文中的图片展示;若用于非企业微信域名下的页面,图片将被屏蔽。 | |||
* 每个企业每天最多可上传100张图片 | |||
* 接口url格式:https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @param file 上传的文件对象 | |||
* @return 返回图片url | |||
*/ | |||
String uploadImg(File file) throws WxErrorException; | |||
} |
@@ -34,6 +34,19 @@ public interface WxCpOAuth2Service { | |||
*/ | |||
String buildAuthorizationUrl(String redirectUri, String state); | |||
/** | |||
* <pre> | |||
* 构造oauth2授权的url连接 | |||
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code | |||
* </pre> | |||
* | |||
* @param redirectUri 跳转链接地址 | |||
* @param state 状态码 | |||
* @param scope 取值参考me.chanjar.weixin.common.api.WxConsts.OAuth2Scope类 | |||
* @return url | |||
*/ | |||
String buildAuthorizationUrl(String redirectUri, String state, String scope); | |||
/** | |||
* <pre> | |||
* 用oauth2获取用户信息 | |||
@@ -51,16 +64,17 @@ public interface WxCpOAuth2Service { | |||
/** | |||
* <pre> | |||
* 用oauth2获取用户信息 | |||
* 根据code获取成员信息 | |||
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息 | |||
* https://work.weixin.qq.com/api/doc#10028/根据code获取成员信息 | |||
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。 | |||
* | |||
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||
* </pre> | |||
* | |||
* @param agentId 企业号应用的id | |||
* @param code 微信oauth授权返回的代码 | |||
* @return [userid, deviceid] | |||
* @param code 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 | |||
* @return [UserId, DeviceId, OpenId, user_ticket, expires_in] | |||
* @see #getUserInfo(String) | |||
*/ | |||
String[] getUserInfo(Integer agentId, String code) throws WxErrorException; | |||
@@ -77,7 +91,7 @@ public interface WxCpOAuth2Service { | |||
* 需要有对应应用的使用权限,且成员必须在授权应用的可见范围内。 | |||
* </pre> | |||
* | |||
* @param userTicket 成员票据 | |||
* @param userTicket 成员票据 | |||
*/ | |||
WxCpUserDetail getUserDetail(String userTicket) throws WxErrorException; | |||
} |
@@ -1,11 +1,12 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import java.util.List; | |||
import java.util.Map; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.bean.WxCpInviteResult; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
import java.util.List; | |||
import java.util.Map; | |||
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; | |||
/** | |||
* <pre> | |||
@@ -18,7 +19,7 @@ import java.util.Map; | |||
public interface WxCpUserService { | |||
/** | |||
* <pre> | |||
* 用在二次验证的时候 | |||
* 用在二次验证的时候. | |||
* 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。 | |||
* </pre> | |||
* | |||
@@ -28,7 +29,7 @@ public interface WxCpUserService { | |||
/** | |||
* <pre> | |||
* 获取部门成员(详情) | |||
* 获取部门成员(详情). | |||
* | |||
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29 | |||
* </pre> | |||
@@ -41,7 +42,7 @@ public interface WxCpUserService { | |||
/** | |||
* <pre> | |||
* 获取部门成员 | |||
* 获取部门成员. | |||
* | |||
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98 | |||
* </pre> | |||
@@ -53,14 +54,14 @@ public interface WxCpUserService { | |||
List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
/** | |||
* 新建用户 | |||
* 新建用户. | |||
* | |||
* @param user 用户对象 | |||
*/ | |||
void create(WxCpUser user) throws WxErrorException; | |||
/** | |||
* 更新用户 | |||
* 更新用户. | |||
* | |||
* @param user 用户对象 | |||
*/ | |||
@@ -68,7 +69,7 @@ public interface WxCpUserService { | |||
/** | |||
* <pre> | |||
* 删除用户/批量删除成员 | |||
* 删除用户/批量删除成员. | |||
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98 | |||
* </pre> | |||
* | |||
@@ -77,7 +78,7 @@ public interface WxCpUserService { | |||
void delete(String... userIds) throws WxErrorException; | |||
/** | |||
* 获取用户 | |||
* 获取用户. | |||
* | |||
* @param userid 用户id | |||
*/ | |||
@@ -85,7 +86,7 @@ public interface WxCpUserService { | |||
/** | |||
* <pre> | |||
* 邀请成员 | |||
* 邀请成员. | |||
* 企业可通过接口批量邀请成员使用企业微信,邀请后将通过短信或邮件下发通知。 | |||
* 请求方式:POST(HTTPS) | |||
* 请求地址: https://qyapi.weixin.qq.com/cgi-bin/batch/invite?access_token=ACCESS_TOKEN | |||
@@ -113,14 +114,14 @@ public interface WxCpUserService { | |||
* @param userId 企业内的成员id | |||
* @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票 | |||
* @return map对象,可能包含以下值: | |||
* - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid | |||
* - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 | |||
* - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid | |||
* - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 | |||
*/ | |||
Map<String, String> userId2Openid(String userId, Integer agentId) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* openid转userid | |||
* openid转userid. | |||
* | |||
* 该接口主要应用于使用微信支付、微信红包和企业转账之后的结果查询。 | |||
* 开发者需要知道某个结果事件的openid对应企业微信内成员的信息时,可以通过调用该接口进行转换查询。 | |||
@@ -134,4 +135,17 @@ public interface WxCpUserService { | |||
* @return userid 该openid在企业微信对应的成员userid | |||
*/ | |||
String openid2UserId(String openid) throws WxErrorException; | |||
/** | |||
* 获取外部联系人详情. | |||
* <pre> | |||
* 企业可通过此接口,根据外部联系人的userid,拉取外部联系人详情。权限说明: | |||
* 企业需要使用外部联系人管理secret所获取的accesstoken来调用 | |||
* 第三方应用需拥有“企业客户”权限。 | |||
* 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。 | |||
* </pre> | |||
* | |||
* @param userId 外部联系人的userid | |||
*/ | |||
WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; | |||
} |
@@ -1,9 +1,17 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.gson.reflect.TypeToken; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.api.WxCpAgentService; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.bean.WxCpAgent; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.util.List; | |||
/** | |||
@@ -15,6 +23,8 @@ import me.chanjar.weixin.cp.bean.WxCpAgent; | |||
* @author <a href="https://github.com/huansinho">huansinho</a> | |||
*/ | |||
public class WxCpAgentServiceImpl implements WxCpAgentService { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private WxCpService mainService; | |||
public WxCpAgentServiceImpl(WxCpService mainService) { | |||
@@ -23,15 +33,36 @@ public class WxCpAgentServiceImpl implements WxCpAgentService { | |||
@Override | |||
public WxCpAgent get(Integer agentId) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/get"; | |||
if (agentId != null) { | |||
url += "?agentid=" + agentId; | |||
} else { | |||
if (agentId == null) { | |||
throw new IllegalArgumentException("缺少agentid参数"); | |||
} | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=" + agentId; | |||
String responseContent = this.mainService.get(url, null); | |||
return WxCpAgent.fromJson(responseContent); | |||
} | |||
@Override | |||
public void set(WxCpAgent agentInfo) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/set"; | |||
String responseContent = this.mainService.post(url, agentInfo.toJson()); | |||
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); | |||
if (jsonObject.get("errcode").getAsInt() != 0) { | |||
throw new WxErrorException(WxError.fromJson(responseContent)); | |||
} | |||
} | |||
@Override | |||
public List<WxCpAgent> list() throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/agent/list"; | |||
String responseContent = this.mainService.get(url, null); | |||
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); | |||
if (jsonObject.get("errcode").getAsInt() != 0) { | |||
throw new WxErrorException(WxError.fromJson(responseContent)); | |||
} | |||
return WxCpGsonBuilder.create().fromJson(jsonObject.get("agentlist").toString(), new TypeToken<List<WxCpAgent>>() { | |||
}.getType()); | |||
} | |||
} |
@@ -1,5 +1,10 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.UUID; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -8,17 +13,13 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
import me.chanjar.weixin.cp.api.WxCpMediaService; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.UUID; | |||
/** | |||
* <pre> | |||
* 媒体管理接口 | |||
* 媒体管理接口. | |||
* Created by Binary Wang on 2017-6-25. | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxCpMediaServiceImpl implements WxCpMediaService { | |||
private WxCpService mainService; | |||
@@ -35,16 +36,22 @@ public class WxCpMediaServiceImpl implements WxCpMediaService { | |||
@Override | |||
public WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; | |||
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file); | |||
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), | |||
MEDIA_UPLOAD_URL + mediaType, file); | |||
} | |||
@Override | |||
public File download(String mediaId) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||
return this.mainService.execute( | |||
BaseMediaDownloadRequestExecutor.create(this.mainService.getRequestHttp(), | |||
this.mainService.getWxCpConfigStorage().getTmpDirFile()), | |||
url, "media_id=" + mediaId); | |||
MEDIA_GET_URL, "media_id=" + mediaId); | |||
} | |||
@Override | |||
public String uploadImg(File file) throws WxErrorException { | |||
final WxMediaUploadResult result = this.mainService | |||
.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), IMG_UPLOAD_URL, file); | |||
return result.getUrl(); | |||
} | |||
} |
@@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.URIUtil; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
@@ -16,6 +17,8 @@ import me.chanjar.weixin.cp.bean.WxCpUserDetail; | |||
* | |||
* Created by Binary Wang on 2017-6-25. | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
* | |||
* @author Binary Wang | |||
* </pre> | |||
*/ | |||
public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service { | |||
@@ -35,16 +38,27 @@ public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service { | |||
@Override | |||
public String buildAuthorizationUrl(String redirectUri, String state) { | |||
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; | |||
url += "appid=" + this.mainService.getWxCpConfigStorage().getCorpId(); | |||
url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); | |||
url += "&response_type=code"; | |||
url += "&scope=snsapi_base"; | |||
return this.buildAuthorizationUrl(redirectUri, state, WxConsts.OAuth2Scope.SNSAPI_BASE); | |||
} | |||
@Override | |||
public String buildAuthorizationUrl(String redirectUri, String state, String scope) { | |||
StringBuilder url = new StringBuilder("https://open.weixin.qq.com/connect/oauth2/authorize?"); | |||
url.append("appid=").append(this.mainService.getWxCpConfigStorage().getCorpId()); | |||
url.append("&redirect_uri=").append(URIUtil.encodeURIComponent(redirectUri)); | |||
url.append("&response_type=code"); | |||
url.append("&scope=").append(scope); | |||
if (WxConsts.OAuth2Scope.SNSAPI_PRIVATEINFO.equals(scope) | |||
|| WxConsts.OAuth2Scope.SNSAPI_USERINFO.equals(scope)) { | |||
url.append("&agentid=").append(this.mainService.getWxCpConfigStorage().getAgentId()); | |||
} | |||
if (state != null) { | |||
url += "&state=" + state; | |||
url.append("&state=").append(state); | |||
} | |||
url += "#wechat_redirect"; | |||
return url; | |||
url.append("#wechat_redirect"); | |||
return url.toString(); | |||
} | |||
@Override | |||
@@ -61,7 +75,10 @@ public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service { | |||
JsonObject jo = je.getAsJsonObject(); | |||
return new String[]{GsonHelper.getString(jo, "UserId"), | |||
GsonHelper.getString(jo, "DeviceId"), | |||
GsonHelper.getString(jo, "OpenId")}; | |||
GsonHelper.getString(jo, "OpenId"), | |||
GsonHelper.getString(jo, "user_ticket"), | |||
GsonHelper.getString(jo, "expires_in") | |||
}; | |||
} | |||
@Override | |||
@@ -1,18 +1,23 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import java.util.List; | |||
import java.util.Map; | |||
import com.google.common.collect.Maps; | |||
import com.google.gson.*; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.gson.JsonPrimitive; | |||
import com.google.gson.reflect.TypeToken; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.api.WxCpUserService; | |||
import me.chanjar.weixin.cp.bean.WxCpInviteResult; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* <pre> | |||
* Created by BinaryWang on 2017/6/24. | |||
@@ -178,4 +183,11 @@ public class WxCpUserServiceImpl implements WxCpUserService { | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
return tmpJsonElement.getAsJsonObject().get("userid").getAsString(); | |||
} | |||
@Override | |||
public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/crm/get_external_contact?external_userid=" + userId; | |||
String responseContent = this.mainService.get(url, null); | |||
return WxCpUserExternalContactInfo.fromJson(responseContent); | |||
} | |||
} |
@@ -1,7 +1,10 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.NoArgsConstructor; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
@@ -16,16 +19,20 @@ import java.util.List; | |||
* @author <a href="https://github.com/huansinho">huansinho</a> | |||
*/ | |||
@Data | |||
@Builder | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
public class WxCpAgent implements Serializable { | |||
private static final long serialVersionUID = 5002894979081127234L; | |||
@SerializedName("errcode") | |||
private Integer errcode; | |||
private Integer errCode; | |||
@SerializedName("errmsg") | |||
private String errmsg; | |||
private String errMsg; | |||
@SerializedName("agentid") | |||
private Integer agentid; | |||
private Integer agentId; | |||
@SerializedName("name") | |||
private String name; | |||
@@ -33,14 +40,17 @@ public class WxCpAgent implements Serializable { | |||
@SerializedName("square_logo_url") | |||
private String squareLogoUrl; | |||
@SerializedName("logo_mediaid") | |||
private String logoMediaId; | |||
@SerializedName("description") | |||
private String description; | |||
@SerializedName("allow_userinfos") | |||
private Users allowUserinfos; | |||
private Users allowUserInfos; | |||
@SerializedName("allow_partys") | |||
private Partys allowPartys; | |||
private Parties allowParties; | |||
@SerializedName("allow_tags") | |||
private Tags allowTags; | |||
@@ -55,7 +65,7 @@ public class WxCpAgent implements Serializable { | |||
private Integer reportLocationFlag; | |||
@SerializedName("isreportenter") | |||
private Integer isreportenter; | |||
private Integer isReportEnter; | |||
@SerializedName("home_url") | |||
private String homeUrl; | |||
@@ -71,26 +81,25 @@ public class WxCpAgent implements Serializable { | |||
@Data | |||
public static class Users implements Serializable { | |||
@SerializedName("user") | |||
private List<User> user; | |||
private List<User> users; | |||
} | |||
@Data | |||
public class User implements Serializable { | |||
@SerializedName("userid") | |||
private String userid; | |||
private String userId; | |||
} | |||
@Data | |||
public class Partys { | |||
public class Parties { | |||
@SerializedName("partyid") | |||
private List<Integer> partyids = null; | |||
private List<Integer> partyIds = null; | |||
} | |||
@Data | |||
public class Tags { | |||
@SerializedName("tagid") | |||
private List<Integer> tagids = null; | |||
private List<Integer> tagIds = null; | |||
} | |||
} |
@@ -1,15 +1,17 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.google.common.base.Splitter; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
/** | |||
* 邀请成员的结果对象类. | |||
@@ -23,7 +25,7 @@ public class WxCpInviteResult implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
public static WxCpInviteResult fromJson(String json) { | |||
@@ -1,15 +1,17 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.google.common.base.Splitter; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
/** | |||
* 消息发送结果对象类. | |||
@@ -23,7 +25,7 @@ public class WxCpMessageSendResult implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
public static WxCpMessageSendResult fromJson(String json) { | |||
@@ -1,15 +1,17 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.google.common.base.Splitter; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
/** | |||
* 为标签添加或移除用户结果对象类. | |||
@@ -23,7 +25,7 @@ public class WxCpTagAddOrRemoveUsersResult implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
public static WxCpTagAddOrRemoveUsersResult fromJson(String json) { | |||
@@ -1,13 +1,15 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Data; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.NoArgsConstructor; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
/** | |||
* 微信用户信息. | |||
* | |||
@@ -24,6 +26,7 @@ public class WxCpUser implements Serializable { | |||
private Gender gender; | |||
private String email; | |||
private String avatar; | |||
private String avatarMediaId; | |||
private Integer status; | |||
private Integer enable; | |||
private Integer isLeader; | |||
@@ -33,6 +36,14 @@ public class WxCpUser implements Serializable { | |||
private String telephone; | |||
private String qrCode; | |||
private Boolean toInvite; | |||
/** | |||
* 成员对外信息. | |||
*/ | |||
private List<ExternalAttribute> externalAttrs = new ArrayList<>(); | |||
public void addExternalAttr(ExternalAttribute externalAttr) { | |||
this.externalAttrs.add(externalAttr); | |||
} | |||
public void addExtAttr(String name, String value) { | |||
this.extAttrs.add(new Attr(name, value)); | |||
@@ -53,4 +64,39 @@ public class WxCpUser implements Serializable { | |||
private String value; | |||
} | |||
@Data | |||
@Builder | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
public static class ExternalAttribute { | |||
/** | |||
* 属性类型: 0-本文 1-网页 2-小程序. | |||
*/ | |||
private int type; | |||
/** | |||
* 属性名称: 需要先确保在管理端有创建改属性,否则会忽略. | |||
*/ | |||
private String name; | |||
/** | |||
* 文本属性内容,长度限制12个UTF8字符. | |||
*/ | |||
private String value; | |||
/** | |||
* 网页的url,必须包含http或者https头. | |||
*/ | |||
private String url; | |||
/** | |||
* 小程序的展示标题,长度限制12个UTF8字符. | |||
* 或者 网页的展示标题,长度限制12个UTF8字符 | |||
*/ | |||
private String title; | |||
/** | |||
* 小程序appid,必须是有在本企业安装授权的小程序,否则会被忽略. | |||
*/ | |||
private String appid; | |||
/** | |||
* 小程序的页面路径. | |||
*/ | |||
private String pagePath; | |||
} | |||
} |
@@ -13,12 +13,42 @@ import lombok.Data; | |||
*/ | |||
@Data | |||
public class WxCpUserDetail { | |||
/** | |||
* 成员UserID | |||
*/ | |||
@SerializedName("userid") | |||
private String userId; | |||
/** | |||
* 成员姓名 | |||
*/ | |||
private String name; | |||
/** | |||
* 成员手机号,仅在用户同意snsapi_privateinfo授权时返回 | |||
*/ | |||
private String mobile; | |||
/** | |||
* 性别。0表示未定义,1表示男性,2表示女性 | |||
*/ | |||
private String gender; | |||
/** | |||
* 成员邮箱,仅在用户同意snsapi_privateinfo授权时返回 | |||
*/ | |||
private String email; | |||
@SerializedName("qrCode") | |||
/** | |||
* 头像url。注:如果要获取小图将url最后的”/0”改成”/100”即可。仅在用户同意snsapi_privateinfo授权时返回 | |||
*/ | |||
private String avatar; | |||
/** | |||
* 员工个人二维码(扫描可添加为外部联系人),仅在用户同意snsapi_privateinfo授权时返回 | |||
*/ | |||
@SerializedName("qr_code") | |||
private String qrCode; | |||
} |
@@ -0,0 +1,126 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.util.List; | |||
import com.google.gson.annotations.SerializedName; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
/** | |||
* <pre> | |||
* 外部联系人详情 | |||
* Created by Binary Wang on 2018/9/16. | |||
* 参考文档:https://work.weixin.qq.com/api/doc#13878 | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Getter | |||
@Setter | |||
public class WxCpUserExternalContactInfo { | |||
@SerializedName("external_contact") | |||
private ExternalContact externalContact; | |||
@SerializedName("follow_user") | |||
private List<FollowedUser> followedUsers; | |||
@Getter | |||
@Setter | |||
public static class ExternalContact { | |||
@SerializedName("external_userid") | |||
private String externalUserId; | |||
@SerializedName("position") | |||
private String position; | |||
@SerializedName("name") | |||
private String name; | |||
@SerializedName("avatar") | |||
private String avatar; | |||
@SerializedName("corp_name") | |||
private String corpName; | |||
@SerializedName("corp_full_name") | |||
private String corpFullName; | |||
@SerializedName("type") | |||
private Integer type; | |||
@SerializedName("gender") | |||
private Integer gender; | |||
@SerializedName("unionid") | |||
private String unionId; | |||
@SerializedName("external_profile") | |||
private ExternalProfile externalProfile; | |||
} | |||
@Setter | |||
@Getter | |||
public static class ExternalProfile { | |||
@SerializedName("external_attr") | |||
private List<ExternalAttribute> externalAttrs; | |||
} | |||
@Data | |||
@Builder | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
public static class ExternalAttribute { | |||
@Setter | |||
@Getter | |||
public static class Text { | |||
private String value; | |||
} | |||
@Setter | |||
@Getter | |||
public static class Web { | |||
private String title; | |||
private String url; | |||
} | |||
@Setter | |||
@Getter | |||
public static class MiniProgram { | |||
@SerializedName("pagepath") | |||
private String pagePath; | |||
private String appid; | |||
private String title; | |||
} | |||
private int type; | |||
private String name; | |||
private Text text; | |||
private Web web; | |||
@SerializedName("miniprogram") | |||
private MiniProgram miniProgram; | |||
} | |||
@Setter | |||
@Getter | |||
public static class FollowedUser { | |||
@SerializedName("userid") | |||
private String userId; | |||
private String remark; | |||
private String description; | |||
@SerializedName("createtime") | |||
private Long createTime; | |||
} | |||
public static WxCpUserExternalContactInfo fromJson(String json) { | |||
return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalContactInfo.class); | |||
} | |||
} |
@@ -1,35 +1,38 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
import lombok.Data; | |||
import lombok.extern.slf4j.Slf4j; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* <pre> | |||
* 微信推送过来的消息,也是同步回复给用户的消息,xml格式 | |||
* 相关字段的解释看微信开发者文档: | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收普通消息 | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收事件推送 | |||
* http://mp.weixin.qq.com/wiki/index.php?title=接收语音识别结果 | |||
* https://work.weixin.qq.com/api/doc#12973 | |||
* https://work.weixin.qq.com/api/doc#12974 | |||
* </pre> | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@XStreamAlias("xml") | |||
@Data | |||
@Slf4j | |||
@XStreamAlias("xml") | |||
public class WxCpXmlMessage implements Serializable { | |||
private static final long serialVersionUID = -1042994982179476410L; | |||
@@ -51,6 +54,24 @@ public class WxCpXmlMessage implements Serializable { | |||
@XStreamAlias("CreateTime") | |||
private Long createTime; | |||
/** | |||
* <pre> | |||
* 当接受用户消息时,可能会获得以下值: | |||
* {@link WxConsts.XmlMsgType#TEXT} | |||
* {@link WxConsts.XmlMsgType#IMAGE} | |||
* {@link WxConsts.XmlMsgType#VOICE} | |||
* {@link WxConsts.XmlMsgType#VIDEO} | |||
* {@link WxConsts.XmlMsgType#LOCATION} | |||
* {@link WxConsts.XmlMsgType#LINK} | |||
* {@link WxConsts.XmlMsgType#EVENT} | |||
* 当发送消息的时候使用: | |||
* {@link WxConsts.XmlMsgType#TEXT} | |||
* {@link WxConsts.XmlMsgType#IMAGE} | |||
* {@link WxConsts.XmlMsgType#VOICE} | |||
* {@link WxConsts.XmlMsgType#VIDEO} | |||
* {@link WxConsts.XmlMsgType#NEWS} | |||
* </pre> | |||
*/ | |||
@XStreamAlias("MsgType") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String msgType; | |||
@@ -128,15 +149,172 @@ public class WxCpXmlMessage implements Serializable { | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String recognition; | |||
/** | |||
* 通讯录变更事件. | |||
* 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType | |||
*/ | |||
@XStreamAlias("ChangeType") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String changeType; | |||
/** | |||
* 变更信息的成员UserID. | |||
*/ | |||
@XStreamAlias("UserID") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String userId; | |||
/** | |||
* 新的UserID,变更时推送(userid由系统生成时可更改一次). | |||
*/ | |||
@XStreamAlias("NewUserID") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String newUserId; | |||
/** | |||
* 成员名称. | |||
* 或者部门名称 | |||
*/ | |||
@XStreamAlias("Name") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String name; | |||
/** | |||
* 成员部门列表. | |||
*/ | |||
@XStreamAlias("Department") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String department; | |||
/** | |||
* 手机号码. | |||
*/ | |||
@XStreamAlias("Mobile") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String mobile; | |||
/** | |||
* 职位信息。长度为0~64个字节. | |||
*/ | |||
@XStreamAlias("Position") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String position; | |||
/** | |||
* 性别,1表示男性,2表示女性. | |||
*/ | |||
@XStreamAlias("Gender") | |||
private Integer gender; | |||
/** | |||
* 邮箱. | |||
*/ | |||
@XStreamAlias("Email") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String email; | |||
/** | |||
* 头像url。注:如果要获取小图将url最后的”/0”改成”/100”即可. | |||
*/ | |||
@XStreamAlias("Avatar") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String avatar; | |||
/** | |||
* 英文名. | |||
*/ | |||
@XStreamAlias("EnglishName") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String englishName; | |||
/** | |||
* 上级字段,标识是否为上级。0表示普通成员,1表示上级. | |||
*/ | |||
@XStreamAlias("IsLeader") | |||
private Integer isLeader; | |||
/** | |||
* 座机. | |||
*/ | |||
@XStreamAlias("Telephone") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String telephone; | |||
/** | |||
* 扩展属性. | |||
*/ | |||
@XStreamAlias("ExtAttr") | |||
private ExtAttr extAttrs = new ExtAttr(); | |||
/** | |||
* 部门Id. | |||
*/ | |||
@XStreamAlias("Id") | |||
private Integer id; | |||
/** | |||
* 父部门id. | |||
*/ | |||
@XStreamAlias("ParentId") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String parentId; | |||
/** | |||
* 部门排序. | |||
*/ | |||
@XStreamAlias("Order") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String order; | |||
/** | |||
* 标签Id. | |||
*/ | |||
@XStreamAlias("TagId") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String tagId; | |||
/** | |||
* 标签中新增的成员userid列表,用逗号分隔. | |||
*/ | |||
@XStreamAlias("AddUserItems") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String addUserItems; | |||
/** | |||
* 标签中删除的成员userid列表,用逗号分隔. | |||
*/ | |||
@XStreamAlias("DelUserItems") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String delUserItems; | |||
/** | |||
* 标签中新增的部门id列表,用逗号分隔. | |||
*/ | |||
@XStreamAlias("AddPartyItems") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String addPartyItems; | |||
/** | |||
* 标签中删除的部门id列表,用逗号分隔. | |||
*/ | |||
@XStreamAlias("DelPartyItems") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String delPartyItems; | |||
/////////////////////////////////////// | |||
// 群发消息返回的结果 | |||
/////////////////////////////////////// | |||
/** | |||
* 群发的结果. | |||
* 多个时间共用字段. | |||
* 1. 群发的结果. | |||
* 2. 通讯录变更事件 | |||
* 激活状态:1=已激活 2=已禁用 4=未激活 已激活代表已激活企业微信或已关注微工作台(原企业号). | |||
*/ | |||
@XStreamAlias("Status") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String status; | |||
/** | |||
* group_id下粉丝数;或者openid_list中的粉丝数. | |||
*/ | |||
@@ -181,19 +359,16 @@ public class WxCpXmlMessage implements Serializable { | |||
/** | |||
* 从加密字符串转换. | |||
*/ | |||
public static WxCpXmlMessage fromEncryptedXml( | |||
String encryptedXml, | |||
WxCpConfigStorage wxCpConfigStorage, | |||
String timestamp, String nonce, String msgSignature) { | |||
public static WxCpXmlMessage fromEncryptedXml(String encryptedXml, WxCpConfigStorage wxCpConfigStorage, | |||
String timestamp, String nonce, String msgSignature) { | |||
WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); | |||
String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); | |||
log.debug("解密后的原始xml消息内容:{}", plainText); | |||
return fromXml(plainText); | |||
} | |||
public static WxCpXmlMessage fromEncryptedXml( | |||
InputStream is, | |||
WxCpConfigStorage wxCpConfigStorage, | |||
String timestamp, String nonce, String msgSignature) { | |||
public static WxCpXmlMessage fromEncryptedXml(InputStream is, WxCpConfigStorage wxCpConfigStorage, | |||
String timestamp, String nonce, String msgSignature) { | |||
try { | |||
return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); | |||
} catch (IOException e) { | |||
@@ -201,39 +376,9 @@ public class WxCpXmlMessage implements Serializable { | |||
} | |||
} | |||
/** | |||
* <pre> | |||
* 当接受用户消息时,可能会获得以下值: | |||
* {@link WxConsts.XmlMsgType#TEXT} | |||
* {@link WxConsts.XmlMsgType#IMAGE} | |||
* {@link WxConsts.XmlMsgType#VOICE} | |||
* {@link WxConsts.XmlMsgType#VIDEO} | |||
* {@link WxConsts.XmlMsgType#LOCATION} | |||
* {@link WxConsts.XmlMsgType#LINK} | |||
* {@link WxConsts.XmlMsgType#EVENT} | |||
* </pre> | |||
*/ | |||
public String getMsgType() { | |||
return this.msgType; | |||
} | |||
/** | |||
* <pre> | |||
* 当发送消息的时候使用: | |||
* {@link WxConsts.XmlMsgType#TEXT} | |||
* {@link WxConsts.XmlMsgType#IMAGE} | |||
* {@link WxConsts.XmlMsgType#VOICE} | |||
* {@link WxConsts.XmlMsgType#VIDEO} | |||
* {@link WxConsts.XmlMsgType#NEWS} | |||
* </pre> | |||
*/ | |||
public void setMsgType(String msgType) { | |||
this.msgType = msgType; | |||
} | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
@Data | |||
@@ -255,6 +400,23 @@ public class WxCpXmlMessage implements Serializable { | |||
private String scanResult; | |||
} | |||
@Data | |||
public static class ExtAttr { | |||
@XStreamAlias("Item") | |||
protected final List<Item> items = new ArrayList<>(); | |||
@Data | |||
public static class Item { | |||
@XStreamAlias("Name") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String name; | |||
@XStreamAlias("Value") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String value; | |||
} | |||
} | |||
@Data | |||
@XStreamAlias("SendPicsInfo") | |||
public static class SendPicsInfo { | |||
@@ -1,16 +1,26 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.io.Serializable; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; | |||
import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
import me.chanjar.weixin.cp.bean.outxmlbuilder.*; | |||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
import java.io.Serializable; | |||
/** | |||
* 被动回复消息. | |||
* https://work.weixin.qq.com/api/doc#12975 | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
@XStreamAlias("xml") | |||
@Data | |||
public abstract class WxCpXmlOutMessage implements Serializable { | |||
@@ -1,11 +1,13 @@ | |||
package me.chanjar.weixin.cp.config; | |||
import java.io.File; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import java.io.File; | |||
/** | |||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
* | |||
@@ -201,7 +203,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
@Override | |||
@@ -43,17 +43,24 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
private volatile File tmpDirFile; | |||
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
public WxCpJedisConfigStorage(JedisPool jedisPool) { | |||
this.jedisPool = jedisPool; | |||
} | |||
public WxCpJedisConfigStorage(String host, int port) { | |||
this.jedisPool = new JedisPool(host, port); | |||
jedisPool = new JedisPool(host, port); | |||
} | |||
public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port) { | |||
this.jedisPool = new JedisPool(poolConfig, host, port); | |||
jedisPool = new JedisPool(poolConfig, host, port); | |||
} | |||
public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, String password) { | |||
jedisPool = new JedisPool(poolConfig, host, port, timeout, password); | |||
} | |||
public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, final String password) { | |||
this.jedisPool = new JedisPool(poolConfig, host, port, timeout, password); | |||
public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, String password, int database) { | |||
jedisPool = new JedisPool(poolConfig, host, port, timeout, password, database); | |||
} | |||
/** | |||
@@ -10,6 +10,9 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpTag; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class WxCpGsonBuilder { | |||
public static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
@@ -8,21 +8,30 @@ | |||
*/ | |||
package me.chanjar.weixin.cp.util.json; | |||
import com.google.gson.*; | |||
import java.lang.reflect.Type; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonDeserializationContext; | |||
import com.google.gson.JsonDeserializer; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParseException; | |||
import com.google.gson.JsonPrimitive; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import me.chanjar.weixin.cp.bean.Gender; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
import java.lang.reflect.Type; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> { | |||
private static final String EXTERNAL_PROFILE = "external_profile"; | |||
private static final String EXTERNAL_ATTR = "external_attr"; | |||
@Override | |||
public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | |||
throws JsonParseException { | |||
public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
JsonObject o = json.getAsJsonObject(); | |||
WxCpUser user = new WxCpUser(); | |||
@@ -43,6 +52,7 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
user.setGender(Gender.fromCode(GsonHelper.getString(o, "gender"))); | |||
user.setEmail(GsonHelper.getString(o, "email")); | |||
user.setAvatar(GsonHelper.getString(o, "avatar")); | |||
user.setAvatarMediaId(GsonHelper.getString(o, "avatar_mediaid")); | |||
user.setStatus(GsonHelper.getInteger(o, "status")); | |||
user.setEnable(GsonHelper.getInteger(o, "enable")); | |||
user.setIsLeader(GsonHelper.getInteger(o, "isleader")); | |||
@@ -62,6 +72,53 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
user.getExtAttrs().add(attr); | |||
} | |||
} | |||
if (GsonHelper.isNotNull(o.get(EXTERNAL_PROFILE))) { | |||
JsonArray attrJsonElements = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(EXTERNAL_ATTR).getAsJsonArray(); | |||
for (JsonElement element : attrJsonElements) { | |||
final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type"); | |||
final String name = GsonHelper.getString(element.getAsJsonObject(), "name"); | |||
switch (type) { | |||
case 0: { | |||
user.getExternalAttrs() | |||
.add(WxCpUser.ExternalAttribute.builder() | |||
.type(type) | |||
.name(name) | |||
.value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value")) | |||
.build() | |||
); | |||
break; | |||
} | |||
case 1: { | |||
final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject(); | |||
user.getExternalAttrs() | |||
.add(WxCpUser.ExternalAttribute.builder() | |||
.type(type) | |||
.name(name) | |||
.url(GsonHelper.getString(web, "url")) | |||
.title(GsonHelper.getString(web, "title")) | |||
.build() | |||
); | |||
break; | |||
} | |||
case 2: { | |||
final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject(); | |||
user.getExternalAttrs() | |||
.add(WxCpUser.ExternalAttribute.builder() | |||
.type(type) | |||
.name(name) | |||
.appid(GsonHelper.getString(miniprogram, "appid")) | |||
.pagePath(GsonHelper.getString(miniprogram, "pagepath")) | |||
.title(GsonHelper.getString(miniprogram, "title")) | |||
.build() | |||
); | |||
break; | |||
} | |||
default://ignored | |||
} | |||
} | |||
} | |||
return user; | |||
} | |||
@@ -96,6 +153,9 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
if (user.getAvatar() != null) { | |||
o.addProperty("avatar", user.getAvatar()); | |||
} | |||
if (user.getAvatarMediaId() != null) { | |||
o.addProperty("avatar_mediaid", user.getAvatarMediaId()); | |||
} | |||
if (user.getStatus() != null) { | |||
o.addProperty("status", user.getStatus()); | |||
} | |||
@@ -133,6 +193,45 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
attrsJson.add("attrs", attrsJsonArray); | |||
o.add("extattr", attrsJson); | |||
} | |||
if (user.getExternalAttrs().size() > 0) { | |||
JsonArray attrsJsonArray = new JsonArray(); | |||
for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) { | |||
JsonObject attrJson = new JsonObject(); | |||
attrJson.addProperty("type",attr.getType()); | |||
attrJson.addProperty("name", attr.getName()); | |||
switch (attr.getType()) { | |||
case 0: { | |||
JsonObject text = new JsonObject(); | |||
text.addProperty("value", attr.getValue()); | |||
attrJson.add("text", text); | |||
break; | |||
} | |||
case 1: { | |||
JsonObject web = new JsonObject(); | |||
web.addProperty("url", attr.getUrl()); | |||
web.addProperty("title", attr.getTitle()); | |||
attrJson.add("web", web); | |||
break; | |||
} | |||
case 2: { | |||
JsonObject miniprogram = new JsonObject(); | |||
miniprogram.addProperty("appid", attr.getAppid()); | |||
miniprogram.addProperty("pagepath", attr.getPagePath()); | |||
miniprogram.addProperty("title", attr.getTitle()); | |||
attrJson.add("miniprogram", miniprogram); | |||
break; | |||
} | |||
default://忽略 | |||
} | |||
attrsJsonArray.add(attrJson); | |||
} | |||
JsonObject attrsJson = new JsonObject(); | |||
attrsJson.add(EXTERNAL_ATTR, attrsJsonArray); | |||
o.add(EXTERNAL_PROFILE, attrsJson); | |||
} | |||
return o; | |||
} | |||
@@ -1,23 +1,21 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
import me.chanjar.weixin.common.bean.menu.WxMenuButton; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.api.ApiTestModule; | |||
import me.chanjar.weixin.cp.api.WxCpAgentService; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.bean.WxCpAgent; | |||
import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage; | |||
import org.mockito.Mock; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import java.util.List; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.testng.Assert.assertEquals; | |||
import static org.testng.Assert.assertNotNull; | |||
import static org.mockito.Mockito.*; | |||
/** | |||
@@ -28,24 +26,64 @@ import static org.mockito.Mockito.*; | |||
* | |||
* @author <a href="https://github.com/huansinho">huansinho</a> | |||
*/ | |||
@Guice(modules = ApiTestModule.class) | |||
public class WxCpAgentServiceImplTest { | |||
protected WxCpService wxService = mock(WxCpService.class); | |||
@Inject | |||
private WxCpService wxCpService; | |||
@Test | |||
public void testGet() throws Exception { | |||
String returnJson = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\",\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\",\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}, {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]},\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]},\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0,\"isreportenter\": 0,\"home_url\": \"\"}"; | |||
when(wxService.get("https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=9", null)).thenReturn(returnJson); | |||
when(wxService.getAgentService()).thenReturn(new WxCpAgentServiceImpl(wxService)); | |||
final Integer agentId = this.wxCpService.getWxCpConfigStorage().getAgentId(); | |||
WxCpAgent wxCpAgent = this.wxCpService.getAgentService().get(agentId); | |||
assertThat(wxCpAgent.getAgentId()).isEqualTo(agentId); | |||
assertThat(wxCpAgent.getAllowUserInfos().getUsers().toArray()).isNotEmpty(); | |||
assertThat(wxCpAgent.getAllowParties().getPartyIds().toArray()).isNotEmpty(); | |||
assertThat(wxCpAgent.getAllowTags().getTagIds().toArray()).isNotEmpty(); | |||
} | |||
@Test | |||
public void testSet() throws WxErrorException { | |||
final Integer agentId = this.wxCpService.getWxCpConfigStorage().getAgentId(); | |||
this.wxCpService.getAgentService().set(WxCpAgent.builder() | |||
.description("abcddd") | |||
.logoMediaId("aaaaaaaaaaaaaa") | |||
.agentId(agentId) | |||
.build()); | |||
} | |||
@Test | |||
public void testList() throws WxErrorException { | |||
List<WxCpAgent> list = this.wxCpService.getAgentService().list(); | |||
assertThat(list).isNotEmpty(); | |||
assertThat(list.get(0).getAgentId()).isNotNull(); | |||
assertThat(list.get(0).getName()).isNotEmpty(); | |||
assertThat(list.get(0).getSquareLogoUrl()).isNotEmpty(); | |||
} | |||
public static class MockTest { | |||
private WxCpService wxService = mock(WxCpService.class); | |||
@Test | |||
public void testGet() throws Exception { | |||
String returnJson = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\",\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\",\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}, {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]},\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]},\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0,\"isreportenter\": 0,\"home_url\": \"\"}"; | |||
when(wxService.get("https://qyapi.weixin.qq.com/cgi-bin/agent/get?agentid=9", null)).thenReturn(returnJson); | |||
when(wxService.getAgentService()).thenReturn(new WxCpAgentServiceImpl(wxService)); | |||
WxCpAgentService wxAgentService = this.wxService.getAgentService(); | |||
WxCpAgent wxCpAgent = wxAgentService.get(9); | |||
WxCpAgentService wxAgentService = this.wxService.getAgentService(); | |||
WxCpAgent wxCpAgent = wxAgentService.get(9); | |||
assertEquals(9, wxCpAgent.getAgentId().intValue()); | |||
Assert.assertEquals(9, wxCpAgent.getAgentid().intValue()); | |||
assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray()); | |||
Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowPartys().getPartyids().toArray()); | |||
assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagIds().toArray()); | |||
Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagids().toArray()); | |||
} | |||
} | |||
@@ -1,5 +1,14 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.net.URL; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.testng.annotations.*; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
@@ -7,22 +16,14 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.api.ApiTestModule; | |||
import me.chanjar.weixin.cp.api.TestConstants; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import org.testng.annotations.*; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.testng.Assert.*; | |||
/** | |||
* <pre> | |||
* | |||
* Created by Binary Wang on 2017-6-25. | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
* </pre> | |||
*/ | |||
@Guice(modules = ApiTestModule.class) | |||
public class WxCpMediaServiceImplTest { | |||
@@ -35,8 +36,8 @@ public class WxCpMediaServiceImplTest { | |||
public Object[][] mediaData() { | |||
return new Object[][]{ | |||
new Object[]{WxConsts.MediaFileType.IMAGE, TestConstants.FILE_JPG, "mm.jpeg"}, | |||
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"}, | |||
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_AMR, "mm.amr"},//{"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"} | |||
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"},//{"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"} | |||
new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_AMR, "mm.amr"}, | |||
new Object[]{WxConsts.MediaFileType.VIDEO, TestConstants.FILE_MP4, "mm.mp4"}, | |||
new Object[]{WxConsts.MediaFileType.FILE, TestConstants.FILE_JPG, "mm.jpeg"} | |||
}; | |||
@@ -75,4 +76,10 @@ public class WxCpMediaServiceImplTest { | |||
System.out.println(file); | |||
} | |||
@Test | |||
public void testUploadImg() throws WxErrorException { | |||
URL url = ClassLoader.getSystemResource("mm.jpeg"); | |||
String res = this.wxService.getMediaService().uploadImg(new File(url.getFile())); | |||
assertThat(res).isNotEmpty(); | |||
} | |||
} |
@@ -1,22 +1,23 @@ | |||
package me.chanjar.weixin.cp.api.impl; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import org.testng.annotations.*; | |||
import com.google.common.collect.Lists; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.api.ApiTestModule; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.bean.Gender; | |||
import me.chanjar.weixin.cp.bean.WxCpInviteResult; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; | |||
import java.util.List; | |||
import java.util.Map; | |||
import static org.testng.Assert.assertNotEquals; | |||
import static org.testng.Assert.assertNotNull; | |||
import static org.testng.Assert.*; | |||
/** | |||
* <pre> | |||
@@ -109,4 +110,11 @@ public class WxCpUserServiceImplTest { | |||
System.out.println(result); | |||
assertNotNull(result); | |||
} | |||
@Test | |||
public void testGetExternalContact() throws WxErrorException { | |||
WxCpUserExternalContactInfo result = this.wxCpService.getUserService().getExternalContact(userId); | |||
System.out.println(result); | |||
assertNotNull(result); | |||
} | |||
} |
@@ -14,11 +14,11 @@ public class WxCpAgentTest { | |||
WxCpAgent wxCpAgent = WxCpAgent.fromJson(json); | |||
Assert.assertEquals(9, wxCpAgent.getAgentid().intValue()); | |||
Assert.assertEquals(9, wxCpAgent.getAgentId().intValue()); | |||
Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowPartys().getPartyids().toArray()); | |||
Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray()); | |||
Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagids().toArray()); | |||
Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagIds().toArray()); | |||
} | |||
@@ -0,0 +1,129 @@ | |||
package me.chanjar.weixin.cp.bean; | |||
import java.util.List; | |||
import org.testng.annotations.*; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
* <pre> | |||
* | |||
* Created by Binary Wang on 2018/9/16. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxCpUserExternalContactInfoTest { | |||
@Test | |||
public void testFromJson() { | |||
final String json = "{\n" + | |||
" \"errcode\": 0,\n" + | |||
" \"errmsg\": \"ok\",\n" + | |||
" \"external_contact\": {\n" + | |||
" \"external_userid\": \"woAJ2GCAAAXtWyujaWJHDDGi0mACH71w\",\n" + | |||
" \"name\": \"李四\",\n" + | |||
" \"position\": \"Mangaer\",\n" + | |||
" \"avatar\": \"http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0\",\n" + | |||
" \"corp_name\": \"腾讯\",\n" + | |||
" \"corp_full_name\": \"腾讯科技有限公司\",\n" + | |||
" \"type\": 2,\n" + | |||
" \"gender\": 1,\n" + | |||
" \"unionid\": \"ozynqsulJFCZ2z1aYeS8h-nuasdfR1\",\n" + | |||
" \"external_profile\": {\n" + | |||
" \"external_attr\": [\n" + | |||
" {\n" + | |||
" \"type\": 0,\n" + | |||
" \"name\": \"文本名称\",\n" + | |||
" \"text\": {\n" + | |||
" \"value\": \"文本\"\n" + | |||
" }\n" + | |||
" },\n" + | |||
" {\n" + | |||
" \"type\": 1,\n" + | |||
" \"name\": \"网页名称\",\n" + | |||
" \"web\": {\n" + | |||
" \"url\": \"http://www.test.com\",\n" + | |||
" \"title\": \"标题\"\n" + | |||
" }\n" + | |||
" },\n" + | |||
" {\n" + | |||
" \"type\": 2,\n" + | |||
" \"name\": \"测试app\",\n" + | |||
" \"miniprogram\": {\n" + | |||
" \"appid\": \"wx8bd80126147df384\",\n" + | |||
" \"pagepath\": \"/index\",\n" + | |||
" \"title\": \"my miniprogram\"\n" + | |||
" }\n" + | |||
" }\n" + | |||
" ]\n" + | |||
" }\n" + | |||
" },\n" + | |||
" \"follow_user\": [\n" + | |||
" {\n" + | |||
" \"userid\": \"rocky\",\n" + | |||
" \"remark\": \"李部长\",\n" + | |||
" \"description\": \"对接采购事物\",\n" + | |||
" \"createtime\": 1525779812\n" + | |||
" },\n" + | |||
" {\n" + | |||
" \"userid\": \"tommy\",\n" + | |||
" \"remark\": \"李总\",\n" + | |||
" \"description\": \"采购问题咨询\",\n" + | |||
" \"createtime\": 1525881637\n" + | |||
" }\n" + | |||
" ]\n" + | |||
"}"; | |||
final WxCpUserExternalContactInfo contactInfo = WxCpUserExternalContactInfo.fromJson(json); | |||
assertThat(contactInfo).isNotNull(); | |||
assertThat(contactInfo.getExternalContact()).isNotNull(); | |||
assertThat(contactInfo.getExternalContact().getExternalUserId()).isEqualTo("woAJ2GCAAAXtWyujaWJHDDGi0mACH71w"); | |||
assertThat(contactInfo.getExternalContact().getPosition()).isEqualTo("Mangaer"); | |||
assertThat(contactInfo.getExternalContact().getAvatar()).isEqualTo("http://p.qlogo.cn/bizmail/IcsdgagqefergqerhewSdage/0"); | |||
assertThat(contactInfo.getExternalContact().getCorpName()).isEqualTo("腾讯"); | |||
assertThat(contactInfo.getExternalContact().getCorpFullName()).isEqualTo("腾讯科技有限公司"); | |||
assertThat(contactInfo.getExternalContact().getType()).isEqualTo(2); | |||
assertThat(contactInfo.getExternalContact().getGender()).isEqualTo(1); | |||
assertThat(contactInfo.getExternalContact().getUnionId()).isEqualTo("ozynqsulJFCZ2z1aYeS8h-nuasdfR1"); | |||
assertThat(contactInfo.getExternalContact().getName()).isEqualTo("李四"); | |||
assertThat(contactInfo.getExternalContact().getExternalProfile()).isNotNull(); | |||
final List<WxCpUserExternalContactInfo.ExternalAttribute> externalAttrs = contactInfo.getExternalContact().getExternalProfile().getExternalAttrs(); | |||
assertThat(externalAttrs).isNotEmpty(); | |||
final WxCpUserExternalContactInfo.ExternalAttribute externalAttr1 = externalAttrs.get(0); | |||
assertThat(externalAttr1.getType()).isEqualTo(0); | |||
assertThat(externalAttr1.getName()).isEqualTo("文本名称"); | |||
assertThat(externalAttr1.getText().getValue()).isEqualTo("文本"); | |||
final WxCpUserExternalContactInfo.ExternalAttribute externalAttr2 = externalAttrs.get(1); | |||
assertThat(externalAttr2.getType()).isEqualTo(1); | |||
assertThat(externalAttr2.getName()).isEqualTo("网页名称"); | |||
assertThat(externalAttr2.getWeb().getUrl()).isEqualTo("http://www.test.com"); | |||
assertThat(externalAttr2.getWeb().getTitle()).isEqualTo("标题"); | |||
final WxCpUserExternalContactInfo.ExternalAttribute externalAttr3 = externalAttrs.get(2); | |||
assertThat(externalAttr3.getType()).isEqualTo(2); | |||
assertThat(externalAttr3.getName()).isEqualTo("测试app"); | |||
assertThat(externalAttr3.getMiniProgram().getAppid()).isEqualTo("wx8bd80126147df384"); | |||
assertThat(externalAttr3.getMiniProgram().getPagePath()).isEqualTo("/index"); | |||
assertThat(externalAttr3.getMiniProgram().getTitle()).isEqualTo("my miniprogram"); | |||
List<WxCpUserExternalContactInfo.FollowedUser> followedUsers = contactInfo.getFollowedUsers(); | |||
assertThat(followedUsers).isNotEmpty(); | |||
assertThat(followedUsers.get(0).getUserId()).isEqualTo("rocky"); | |||
assertThat(followedUsers.get(0).getRemark()).isEqualTo("李部长"); | |||
assertThat(followedUsers.get(0).getDescription()).isEqualTo("对接采购事物"); | |||
assertThat(followedUsers.get(0).getCreateTime()).isEqualTo(1525779812); | |||
assertThat(followedUsers.get(1).getUserId()).isEqualTo("tommy"); | |||
assertThat(followedUsers.get(1).getRemark()).isEqualTo("李总"); | |||
assertThat(followedUsers.get(1).getDescription()).isEqualTo("采购问题咨询"); | |||
assertThat(followedUsers.get(1).getCreateTime()).isEqualTo(1525881637); | |||
} | |||
} |
@@ -1,6 +1,16 @@ | |||
package me.chanjar.weixin.cp.demo; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Map; | |||
import org.eclipse.jetty.server.Server; | |||
import org.eclipse.jetty.servlet.ServletHandler; | |||
import org.eclipse.jetty.servlet.ServletHolder; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.cp.WxCpConsts; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
@@ -9,13 +19,6 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||
import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
import me.chanjar.weixin.cp.message.WxCpMessageHandler; | |||
import me.chanjar.weixin.cp.message.WxCpMessageRouter; | |||
import org.eclipse.jetty.server.Server; | |||
import org.eclipse.jetty.servlet.ServletHandler; | |||
import org.eclipse.jetty.servlet.ServletHolder; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Map; | |||
public class WxCpDemoServer { | |||
@@ -78,9 +81,26 @@ public class WxCpDemoServer { | |||
}; | |||
wxCpMessageRouter = new WxCpMessageRouter(wxCpService); | |||
wxCpMessageRouter.rule().async(false).content("哈哈") // 拦截内容为“哈哈”的消息 | |||
.handler(handler).end().rule().async(false).content("oauth") | |||
.handler(oauth2handler).end(); | |||
wxCpMessageRouter.rule() | |||
.async(false) | |||
.content("哈哈") // 拦截内容为“哈哈”的消息 | |||
.handler(handler) | |||
.end() | |||
.rule() | |||
.async(false) | |||
.content("oauth") | |||
.handler(oauth2handler) | |||
.end() | |||
.rule() | |||
.event(WxCpConsts.EventType.CHANGE_CONTACT) | |||
.handler(new WxCpMessageHandler() { | |||
@Override | |||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException { | |||
System.out.println("通讯录发生变更"); | |||
return null; | |||
} | |||
}) | |||
.end(); | |||
} | |||
} | |||
@@ -0,0 +1,124 @@ | |||
package me.chanjar.weixin.cp.util.json; | |||
import org.testng.annotations.*; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
* <pre> | |||
* | |||
* Created by Binary Wang on 2018/9/16. | |||
* </pre> | |||
* | |||
* @author BinaryWang | |||
*/ | |||
public class WxCpUserGsonAdapterTest { | |||
@Test | |||
public void testDeserialize() { | |||
final String userJson = "{\n" + | |||
" \"errcode\": 0,\n" + | |||
" \"errmsg\": \"ok\",\n" + | |||
" \"userid\": \"zhangsan\",\n" + | |||
" \"name\": \"李四\",\n" + | |||
" \"department\": [1, 2],\n" + | |||
" \"order\": [1, 2],\n" + | |||
" \"position\": \"后台工程师\",\n" + | |||
" \"mobile\": \"15913215421\",\n" + | |||
" \"gender\": \"1\",\n" + | |||
" \"email\": \"zhangsan@gzdev.com\",\n" + | |||
" \"isleader\": 1,\n" + | |||
" \"avatar\": \"http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0\",\n" + | |||
" \"telephone\": \"020-123456\",\n" + | |||
" \"enable\": 1,\n" + | |||
" \"alias\": \"jackzhang\",\n" + | |||
" \"extattr\": {\n" + | |||
" \"attrs\": [{\n" + | |||
" \"name\": \"爱好\",\n" + | |||
" \"value\": \"旅游\"\n" + | |||
" }, {\n" + | |||
" \"name\": \"卡号\",\n" + | |||
" \"value\": \"1234567234\"\n" + | |||
" }]\n" + | |||
" },\n" + | |||
" \"status\": 1,\n" + | |||
" \"qr_code\": \"https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx\",\n" + | |||
" \"external_profile\": {\n" + | |||
" \"external_attr\": [{\n" + | |||
" \"type\": 0,\n" + | |||
" \"name\": \"文本名称\",\n" + | |||
" \"text\": {\n" + | |||
" \"value\": \"文本\"\n" + | |||
" }\n" + | |||
" },\n" + | |||
" {\n" + | |||
" \"type\": 1,\n" + | |||
" \"name\": \"网页名称\",\n" + | |||
" \"web\": {\n" + | |||
" \"url\": \"http://www.test.com\",\n" + | |||
" \"title\": \"标题\"\n" + | |||
" }\n" + | |||
" },\n" + | |||
" {\n" + | |||
" \"type\": 2,\n" + | |||
" \"name\": \"测试app\",\n" + | |||
" \"miniprogram\": {\n" + | |||
" \"appid\": \"wx8bd80126147df384\",\n" + | |||
" \"pagepath\": \"/index\",\n" + | |||
" \"title\": \"my miniprogram\"\n" + | |||
" }\n" + | |||
" }\n" + | |||
" ]\n" + | |||
" }\n" + | |||
"}"; | |||
final WxCpUser user = WxCpUser.fromJson(userJson); | |||
assertThat(user).isNotNull(); | |||
assertThat(user.getExternalAttrs()).isNotEmpty(); | |||
final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0); | |||
assertThat(externalAttr1.getType()).isEqualTo(0); | |||
assertThat(externalAttr1.getName()).isEqualTo("文本名称"); | |||
assertThat(externalAttr1.getValue()).isEqualTo("文本"); | |||
final WxCpUser.ExternalAttribute externalAttr2 = user.getExternalAttrs().get(1); | |||
assertThat(externalAttr2.getType()).isEqualTo(1); | |||
assertThat(externalAttr2.getName()).isEqualTo("网页名称"); | |||
assertThat(externalAttr2.getUrl()).isEqualTo("http://www.test.com"); | |||
assertThat(externalAttr2.getTitle()).isEqualTo("标题"); | |||
final WxCpUser.ExternalAttribute externalAttr3 = user.getExternalAttrs().get(2); | |||
assertThat(externalAttr3.getType()).isEqualTo(2); | |||
assertThat(externalAttr3.getName()).isEqualTo("测试app"); | |||
assertThat(externalAttr3.getAppid()).isEqualTo("wx8bd80126147df384"); | |||
assertThat(externalAttr3.getPagePath()).isEqualTo("/index"); | |||
assertThat(externalAttr3.getTitle()).isEqualTo("my miniprogram"); | |||
} | |||
@Test | |||
public void testSerialize() { | |||
WxCpUser user = new WxCpUser(); | |||
user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | |||
.type(0) | |||
.name("文本名称") | |||
.value("文本") | |||
.build()); | |||
user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | |||
.type(1) | |||
.name("网页名称") | |||
.url("http://www.test.com") | |||
.title("标题") | |||
.build()); | |||
user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | |||
.type(2) | |||
.name("测试app") | |||
.appid("wx8bd80126147df384") | |||
.pagePath("/index") | |||
.title("my miniprogram") | |||
.build()); | |||
assertThat(user.toJson()).isEqualTo("{\"external_profile\":{\"external_attr\":[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}},{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}},{\"type\":2,\"name\":\"测试app\",\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}"); | |||
} | |||
} |
@@ -7,7 +7,7 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>3.1.0</version> | |||
<version>3.2.0</version> | |||
</parent> | |||
<artifactId>weixin-java-miniapp</artifactId> | |||
<name>Weixin Java Tools - MiniApp</name> | |||
@@ -1,5 +1,7 @@ | |||
package cn.binarywang.wx.miniapp.api; | |||
import java.util.List; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCategory; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest; | |||
@@ -7,8 +9,6 @@ import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import java.util.List; | |||
/** | |||
* 小程序代码管理相关 API(大部分只能是第三方平台调用) | |||
* 文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | |||
@@ -44,11 +44,15 @@ public interface WxMaCodeService { | |||
/** | |||
* 获取体验小程序的体验二维码 | |||
* 文档地址: | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | |||
* | |||
* @param path 指定体验版二维码跳转到某个具体页面(如果不需要的话,则不需要填path参数,可在路径后以“?参数”方式传入参数) | |||
* 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1 | |||
* @return 二维码 bytes | |||
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | |||
*/ | |||
byte[] getQrCode() throws WxErrorException; | |||
byte[] getQrCode(String path) throws WxErrorException; | |||
/** | |||
* 获取授权小程序帐号的可选类目 | |||
@@ -0,0 +1,48 @@ | |||
package cn.binarywang.wx.miniapp.api; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* <pre> | |||
* jsapi相关接口 | |||
* Created by BinaryWang on 2018/8/5. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface WxMaJsapiService { | |||
/** | |||
* 获得jsapi_ticket的url | |||
*/ | |||
String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||
/** | |||
* 获得jsapi_ticket,不强制刷新jsapi_ticket | |||
* | |||
* @see #getJsapiTicket(boolean) | |||
*/ | |||
String getJsapiTicket() throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获得jsapi_ticket | |||
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||
* | |||
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||
* </pre> | |||
* | |||
* @param forceRefresh 强制刷新 | |||
*/ | |||
String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 创建调用jsapi时所需要的签名 | |||
* | |||
* 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||
* </pre> | |||
*/ | |||
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
} |
@@ -2,6 +2,7 @@ package cn.binarywang.wx.miniapp.api; | |||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
@@ -14,6 +15,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
public interface WxMaMsgService { | |||
String KEFU_MESSAGE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | |||
String TEMPLATE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send"; | |||
String UNIFORM_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send"; | |||
/** | |||
* <pre> | |||
@@ -32,4 +34,14 @@ public interface WxMaMsgService { | |||
* </pre> | |||
*/ | |||
void sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 下发小程序和公众号统一的服务消息 | |||
* 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html">下发小程序和公众号统一的服务消息</a> | |||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
* </pre> | |||
*/ | |||
void sendUniformMsg(WxMaUniformMessage uniformMessage) throws WxErrorException; | |||
} |
@@ -1,5 +1,7 @@ | |||
package cn.binarywang.wx.miniapp.api; | |||
import java.io.File; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
@@ -17,12 +19,25 @@ public interface WxMaService { | |||
String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; | |||
String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | |||
String IMG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/img_sec_check"; | |||
/** | |||
* <pre> | |||
* 校验一张图片是否含有违法违规内容. | |||
* 应用场景举例:1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等;2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天 | |||
* 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/imgSecCheck.html | |||
* </pre> | |||
*/ | |||
boolean imgSecCheck(File file) throws WxErrorException; | |||
/** | |||
* 获取登录后的session信息 | |||
* 获取登录后的session信息. | |||
* | |||
* @param jsCode 登录时获取的 code | |||
*/ | |||
WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 验证消息的确来自微信服务器. | |||
@@ -131,6 +146,7 @@ public interface WxMaService { | |||
/** | |||
* 返回模板配置相关接口方法的实现类对象, 以方便调用其各个接口. | |||
* | |||
* @return WxMaTemplateService | |||
*/ | |||
WxMaTemplateService getTemplateService(); | |||
@@ -149,6 +165,13 @@ public interface WxMaService { | |||
*/ | |||
WxMaCodeService getCodeService(); | |||
/** | |||
* 返回jsapi操作相关的 API服务类对象 | |||
* | |||
* @return WxMaJsapiService | |||
*/ | |||
WxMaJsapiService getJsapiService(); | |||
/** | |||
* 小程序修改服务器地址、成员管理 API | |||
* | |||
@@ -5,6 +5,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import java.util.Map; | |||
/** | |||
* 用户信息相关操作接口. | |||
* | |||
@@ -28,6 +30,16 @@ public interface WxMaUserService { | |||
*/ | |||
WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr); | |||
/** | |||
* 上报用户数据后台接口. | |||
* <p>小游戏可以通过本接口上报key-value数据到用户的CloudStorage。</p> | |||
* 文档参考https://developers.weixin.qq.com/minigame/dev/document/open-api/data/setUserStorage.html | |||
* @param kvMap 要上报的数据 | |||
* @param sessionKey 通过wx.login 获得的登录态 | |||
* @param openid | |||
*/ | |||
void setUserStorage(Map<String, String> kvMap, String sessionKey, String openid) throws WxErrorException; | |||
/** | |||
* 解密用户手机号信息. | |||
* | |||
@@ -1,5 +1,15 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.net.URLEncoder; | |||
import java.nio.charset.StandardCharsets; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.List; | |||
import org.apache.commons.lang3.StringUtils; | |||
import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCategory; | |||
@@ -17,12 +27,6 @@ import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.List; | |||
/** | |||
* @author <a href="https://github.com/charmingoh">Charming</a> | |||
* @since 2018-04-26 20:00 | |||
@@ -41,13 +45,19 @@ public class WxMaCodeServiceImpl implements WxMaCodeService { | |||
} | |||
@Override | |||
public byte[] getQrCode() throws WxErrorException { | |||
public byte[] getQrCode(String path) throws WxErrorException { | |||
String appId = this.wxMaService.getWxMaConfig().getAppid(); | |||
Path qrCodeFilePath = null; | |||
try { | |||
RequestExecutor<File, String> executor = BaseMediaDownloadRequestExecutor | |||
.create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("weixin-java-tools-ma-" + appId).toFile()); | |||
qrCodeFilePath = this.wxMaService.execute(executor, GET_QRCODE_URL, null).toPath(); | |||
final StringBuilder url = new StringBuilder(GET_QRCODE_URL); | |||
if (StringUtils.isNotBlank(path)) { | |||
url.append("?path=").append(URLEncoder.encode(path, StandardCharsets.UTF_8.name())); | |||
} | |||
qrCodeFilePath = this.wxMaService.execute(executor, url.toString(), null).toPath(); | |||
return Files.readAllBytes(qrCodeFilePath); | |||
} catch (IOException e) { | |||
throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e); | |||
@@ -0,0 +1,75 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import cn.binarywang.wx.miniapp.api.WxMaJsapiService; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.RandomUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import java.util.concurrent.locks.Lock; | |||
/** | |||
* <pre> | |||
* Created by BinaryWang on 2018/8/5. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxMaJsapiServiceImpl implements WxMaJsapiService { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private WxMaService wxMaService; | |||
public WxMaJsapiServiceImpl(WxMaService wxMaService) { | |||
this.wxMaService = wxMaService; | |||
} | |||
@Override | |||
public String getJsapiTicket() throws WxErrorException { | |||
return getJsapiTicket(false); | |||
} | |||
@Override | |||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||
Lock lock = this.wxMaService.getWxMaConfig().getJsapiTicketLock(); | |||
try { | |||
lock.lock(); | |||
if (forceRefresh) { | |||
this.wxMaService.getWxMaConfig().expireJsapiTicket(); | |||
} | |||
if (this.wxMaService.getWxMaConfig().isJsapiTicketExpired()) { | |||
String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL, null); | |||
JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.wxMaService.getWxMaConfig().updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
return this.wxMaService.getWxMaConfig().getJsapiTicket(); | |||
} | |||
@Override | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
long timestamp = System.currentTimeMillis() / 1000; | |||
String randomStr = RandomUtils.getRandomStr(); | |||
String jsapiTicket = getJsapiTicket(false); | |||
String signature = SHA1.genWithAmple("jsapi_ticket=" + jsapiTicket, | |||
"noncestr=" + randomStr, "timestamp=" + timestamp, "url=" + url); | |||
return WxJsapiSignature | |||
.builder() | |||
.appId(this.wxMaService.getWxMaConfig().getAppid()) | |||
.timestamp(timestamp) | |||
.nonceStr(randomStr) | |||
.url(url) | |||
.signature(signature) | |||
.build(); | |||
} | |||
} |
@@ -4,6 +4,7 @@ import cn.binarywang.wx.miniapp.api.WxMaMsgService; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import cn.binarywang.wx.miniapp.constant.WxMaConstants; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
@@ -36,4 +37,13 @@ public class WxMaMsgServiceImpl implements WxMaMsgService { | |||
} | |||
} | |||
@Override | |||
public void sendUniformMsg(WxMaUniformMessage uniformMessage) throws WxErrorException { | |||
String responseContent = this.wxMaService.post(UNIFORM_MSG_SEND_URL, uniformMessage.toJson()); | |||
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); | |||
if (jsonObject.get(WxMaConstants.ERRCODE).getAsInt() != 0) { | |||
throw new WxErrorException(WxError.fromJson(responseContent)); | |||
} | |||
} | |||
} |
@@ -1,7 +1,23 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.concurrent.locks.Lock; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import cn.binarywang.wx.miniapp.api.WxMaAnalysisService; | |||
import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||
import cn.binarywang.wx.miniapp.api.WxMaJsapiService; | |||
import cn.binarywang.wx.miniapp.api.WxMaMediaService; | |||
import cn.binarywang.wx.miniapp.api.WxMaMsgService; | |||
import cn.binarywang.wx.miniapp.api.WxMaQrcodeService; | |||
@@ -12,31 +28,21 @@ import cn.binarywang.wx.miniapp.api.WxMaUserService; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import com.google.common.base.Joiner; | |||
import com.google.gson.Gson; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.DataUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.http.HttpType; | |||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.concurrent.locks.Lock; | |||
import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | |||
@@ -58,10 +64,13 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
private WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this); | |||
private WxMaCodeService codeService = new WxMaCodeServiceImpl(this); | |||
private WxMaSettingService settingService = new WxMaSettingServiceImpl(this); | |||
private WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); | |||
private int retrySleepMillis = 1000; | |||
private int maxRetryTimes = 5; | |||
protected static final Gson GSON = new Gson(); | |||
@Override | |||
public CloseableHttpClient getRequestHttpClient() { | |||
return httpClient; | |||
@@ -140,6 +149,13 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
return this.getWxMaConfig().getAccessToken(); | |||
} | |||
@Override | |||
public boolean imgSecCheck(File file) throws WxErrorException { | |||
//这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult | |||
WxMediaUploadResult result = this.execute(MediaUploadRequestExecutor.create(this.getRequestHttp()), IMG_SEC_CHECK_URL, file); | |||
return result != null; | |||
} | |||
@Override | |||
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException { | |||
final WxMaConfig config = getWxMaConfig(); | |||
@@ -310,6 +326,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
return this.codeService; | |||
} | |||
@Override | |||
public WxMaJsapiService getJsapiService() { | |||
return this.jsapiService; | |||
} | |||
@Override | |||
public WxMaSettingService getSettingService() { | |||
return this.settingService; | |||
@@ -1,14 +1,25 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.api.WxMaUserService; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonObject; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.SignUtils; | |||
import org.apache.commons.codec.binary.Hex; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import javax.crypto.Mac; | |||
import javax.crypto.spec.SecretKeySpec; | |||
import java.security.InvalidKeyException; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.util.Map; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -30,6 +41,30 @@ public class WxMaUserServiceImpl implements WxMaUserService { | |||
return WxMaUserInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
} | |||
@Override | |||
public void setUserStorage(Map<String, String> kvMap, String sessionKey, String openid) throws WxErrorException { | |||
final WxMaConfig config = this.service.getWxMaConfig(); | |||
JsonObject param = new JsonObject(); | |||
JsonArray array = new JsonArray(); | |||
for (Map.Entry<String, String> e : kvMap.entrySet()) { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("key", e.getKey()); | |||
jsonObject.addProperty("value", e.getValue()); | |||
array.add(jsonObject); | |||
} | |||
param.add("kv_list", array); | |||
String params = param.toString(); | |||
String signature = SignUtils.createHmacSha256Sign(params, sessionKey); | |||
String url = String.format("https://api.weixin.qq.com/wxa/set_user_storage" + | |||
"?appid=%s&signature=%s&openid=%s&sig_method=%s", | |||
config.getAppid(), signature, openid, "hmac_sha256"); | |||
String result = this.service.post(url, params); | |||
WxError error = WxError.fromJson(result); | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
} | |||
@Override | |||
public WxMaPhoneNumberInfo getPhoneNoInfo(String sessionKey, String encryptedData, String ivStr) { | |||
return WxMaPhoneNumberInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
@@ -1,5 +1,14 @@ | |||
package cn.binarywang.wx.miniapp.bean; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.nio.charset.StandardCharsets; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
@@ -8,14 +17,7 @@ import com.google.gson.annotations.SerializedName; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
import lombok.Data; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.nio.charset.StandardCharsets; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -166,7 +168,7 @@ public class WxMaMessage implements Serializable { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
public String toJson() { | |||
@@ -0,0 +1,32 @@ | |||
package cn.binarywang.wx.miniapp.bean; | |||
import lombok.Data; | |||
import lombok.NoArgsConstructor; | |||
/** | |||
* <pre> | |||
* | |||
* Created by Binary Wang on 2018/9/23. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Data | |||
@NoArgsConstructor | |||
public class WxMaTemplateData { | |||
private String name; | |||
private String value; | |||
private String color; | |||
public WxMaTemplateData(String name, String value) { | |||
this.name = name; | |||
this.value = value; | |||
} | |||
public WxMaTemplateData(String name, String value, String color) { | |||
this.name = name; | |||
this.value = value; | |||
this.color = color; | |||
} | |||
} |
@@ -1,12 +1,16 @@ | |||
package cn.binarywang.wx.miniapp.bean; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
import lombok.*; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
/** | |||
* 模板消息. | |||
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分 | |||
@@ -69,7 +73,7 @@ public class WxMaTemplateMessage implements Serializable { | |||
* 描述: 模板内容,不填则下发空模板 | |||
* </pre> | |||
*/ | |||
private List<Data> data; | |||
private List<WxMaTemplateData> data; | |||
/** | |||
* 模板内容字体的颜色,不填默认黑色. | |||
@@ -91,7 +95,7 @@ public class WxMaTemplateMessage implements Serializable { | |||
*/ | |||
private String emphasisKeyword; | |||
public WxMaTemplateMessage addData(Data datum) { | |||
public WxMaTemplateMessage addData(WxMaTemplateData datum) { | |||
if (this.data == null) { | |||
this.data = new ArrayList<>(); | |||
} | |||
@@ -104,23 +108,4 @@ public class WxMaTemplateMessage implements Serializable { | |||
return WxMaGsonBuilder.create().toJson(this); | |||
} | |||
@lombok.Data | |||
public static class Data { | |||
private String name; | |||
private String value; | |||
private String color; | |||
public Data(String name, String value) { | |||
this.name = name; | |||
this.value = value; | |||
} | |||
public Data(String name, String value, String color) { | |||
this.name = name; | |||
this.value = value; | |||
this.color = color; | |||
} | |||
} | |||
} |
@@ -0,0 +1,108 @@ | |||
package cn.binarywang.wx.miniapp.bean; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
/** | |||
* 模板消息. | |||
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分 | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Getter | |||
@Setter | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
@Builder | |||
public class WxMaUniformMessage implements Serializable { | |||
private static final long serialVersionUID = 5063374783759519418L; | |||
/** | |||
* 是否发送公众号模版消息,否则发送小程序模版消息. | |||
*/ | |||
private boolean isMpTemplateMsg; | |||
/** | |||
* 用户openid. | |||
* 可以是小程序的openid,也可以是mp_template_msg.appid对应的公众号的openid | |||
*/ | |||
private String toUser; | |||
/** | |||
* 公众号appid,要求与小程序有绑定且同主体. | |||
*/ | |||
private String appid; | |||
/** | |||
* 公众号或小程序模板ID. | |||
*/ | |||
private String templateId; | |||
/** | |||
* 公众号模板消息所要跳转的url. | |||
*/ | |||
private String url; | |||
/** | |||
* 小程序页面路径. | |||
*/ | |||
private String page; | |||
/** | |||
* 小程序模板消息formid. | |||
*/ | |||
private String formId; | |||
/** | |||
* 公众号模板消息所要跳转的小程序,小程序的必须与公众号具有绑定关系. | |||
*/ | |||
private MiniProgram miniProgram; | |||
/** | |||
* 小程序模板数据. | |||
*/ | |||
private List<WxMaTemplateData> data; | |||
/** | |||
* 模板需要放大的关键词,不填则默认无放大. | |||
*/ | |||
private String emphasisKeyword; | |||
public WxMaUniformMessage addData(WxMaTemplateData datum) { | |||
if (this.data == null) { | |||
this.data = new ArrayList<>(); | |||
} | |||
this.data.add(datum); | |||
return this; | |||
} | |||
public String toJson() { | |||
return WxMaGsonBuilder.create().toJson(this); | |||
} | |||
@Data | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
public static class MiniProgram implements Serializable { | |||
private static final long serialVersionUID = -7945254706501974849L; | |||
private String appid; | |||
private String pagePath; | |||
/** | |||
* 是否使用path,否则使用pagepath. | |||
* 加入此字段是基于微信官方接口变化多端的考虑 | |||
*/ | |||
private boolean usePath = false; | |||
} | |||
} |
@@ -1,14 +1,15 @@ | |||
package cn.binarywang.wx.miniapp.bean.code; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import java.util.List; | |||
import java.util.Map; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
/** | |||
* 上传代码需要用到的第三方自定义的配置 | |||
* 详细文档,参考:https://developers.weixin.qq.com/miniprogram/dev/framework/config.html | |||
* | |||
* @author <a href="https://github.com/charmingoh">Charming</a> | |||
* @since 2018-04-26 19:44 | |||
@@ -18,34 +19,34 @@ import java.util.Map; | |||
public class WxMaCodeExtConfig implements Serializable { | |||
private static final long serialVersionUID = -7666911367458178753L; | |||
/** | |||
* 配置 ext.json 是否生效 | |||
* 配置 ext.json 是否生效. | |||
* 必填:是 | |||
*/ | |||
private boolean extEnable; | |||
/** | |||
* 配置 extAppid | |||
* 配置 extAppid. | |||
* 必填:是 | |||
*/ | |||
private String extAppid; | |||
/** | |||
* 开发自定义的数据字段 | |||
* 开发自定义的数据字段. | |||
* 必填:否 | |||
*/ | |||
private Object ext; | |||
/** | |||
* 单独设置每个页面的 json | |||
* 单独设置每个页面的 json. | |||
* 必填:否 | |||
* key: page 名称,如 pages/logs/logs | |||
* value: page 配置 | |||
*/ | |||
private Map<String, PageConfig> extPages; | |||
/** | |||
* 是否直接提交到待审核列表 | |||
* 是否直接提交到待审核列表. | |||
* 必填:否 | |||
*/ | |||
private Boolean directCommit; | |||
/** | |||
* 设置页面路径(同 app.json 相同的字段,填写会覆盖 app.json) | |||
* 设置页面路径(同 app.json 相同的字段,填写会覆盖 app.json). | |||
* 必填:否 | |||
*/ | |||
private List<String> pages; | |||
@@ -64,6 +65,11 @@ public class WxMaCodeExtConfig implements Serializable { | |||
* 必填:否 | |||
*/ | |||
private Boolean debug; | |||
/** | |||
* 底部 tab 栏的表现. | |||
* 必填:否 | |||
*/ | |||
private TabBar tabBar; | |||
/** | |||
* page.json 配置,页面配置 | |||
@@ -73,125 +79,125 @@ public class WxMaCodeExtConfig implements Serializable { | |||
@Builder | |||
public static class PageConfig { | |||
/** | |||
* 导航栏背景颜色,如"#000000" HexColor | |||
* 导航栏背景颜色,如"#000000" HexColor. | |||
* 默认:#000000 | |||
*/ | |||
private String navigationBarBackgroundColor; | |||
/** | |||
* 导航栏标题颜色,仅支持 black/white | |||
* 导航栏标题颜色,仅支持 black/white. | |||
* 默认:white | |||
*/ | |||
private String navigationBarTextStyle; | |||
/** | |||
* 导航栏标题文字内容 | |||
* 导航栏标题文字内容. | |||
*/ | |||
private String navigationBarTitleText; | |||
/** | |||
* 窗口的背景色 HexColor | |||
* 窗口的背景色 HexColor. | |||
* 默认:#ffffff | |||
*/ | |||
private String backgroundColor; | |||
/** | |||
* 下拉背景字体、loading 图的样式,仅支持 dark/light | |||
* 下拉背景字体、loading 图的样式,仅支持 dark/light. | |||
* 默认:dark | |||
*/ | |||
private String backgroundTextStyle; | |||
/** | |||
* 是否开启下拉刷新,详见页面相关事件处理函数 | |||
* 是否开启下拉刷新,详见页面相关事件处理函数. | |||
* 默认:false | |||
*/ | |||
private String enablePullDownRefresh; | |||
/** | |||
* 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项 | |||
* 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项. | |||
* 默认:false | |||
*/ | |||
private Boolean disableScroll; | |||
/** | |||
* 页面上拉触底事件触发时距页面底部距离,单位为px | |||
* 页面上拉触底事件触发时距页面底部距离,单位为px. | |||
* 默认:50 | |||
*/ | |||
private Integer onReachBottomDistance; | |||
} | |||
/** | |||
* tabBar 配置 | |||
* tabBar 配置. | |||
*/ | |||
@Data | |||
@Builder | |||
public static class TabBar { | |||
/** | |||
* HexColor, tab 上的文字默认颜色 | |||
* HexColor, tab 上的文字默认颜色. | |||
*/ | |||
private String color; | |||
/** | |||
* HexColor, tab 上的文字选中时的颜色 | |||
* HexColor, tab 上的文字选中时的颜色. | |||
*/ | |||
private String selectedColor; | |||
/** | |||
* HexColor, tab 的背景色 | |||
* HexColor, tab 的背景色. | |||
*/ | |||
private String backgroundColor; | |||
/** | |||
* tabbar 上边框的颜色,仅支持 black/white | |||
* tabbar 上边框的颜色,仅支持 black/white. | |||
*/ | |||
private String borderStyle; | |||
/** | |||
* tab 的列表,最少2个、最多5个 tab | |||
* tab 的列表,最少2个、最多5个 tab. | |||
*/ | |||
private List<Item> list; | |||
/** | |||
* 可选值 bottom、top | |||
* 可选值 bottom、top. | |||
*/ | |||
private String position; | |||
/** | |||
* list item | |||
* list item. | |||
*/ | |||
@Data | |||
@Builder | |||
public static class Item { | |||
/** | |||
* 是 页面路径,必须在 pages 中先定义 | |||
* 页面路径,必须在 pages 中先定义. | |||
*/ | |||
private String pagePath; | |||
/** | |||
* tab 上按钮文字 | |||
* tab 上按钮文字. | |||
*/ | |||
private String text; | |||
/** | |||
* 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效,不支持网络图片 | |||
* 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效,不支持网络图片. | |||
*/ | |||
private String iconPath; | |||
/** | |||
* 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效 | |||
* 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效. | |||
*/ | |||
private String selectedIconPath; | |||
} | |||
} | |||
/** | |||
* 各种网络请求的超时时间 | |||
* 各种网络请求的超时时间. | |||
*/ | |||
@Data | |||
@Builder | |||
public static class NetworkTimeout { | |||
/** | |||
* wx.request的超时时间,单位毫秒,默认为:60000 | |||
* wx.request的超时时间,单位毫秒,默认为:60000. | |||
* 必填:否 | |||
*/ | |||
private Integer request; | |||
/** | |||
* wx.connectSocket的超时时间,单位毫秒,默认为:60000 | |||
* wx.connectSocket的超时时间,单位毫秒,默认为:60000. | |||
* 必填:否 | |||
*/ | |||
private Integer connectSocket; | |||
/** | |||
* wx.uploadFile的超时时间,单位毫秒,默认为:60000 | |||
* wx.uploadFile的超时时间,单位毫秒,默认为:60000. | |||
* 必填:否 | |||
*/ | |||
private Integer uploadFile; | |||
/** | |||
* wx.downloadFile的超时时间,单位毫秒,默认为:60000 | |||
* wx.downloadFile的超时时间,单位毫秒,默认为:60000. | |||
* 必填:否 | |||
*/ | |||
private Integer downloadFile; | |||
@@ -38,6 +38,25 @@ public interface WxMaConfig { | |||
*/ | |||
void updateAccessToken(String accessToken, int expiresInSeconds); | |||
String getJsapiTicket(); | |||
Lock getJsapiTicketLock(); | |||
boolean isJsapiTicketExpired(); | |||
/** | |||
* 强制将jsapi ticket过期掉 | |||
*/ | |||
void expireJsapiTicket(); | |||
/** | |||
* 应该是线程安全的 | |||
* | |||
* @param jsapiTicket 新的jsapi ticket值 | |||
* @param expiresInSeconds 过期时间,以秒为单位 | |||
*/ | |||
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
String getAppid(); | |||
String getSecret(); | |||
@@ -1,13 +1,15 @@ | |||
package cn.binarywang.wx.miniapp.config; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import java.io.File; | |||
import java.util.concurrent.locks.Lock; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
/** | |||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
* | |||
@@ -27,7 +29,11 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
protected volatile String httpProxyUsername; | |||
protected volatile String httpProxyPassword; | |||
protected volatile String jsapiTicket; | |||
protected volatile long jsapiTicketExpiresTime; | |||
protected Lock accessTokenLock = new ReentrantLock(); | |||
protected Lock jsapiTicketLock = new ReentrantLock(); | |||
/** | |||
* 临时文件目录 | |||
@@ -70,6 +76,33 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
} | |||
@Override | |||
public String getJsapiTicket() { | |||
return this.jsapiTicket; | |||
} | |||
@Override | |||
public Lock getJsapiTicketLock() { | |||
return this.jsapiTicketLock; | |||
} | |||
@Override | |||
public boolean isJsapiTicketExpired() { | |||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||
} | |||
@Override | |||
public void expireJsapiTicket() { | |||
this.jsapiTicketExpiresTime = 0; | |||
} | |||
@Override | |||
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
this.jsapiTicket = jsapiTicket; | |||
// 预留200秒的时间 | |||
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
} | |||
@Override | |||
public void expireAccessToken() { | |||
this.expiresTime = 0; | |||
@@ -158,7 +191,7 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
@Override | |||
@@ -1,6 +1,7 @@ | |||
package cn.binarywang.wx.miniapp.util.json; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo; | |||
import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait; | |||
import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution; | |||
@@ -18,6 +19,7 @@ public class WxMaGsonBuilder { | |||
static { | |||
INSTANCE.disableHtmlEscaping(); | |||
INSTANCE.registerTypeAdapter(WxMaTemplateMessage.class, new WxMaTemplateMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMaUniformMessage.class, new WxMaUniformMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMaCodeCommitRequest.class, new WxMaCodeCommitRequestGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMaCodeVersionDistribution.class, new WxMaCodeVersionDistributionGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMaVisitDistribution.class, new WxMaVisitDistributionGsonAdapter()); | |||
@@ -1,13 +1,14 @@ | |||
package cn.binarywang.wx.miniapp.util.json; | |||
import java.lang.reflect.Type; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
import java.lang.reflect.Type; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@@ -26,10 +27,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla | |||
messageJson.addProperty("form_id", message.getFormId()); | |||
} | |||
if (message.getPage() != null) { | |||
messageJson.addProperty("page", message.getPage()); | |||
} | |||
if (message.getColor() != null) { | |||
messageJson.addProperty("color", message.getColor()); | |||
} | |||
@@ -45,7 +42,7 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla | |||
return messageJson; | |||
} | |||
for (WxMaTemplateMessage.Data datum : message.getData()) { | |||
for (WxMaTemplateData datum : message.getData()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", datum.getValue()); | |||
if (datum.getColor() != null) { | |||
@@ -0,0 +1,98 @@ | |||
package cn.binarywang.wx.miniapp.util.json; | |||
import java.lang.reflect.Type; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniformMessage> { | |||
@Override | |||
public JsonElement serialize(WxMaUniformMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||
JsonObject messageJson = new JsonObject(); | |||
messageJson.addProperty("touser", message.getToUser()); | |||
if (message.isMpTemplateMsg()) { | |||
JsonObject msg = new JsonObject(); | |||
if (message.getAppid() != null) { | |||
msg.addProperty("appid", message.getAppid()); | |||
} | |||
msg.addProperty("template_id", message.getTemplateId()); | |||
if (message.getUrl() != null) { | |||
msg.addProperty("url", message.getUrl()); | |||
} | |||
final WxMaUniformMessage.MiniProgram miniProgram = message.getMiniProgram(); | |||
if (miniProgram != null) { | |||
JsonObject miniProgramJson = new JsonObject(); | |||
miniProgramJson.addProperty("appid", miniProgram.getAppid()); | |||
if (miniProgram.isUsePath()) { | |||
miniProgramJson.addProperty("path", miniProgram.getPagePath()); | |||
} else { | |||
miniProgramJson.addProperty("pagepath", miniProgram.getPagePath()); | |||
} | |||
msg.add("miniprogram", miniProgramJson); | |||
} | |||
if (message.getData() != null) { | |||
JsonObject data = new JsonObject(); | |||
for (WxMaTemplateData templateData : message.getData()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", templateData.getValue()); | |||
if (templateData.getColor() != null) { | |||
dataJson.addProperty("color", templateData.getColor()); | |||
} | |||
data.add(templateData.getName(), dataJson); | |||
} | |||
msg.add("data", data); | |||
} | |||
messageJson.add("mp_template_msg", msg); | |||
return messageJson; | |||
} | |||
//小程序模版消息 | |||
JsonObject msg = new JsonObject(); | |||
msg.addProperty("template_id", message.getTemplateId()); | |||
if (message.getPage() != null) { | |||
msg.addProperty("page", message.getPage()); | |||
} | |||
if (message.getFormId() != null) { | |||
msg.addProperty("form_id", message.getFormId()); | |||
} | |||
JsonObject data = new JsonObject(); | |||
msg.add("data", data); | |||
if (message.getData() != null) { | |||
for (WxMaTemplateData templateData : message.getData()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", templateData.getValue()); | |||
if (templateData.getColor() != null) { | |||
dataJson.addProperty("color", templateData.getColor()); | |||
} | |||
data.add(templateData.getName(), dataJson); | |||
} | |||
} | |||
if (message.getEmphasisKeyword() != null) { | |||
msg.addProperty("emphasis_keyword", message.getEmphasisKeyword()); | |||
} | |||
messageJson.add("weapp_template_msg", msg); | |||
return messageJson; | |||
} | |||
} |
@@ -1,5 +1,12 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.bean.code.WxMaCategory; | |||
@@ -11,16 +18,8 @@ import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import com.google.inject.Inject; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import static org.testng.Assert.assertNotNull; | |||
import static org.testng.Assert.assertTrue; | |||
import static org.testng.Assert.*; | |||
/** | |||
* @author <a href="https://github.com/charmingoh">Charming</a> | |||
@@ -77,7 +76,7 @@ public class WxMaCodeServiceImplTest { | |||
@Test | |||
public void testGetQrCode() throws Exception { | |||
byte[] qrCode = wxService.getCodeService().getQrCode(); | |||
byte[] qrCode = wxService.getCodeService().getQrCode(null); | |||
assertTrue(qrCode.length > 0); | |||
} | |||
@@ -0,0 +1,45 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
* <pre> | |||
* Created by BinaryWang on 2018/8/5. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Test | |||
@Guice(modules = ApiTestModule.class) | |||
public class WxMaJsapiServiceImplTest { | |||
@Inject | |||
private WxMaService wxService; | |||
@Inject | |||
private WxMaConfig wxMaConfig; | |||
@Test | |||
public void testGetJsapiTicket() throws WxErrorException { | |||
assertThat(this.wxService.getJsapiService().getJsapiTicket()).isNotBlank(); | |||
} | |||
@Test | |||
public void testGetJsapiTicket1() throws WxErrorException { | |||
assertThat(this.wxService.getJsapiService().getJsapiTicket(true)).isNotBlank(); | |||
} | |||
@Test | |||
public void testCreateJsapiSignature() throws WxErrorException { | |||
final WxJsapiSignature jsapiSignature = this.wxService.getJsapiService().createJsapiSignature("http://www.qq.com"); | |||
System.out.println(jsapiSignature); | |||
assertThat(jsapiSignature).isNotNull(); | |||
} | |||
} |
@@ -1,17 +1,20 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import cn.binarywang.wx.miniapp.test.TestConfig; | |||
import com.google.common.collect.Lists; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.testng.annotations.*; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
/** | |||
* 测试消息相关接口 | |||
@@ -45,15 +48,33 @@ public class WxMaMsgServiceImplTest { | |||
.formId("FORMID") | |||
.page("index") | |||
.data(Lists.newArrayList( | |||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"), | |||
new WxMaTemplateMessage.Data("keyword2", dateFormat.format(new Date()), "#173177"), | |||
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"), | |||
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177"))) | |||
new WxMaTemplateData("keyword1", "339208499", "#173177"), | |||
new WxMaTemplateData("keyword2", dateFormat.format(new Date()), "#173177"), | |||
new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"), | |||
new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177"))) | |||
.templateId(config.getTemplateId()) | |||
.emphasisKeyword("keyword1.DATA") | |||
.build(); | |||
//templateMessage.addData( new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177")); | |||
//templateMessage.addData( new WxMaTemplateData("keyword1", "339208499", "#173177")); | |||
this.wxService.getMsgService().sendTemplateMsg(templateMessage); | |||
} | |||
@Test | |||
public void testSendUniformMsg() throws WxErrorException { | |||
TestConfig config = (TestConfig) this.wxService.getWxMaConfig(); | |||
WxMaUniformMessage message = WxMaUniformMessage.builder() | |||
.isMpTemplateMsg(false) | |||
.toUser(config.getOpenid()) | |||
.page("page/page/index") | |||
.templateId("TEMPLATE_ID") | |||
.formId("FORMID") | |||
.emphasisKeyword("keyword1.DATA") | |||
.build(); | |||
message.addData(new WxMaTemplateData("keyword1", "339208499")) | |||
.addData(new WxMaTemplateData("keyword2", "2015年01月05日 12:30")) | |||
.addData(new WxMaTemplateData("keyword3", "腾讯微信总部")) | |||
.addData(new WxMaTemplateData("keyword4", "广州市海珠区新港中路397号")); | |||
this.wxService.getMsgService().sendUniformMsg(message); | |||
} | |||
} |
@@ -1,16 +1,17 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.io.File; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import static org.testng.Assert.assertNotEquals; | |||
import static org.testng.Assert.assertTrue; | |||
import static org.testng.Assert.*; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -32,4 +33,9 @@ public class WxMaServiceImplTest { | |||
assertTrue(StringUtils.isNotBlank(after)); | |||
} | |||
@Test | |||
public void testImgSecCheck() throws WxErrorException { | |||
boolean result = this.wxService.imgSecCheck(new File(ClassLoader.getSystemResource("tmp.png").getFile())); | |||
assertTrue(result); | |||
} | |||
} |
@@ -1,5 +1,9 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import cn.binarywang.wx.miniapp.test.TestConfig; | |||
import com.google.common.collect.ImmutableMap; | |||
import jdk.nashorn.internal.ir.annotations.Immutable; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
@@ -8,6 +12,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import com.google.inject.Inject; | |||
import javax.management.ImmutableDescriptor; | |||
import static org.testng.Assert.*; | |||
/** | |||
@@ -54,4 +60,17 @@ public class WxMaUserServiceImplTest { | |||
assertNotNull(phoneNoInfo); | |||
System.out.println(phoneNoInfo.toString()); | |||
} | |||
@Test | |||
public void testGetSessionInfo() { | |||
} | |||
/** | |||
* TODO 测试数据有问题,需要替换为正确的数据 | |||
*/ | |||
@Test | |||
public void testSetUserStorage() throws WxErrorException { | |||
this.wxService.getUserService().setUserStorage(ImmutableMap.of("1","2"), | |||
"r7BXXKkLb8qrSNn05n0qiA",((TestConfig)this.wxService.getWxMaConfig()).getOpenid()); | |||
} | |||
} |
@@ -1,9 +1,10 @@ | |||
package cn.binarywang.wx.miniapp.bean; | |||
import org.testng.annotations.*; | |||
import com.google.common.collect.Lists; | |||
import org.testng.annotations.Test; | |||
import static org.testng.AssertJUnit.assertEquals; | |||
import static org.testng.AssertJUnit.*; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -17,10 +18,10 @@ public class WxMaTemplateMessageTest { | |||
.formId("FORMID") | |||
.page("index") | |||
.data(Lists.newArrayList( | |||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"), | |||
new WxMaTemplateMessage.Data("keyword2", "2015年01月05日12:30", "#173177"), | |||
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"), | |||
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177"))) | |||
new WxMaTemplateData("keyword1", "339208499", "#173177"), | |||
new WxMaTemplateData("keyword2", "2015年01月05日12:30", "#173177"), | |||
new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"), | |||
new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177"))) | |||
.templateId("TEMPLATE_ID") | |||
.emphasisKeyword("keyword1.DATA") | |||
.build(); | |||
@@ -1,9 +1,20 @@ | |||
package cn.binarywang.wx.miniapp.demo; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Map; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
import org.eclipse.jetty.server.Server; | |||
import org.eclipse.jetty.servlet.ServletHandler; | |||
import org.eclipse.jetty.servlet.ServletHolder; | |||
import cn.binarywang.wx.miniapp.api.WxMaService; | |||
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; | |||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaMessage; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.constant.WxMaConstants; | |||
@@ -14,15 +25,6 @@ import com.google.common.collect.Lists; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import org.eclipse.jetty.server.Server; | |||
import org.eclipse.jetty.servlet.ServletHandler; | |||
import org.eclipse.jetty.servlet.ServletHolder; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Map; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -96,7 +98,7 @@ public class WxMaDemoServer { | |||
throws WxErrorException { | |||
service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.builder() | |||
.templateId(templateId).data(Lists.newArrayList( | |||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"))) | |||
new WxMaTemplateData("keyword1", "339208499", "#173177"))) | |||
.toUser(wxMessage.getFromUser()) | |||
.formId("自己替换可用的formid") | |||
.build()); | |||
@@ -54,6 +54,7 @@ public class TestConfig extends WxMaInMemoryConfig { | |||
this.templateId = templateId; | |||
} | |||
@Override | |||
public void setAccessTokenLock(Lock lock) { | |||
super.accessTokenLock = lock; | |||
} | |||
@@ -0,0 +1,112 @@ | |||
package cn.binarywang.wx.miniapp.util.json; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import com.google.gson.JsonParser; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
/** | |||
* <pre> | |||
* | |||
* Created by Binary Wang on 2018/9/23. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class WxMaUniformMessageGsonAdapterTest { | |||
@Test | |||
public void testSerialize_mp() { | |||
WxMaUniformMessage message = WxMaUniformMessage.builder() | |||
.isMpTemplateMsg(true) | |||
.toUser("OPENID") | |||
.appid("APPID") | |||
.templateId("TEMPLATE_ID") | |||
.url("http://weixin.qq.com/download") | |||
.miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false)) | |||
.build(); | |||
message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177")) | |||
.addData(new WxMaTemplateData("keyword1", "巧克力", "#173177")) | |||
.addData(new WxMaTemplateData("keyword2", "39.8元", "#173177")) | |||
.addData(new WxMaTemplateData("keyword3", "2014年9月22日", "#173177")) | |||
.addData(new WxMaTemplateData("remark", "欢迎再次购买!", "#173177")); | |||
assertThat(message.toJson()).isEqualTo(new JsonParser().parse("{\n" + | |||
" \"touser\":\"OPENID\",\n" + | |||
" \"mp_template_msg\":{\n" + | |||
" \"appid\":\"APPID\",\n" + | |||
" \"template_id\":\"TEMPLATE_ID\",\n" + | |||
" \"url\":\"http://weixin.qq.com/download\",\n" + | |||
" \"miniprogram\":{\n" + | |||
" \"appid\":\"xiaochengxuappid12345\",\n" + | |||
" \"pagepath\":\"index?foo=bar\"\n" + | |||
" },\n" + | |||
" \"data\":{\n" + | |||
" \"first\":{\n" + | |||
" \"value\":\"恭喜你购买成功!\",\n" + | |||
" \"color\":\"#173177\"\n" + | |||
" },\n" + | |||
" \"keyword1\":{\n" + | |||
" \"value\":\"巧克力\",\n" + | |||
" \"color\":\"#173177\"\n" + | |||
" },\n" + | |||
" \"keyword2\":{\n" + | |||
" \"value\":\"39.8元\",\n" + | |||
" \"color\":\"#173177\"\n" + | |||
" },\n" + | |||
" \"keyword3\":{\n" + | |||
" \"value\":\"2014年9月22日\",\n" + | |||
" \"color\":\"#173177\"\n" + | |||
" },\n" + | |||
" \"remark\":{\n" + | |||
" \"value\":\"欢迎再次购买!\",\n" + | |||
" \"color\":\"#173177\"\n" + | |||
" }\n" + | |||
" }\n" + | |||
" }\n" + | |||
"}").getAsJsonObject().toString()); | |||
} | |||
@Test | |||
public void testSerialize_ma() { | |||
WxMaUniformMessage message = WxMaUniformMessage.builder() | |||
.isMpTemplateMsg(false) | |||
.toUser("OPENID") | |||
.page("page/page/index") | |||
.templateId("TEMPLATE_ID") | |||
.formId("FORMID") | |||
.emphasisKeyword("keyword1.DATA") | |||
.build(); | |||
message.addData(new WxMaTemplateData("keyword1", "339208499")) | |||
.addData(new WxMaTemplateData("keyword2", "2015年01月05日 12:30")) | |||
.addData(new WxMaTemplateData("keyword3", "腾讯微信总部")) | |||
.addData(new WxMaTemplateData("keyword4", "广州市海珠区新港中路397号")); | |||
assertThat(message.toJson()).isEqualTo(new JsonParser().parse("{\n" + | |||
" \"touser\":\"OPENID\",\n" + | |||
" \"weapp_template_msg\":{\n" + | |||
" \"template_id\":\"TEMPLATE_ID\",\n" + | |||
" \"page\":\"page/page/index\",\n" + | |||
" \"form_id\":\"FORMID\",\n" + | |||
" \"data\":{\n" + | |||
" \"keyword1\":{\n" + | |||
" \"value\":\"339208499\"\n" + | |||
" },\n" + | |||
" \"keyword2\":{\n" + | |||
" \"value\":\"2015年01月05日 12:30\"\n" + | |||
" },\n" + | |||
" \"keyword3\":{\n" + | |||
" \"value\":\"腾讯微信总部\"\n" + | |||
" },\n" + | |||
" \"keyword4\":{\n" + | |||
" \"value\":\"广州市海珠区新港中路397号\"\n" + | |||
" }\n" + | |||
" },\n" + | |||
" \"emphasis_keyword\":\"keyword1.DATA\"\n" + | |||
" }\n" + | |||
"}").getAsJsonObject().toString()); | |||
} | |||
} |
@@ -7,7 +7,7 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>3.1.0</version> | |||
<version>3.2.0</version> | |||
</parent> | |||
<artifactId>weixin-java-mp</artifactId> | |||
<name>Weixin Java Tools - MP</name> | |||
@@ -2,12 +2,16 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxCardApiSignature; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpCardResult; | |||
/** | |||
* 卡券相关接口 | |||
* | |||
* @author YuJian(mgcnrx11@hotmail.com) on 01/11/2016 | |||
* @author YuJian(mgcnrx11 @ hotmail.com) on 01/11/2016 | |||
* @author yuanqixun 2018-08-29 | |||
*/ | |||
public interface WxMpCardService { | |||
String CARD_GET = "https://api.weixin.qq.com/card/get"; | |||
@@ -16,6 +20,13 @@ public interface WxMpCardService { | |||
String CARD_CODE_GET = "https://api.weixin.qq.com/card/code/get"; | |||
String CARD_CODE_CONSUME = "https://api.weixin.qq.com/card/code/consume"; | |||
String CARD_CODE_MARK = "https://api.weixin.qq.com/card/code/mark"; | |||
String CARD_TEST_WHITELIST = "https://api.weixin.qq.com/card/testwhitelist/set"; | |||
String CARD_QRCODE_CREAET = "https://api.weixin.qq.com/card/qrcode/create"; | |||
String CARD_LANDING_PAGE_CREAET = "https://api.weixin.qq.com/card/landingpage/create"; | |||
/** | |||
* 将用户的卡券设置为失效状态 | |||
*/ | |||
String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable"; | |||
/** | |||
* 得到WxMpService | |||
@@ -122,4 +133,53 @@ public interface WxMpCardService { | |||
* <br> 可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。 | |||
*/ | |||
String getCardDetail(String cardId) throws WxErrorException; | |||
/** | |||
* 添加测试白名单 | |||
* | |||
* @param openid 用户的openid | |||
* @return | |||
*/ | |||
String addTestWhiteList(String openid) throws WxErrorException; | |||
/** | |||
* 创建卡券二维码 | |||
* | |||
* @param cardId 卡券编号 | |||
* @param outerStr 二维码标识 | |||
* @return WxMpCardQrcodeCreateResult | |||
*/ | |||
WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException; | |||
/** | |||
* 创建卡券二维码 | |||
* | |||
* @param cardId 卡券编号 | |||
* @param outerStr 二维码标识 | |||
* @param expiresIn 失效时间,单位秒,不填默认365天 | |||
* @return WxMpCardQrcodeCreateResult | |||
*/ | |||
WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException; | |||
/** | |||
* 创建卡券货架 | |||
* | |||
* @param createRequest 货架创建参数 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest createRequest) throws WxErrorException; | |||
/** | |||
* 将用户的卡券设置为失效状态 | |||
* 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9 | |||
* | |||
* @param cardId 卡券编号 | |||
* @param code 用户会员卡号 | |||
* @param reason 设置为失效的原因 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException; | |||
} |
@@ -1,13 +1,15 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import java.io.File; | |||
import java.util.concurrent.locks.Lock; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
import org.apache.commons.lang3.builder.ToStringBuilder; | |||
import org.apache.commons.lang3.builder.ToStringStyle; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
/** | |||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
* | |||
@@ -248,7 +250,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
} | |||
@Override | |||
@@ -1,16 +1,21 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import java.io.File; | |||
import java.util.Date; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; | |||
import me.chanjar.weixin.mp.bean.kefu.result.*; | |||
import java.io.File; | |||
import java.util.Date; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; | |||
/** | |||
* <pre> | |||
* 客服接口 , | |||
* 客服接口. | |||
* 注意:命名采用kefu拼音的原因是:其英文CustomerService如果再加上Service后缀显得有点啰嗦,如果不加又显得表意不完整。 | |||
* </pre> | |||
* | |||
@@ -30,7 +35,8 @@ public interface WxMpKefuService { | |||
String KFSESSION_GET_SESSION = "https://api.weixin.qq.com/customservice/kfsession/getsession?openid=%s"; | |||
String KFSESSION_GET_SESSION_LIST = "https://api.weixin.qq.com/customservice/kfsession/getsessionlist?kf_account=%s"; | |||
String KFSESSION_GET_WAIT_CASE = "https://api.weixin.qq.com/customservice/kfsession/getwaitcase"; | |||
String MSGRECORD_GET_MSG_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist"; | |||
String MSG_RECORD_LIST = "https://api.weixin.qq.com/customservice/msgrecord/getmsglist"; | |||
String CUSTOM_TYPING = "https://api.weixin.qq.com/cgi-bin/message/custom/typing"; | |||
/** | |||
* <pre> | |||
@@ -38,6 +44,8 @@ public interface WxMpKefuService { | |||
* 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547&token=&lang=zh_CN">发送客服消息</a> | |||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean sendKefuMessage(WxMpKefuMessage message) throws WxErrorException; | |||
@@ -49,6 +57,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfList kfList() throws WxErrorException; | |||
@@ -58,6 +68,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfOnlineList kfOnlineList() throws WxErrorException; | |||
@@ -67,6 +79,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/add?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfAccountAdd(WxMpKfAccountRequest request) throws WxErrorException; | |||
@@ -85,6 +99,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfAccountInviteWorker(WxMpKfAccountRequest request) throws WxErrorException; | |||
@@ -94,8 +110,10 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException; | |||
boolean kfAccountUploadHeadImg(String kfAccount, File imgFile) throws WxErrorException; | |||
/** | |||
* <pre> | |||
@@ -103,6 +121,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044813&token=&lang=zh_CN">客服管理</a> | |||
* 接口url格式:https://api.weixin.qq.com/customservice/kfaccount/del?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfAccountDel(String kfAccount) throws WxErrorException; | |||
@@ -115,6 +135,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a> | |||
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/create?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfSessionCreate(String openid, String kfAccount) throws WxErrorException; | |||
@@ -125,6 +147,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a> | |||
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/close?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean kfSessionClose(String openid, String kfAccount) throws WxErrorException; | |||
@@ -135,6 +159,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制接口</a> | |||
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=ACCESS_TOKEN&openid=OPENID | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfSessionGetResult kfSessionGet(String openid) throws WxErrorException; | |||
@@ -145,6 +171,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制</a> | |||
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getsessionlist?access_token=ACCESS_TOKEN&kf_account=KFACCOUNT | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfSessionList kfSessionList(String kfAccount) throws WxErrorException; | |||
@@ -155,6 +183,8 @@ public interface WxMpKefuService { | |||
* 详情请见:<a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1458044820&token=&lang=zh_CN">客服会话控制</a> | |||
* 接口url格式: https://api.weixin.qq.com/customservice/kfsession/getwaitcase?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException; | |||
@@ -173,7 +203,7 @@ public interface WxMpKefuService { | |||
* @param msgId 消息id顺序从小到大,从1开始 | |||
* @param number 每次获取条数,最多10000条 | |||
* @return 聊天记录对象 | |||
* @throws WxErrorException | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException; | |||
@@ -188,8 +218,27 @@ public interface WxMpKefuService { | |||
* @param startTime 起始时间 | |||
* @param endTime 结束时间 | |||
* @return 聊天记录对象 | |||
* @throws WxErrorException | |||
* @throws WxErrorException 异常 | |||
*/ | |||
WxMpKfMsgList kfMsgList(Date startTime, Date endTime) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 客服输入状态 | |||
* 开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。 | |||
* 此接口需要客服消息接口权限。 | |||
* 如果不满足发送客服消息的触发条件,则无法下发输入状态。 | |||
* 下发输入状态,需要客服之前30秒内跟用户有过消息交互。 | |||
* 在输入状态中(持续15s),不可重复下发输入态。 | |||
* 在输入状态中,如果向用户下发消息,会同时取消输入状态。 | |||
* | |||
* 详情请见:<a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547">客服输入状态</a> | |||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN | |||
* </pre> | |||
* | |||
* @param openid 用户id | |||
* @param command "Typing":对用户下发“正在输入"状态 "CancelTyping":取消对用户的”正在输入"状态 | |||
* @throws WxErrorException 异常 | |||
*/ | |||
boolean sendKfTypingState(String openid, String command) throws WxErrorException; | |||
} |
@@ -1,12 +1,20 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.material.*; | |||
import java.io.File; | |||
import java.io.InputStream; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterial; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialCountResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNewsBatchGetResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult; | |||
/** | |||
* <pre> | |||
* Created by Binary Wang on 2016/7/21. | |||
@@ -1,29 +1,57 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; | |||
import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormRequest; | |||
import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormResult; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult; | |||
import me.chanjar.weixin.mp.bean.membercard.*; | |||
/** | |||
* 会员卡相关接口 | |||
* | |||
* @author YuJian(mgcnrx11@gmail.com) | |||
* @author YuJian(mgcnrx11 @ gmail.com) | |||
* @author yuanqixun | |||
* @version 2017/7/8 | |||
* @date 2018-08-30 | |||
*/ | |||
public interface WxMpMemberCardService { | |||
String MEMBER_CARD_CREAET = "https://api.weixin.qq.com/card/create"; | |||
String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate"; | |||
String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get"; | |||
String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser"; | |||
/** | |||
* 会员卡激活之微信开卡接口(wx_activate=true情况调用) | |||
*/ | |||
String MEMBER_CARD_ACTIVATEUSERFORM = "https://api.weixin.qq.com/card/membercard/activateuserform/set"; | |||
/** | |||
* 得到WxMpService | |||
*/ | |||
WxMpService getWxMpService(); | |||
/** | |||
* 会员卡创建接口 | |||
* | |||
* @param createJson | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException; | |||
/** | |||
* 会员卡创建接口 | |||
* | |||
* @param createMessageMessage | |||
* @return WxMpCardCreateResult | |||
* @throws WxErrorException | |||
*/ | |||
WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException; | |||
/** | |||
* 会员卡激活接口 | |||
* | |||
* @param activatedMessage 激活所需参数 | |||
* @return 调用返回的JSON字符串。 | |||
* @return 返回json字符串 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException; | |||
@@ -32,7 +60,7 @@ public interface WxMpMemberCardService { | |||
* 拉取会员信息接口 | |||
* | |||
* @param cardId 会员卡的CardId,微信分配 | |||
* @param code 领取会员的会员卡Code | |||
* @param code 领取会员的会员卡Code | |||
* @return 会员信息的结果对象 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
@@ -40,7 +68,7 @@ public interface WxMpMemberCardService { | |||
/** | |||
* 当会员持卡消费后,支持开发者调用该接口更新会员信息。会员卡交易后的每次信息变更需通过该接口通知微信,便于后续消息通知及其他扩展功能。 | |||
* | |||
* <p> | |||
* 1.开发者可以同时传入add_bonus和bonus解决由于同步失败带来的幂等性问题。同时传入add_bonus和bonus时 | |||
* add_bonus作为积分变动消息中的变量值,而bonus作为卡面上的总积分额度显示。余额变动同理。 | |||
* 2.开发者可以传入is_notify_bonus控制特殊的积分对账变动不发送消息,余额变动同理。 | |||
@@ -50,4 +78,14 @@ public interface WxMpMemberCardService { | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
WxMpMemberCardUpdateResult updateUserMemberCard(WxMpMemberCardUpdateMessage updateUserMessage) throws WxErrorException; | |||
/** | |||
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要) | |||
* | |||
* @param userFormRequest | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException; | |||
} |
@@ -1,9 +1,6 @@ | |||
package me.chanjar.weixin.mp.api.impl; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.gson.JsonPrimitive; | |||
import com.google.gson.*; | |||
import com.google.gson.reflect.TypeToken; | |||
import me.chanjar.weixin.common.bean.WxCardApiSignature; | |||
import me.chanjar.weixin.common.error.WxError; | |||
@@ -13,8 +10,12 @@ import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
import me.chanjar.weixin.mp.api.WxMpCardService; | |||
import me.chanjar.weixin.mp.api.WxMpService; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult; | |||
import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpCardResult; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -30,6 +31,8 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
private WxMpService wxMpService; | |||
private static final Gson GSON = new Gson(); | |||
public WxMpCardServiceImpl(WxMpService wxMpService) { | |||
this.wxMpService = wxMpService; | |||
} | |||
@@ -236,4 +239,90 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
return responseContent; | |||
} | |||
/** | |||
* 添加测试白名单 | |||
* | |||
* @param openid 用户的openid | |||
* @return | |||
*/ | |||
public String addTestWhiteList(String openid) throws WxErrorException { | |||
JsonArray array = new JsonArray(); | |||
array.add(openid); | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.add("openid", array); | |||
String respone = this.wxMpService.post(CARD_TEST_WHITELIST, GSON.toJson(jsonObject)); | |||
return respone; | |||
} | |||
/** | |||
* 创建卡券二维码 | |||
* | |||
* @param cardId | |||
* @param outerStr | |||
* @return | |||
*/ | |||
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException { | |||
return createQrcodeCard(cardId, outerStr, 0); | |||
} | |||
/** | |||
* 创建卡券二维码 | |||
* | |||
* @param cardId 卡券编号 | |||
* @param outerStr 二维码标识 | |||
* @param expiresIn 失效时间,单位秒,不填默认365天 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("action_name", "QR_CARD"); | |||
if (expiresIn > 0) { | |||
jsonObject.addProperty("expire_seconds", expiresIn); | |||
} | |||
JsonObject actionInfoJson = new JsonObject(); | |||
JsonObject cardJson = new JsonObject(); | |||
cardJson.addProperty("card_id", cardId); | |||
cardJson.addProperty("outer_str", outerStr); | |||
actionInfoJson.add("card", cardJson); | |||
jsonObject.add("action_info", actionInfoJson); | |||
String response = this.wxMpService.post(CARD_QRCODE_CREAET, GSON.toJson(jsonObject)); | |||
return WxMpCardQrcodeCreateResult.fromJson(response); | |||
} | |||
/** | |||
* 创建卡券货架接口 | |||
* | |||
* @param request | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest request) throws WxErrorException { | |||
String response = this.wxMpService.post(CARD_LANDING_PAGE_CREAET, GSON.toJson(request)); | |||
return WxMpCardLandingPageCreateResult.fromJson(response); | |||
} | |||
/** | |||
* 将用户的卡券设置为失效状态 | |||
* 详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=9 | |||
* | |||
* @param cardId 卡券编号 | |||
* @param code 用户会员卡号 | |||
* @param reason 设置为失效的原因 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException { | |||
if (StringUtils.isAnyBlank(cardId, code, reason)) | |||
throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("参数不完整").build()); | |||
JsonObject jsonRequest = new JsonObject(); | |||
jsonRequest.addProperty("card_id", cardId); | |||
jsonRequest.addProperty("code", code); | |||
jsonRequest.addProperty("reason", reason); | |||
String response = this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest)); | |||
return response; | |||
} | |||
} |
@@ -1,8 +1,14 @@ | |||
package me.chanjar.weixin.mp.api.impl; | |||
import java.io.File; | |||
import java.util.Date; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.google.gson.JsonObject; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
import me.chanjar.weixin.mp.api.WxMpKefuService; | |||
@@ -10,12 +16,12 @@ import me.chanjar.weixin.mp.api.WxMpService; | |||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; | |||
import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfSessionRequest; | |||
import me.chanjar.weixin.mp.bean.kefu.result.*; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.File; | |||
import java.util.Date; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; | |||
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; | |||
/** | |||
* @author Binary Wang | |||
@@ -120,12 +126,12 @@ public class WxMpKefuServiceImpl implements WxMpKefuService { | |||
} | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("starttime", startTime.getTime() / 1000); //starttime 起始时间,unix时间戳 | |||
param.addProperty("endtime", endTime.getTime() / 1000); //endtime 结束时间,unix时间戳,每次查询时段不能超过24小时 | |||
param.addProperty("msgid", msgId); //msgid 消息id顺序从小到大,从1开始 | |||
param.addProperty("number", number); //number 每次获取条数,最多10000条 | |||
param.addProperty("starttime", startTime.getTime() / 1000); | |||
param.addProperty("endtime", endTime.getTime() / 1000); | |||
param.addProperty("msgid", msgId); | |||
param.addProperty("number", number); | |||
String responseContent = this.wxMpService.post(MSGRECORD_GET_MSG_LIST, param.toString()); | |||
String responseContent = this.wxMpService.post(MSG_RECORD_LIST, param.toString()); | |||
return WxMpKfMsgList.fromJson(responseContent); | |||
} | |||
@@ -149,4 +155,13 @@ public class WxMpKefuServiceImpl implements WxMpKefuService { | |||
return result; | |||
} | |||
@Override | |||
public boolean sendKfTypingState(String openid, String command) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("touser", openid); | |||
params.addProperty("command", command); | |||
String responseContent = this.wxMpService.post(CUSTOM_TYPING, params.toString()); | |||
return responseContent != null; | |||
} | |||
} |
@@ -1,9 +1,16 @@ | |||
package me.chanjar.weixin.mp.api.impl; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; | |||
@@ -11,17 +18,22 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import me.chanjar.weixin.mp.api.WxMpMaterialService; | |||
import me.chanjar.weixin.mp.api.WxMpService; | |||
import me.chanjar.weixin.mp.bean.material.*; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.*; | |||
import me.chanjar.weixin.mp.util.requestexecuter.media.MediaImgUploadRequestExecutor; | |||
import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterial; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialCountResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialFileBatchGetResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialNewsBatchGetResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; | |||
import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialDeleteRequestExecutor; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialNewsInfoRequestExecutor; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialUploadRequestExecutor; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialVideoInfoRequestExecutor; | |||
import me.chanjar.weixin.mp.util.requestexecuter.material.MaterialVoiceAndImageDownloadRequestExecutor; | |||
import me.chanjar.weixin.mp.util.requestexecuter.media.MediaImgUploadRequestExecutor; | |||
/** | |||
* Created by Binary Wang on 2016/7/21. | |||
@@ -6,30 +6,29 @@ import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.gson.reflect.TypeToken; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import me.chanjar.weixin.mp.api.WxMpMemberCardService; | |||
import me.chanjar.weixin.mp.api.WxMpService; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult; | |||
import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; | |||
import me.chanjar.weixin.mp.bean.card.*; | |||
import me.chanjar.weixin.mp.bean.card.enums.BusinessServiceType; | |||
import me.chanjar.weixin.mp.bean.card.enums.CardColor; | |||
import me.chanjar.weixin.mp.bean.card.enums.DateInfoType; | |||
import me.chanjar.weixin.mp.bean.membercard.*; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
/** | |||
* 会员卡相关接口的实现类 | |||
* | |||
* @author YuJian(mgcnrx11@gmail.com) | |||
* @author YuJian(mgcnrx11 @ gmail.com) | |||
* @version 2017/7/8 | |||
*/ | |||
public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
private final Logger log = LoggerFactory.getLogger(WxMpMemberCardServiceImpl.class); | |||
private static final String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate"; | |||
private static final String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get"; | |||
private static final String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser"; | |||
private WxMpService wxMpService; | |||
private static final Gson GSON = new Gson(); | |||
@@ -46,11 +45,176 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
return this.wxMpService; | |||
} | |||
/** | |||
* 会员卡创建接口 | |||
* | |||
* @param createJson 创建json | |||
* @return 调用返回的JSON字符串。 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
@Override | |||
public WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException { | |||
WxMpMemberCardCreateMessage createMessage = WxGsonBuilder.create().fromJson(createJson, WxMpMemberCardCreateMessage.class); | |||
return createMemberCard(createMessage); | |||
} | |||
/** | |||
* 会员卡创建接口 | |||
* | |||
* @param createMessageMessage 创建所需参数 | |||
* @return WxMpCardCreateResult。 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
@Override | |||
public WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException { | |||
//校验请求对象合法性 | |||
WxMpCardCreateResult validResult = validCheck(createMessageMessage); | |||
if (!validResult.isSuccess()) | |||
return validResult; | |||
String response = this.wxMpService.post(MEMBER_CARD_CREAET, GSON.toJson(createMessageMessage)); | |||
return WxMpCardCreateResult.fromJson(response); | |||
} | |||
private WxMpCardCreateResult validCheck(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException { | |||
if (createMessageMessage == null) { | |||
return WxMpCardCreateResult.failure("对象不能为空"); | |||
} | |||
MemberCardCreateRequest cardCreateRequest = createMessageMessage.getCardCreateRequest(); | |||
if (createMessageMessage == null) { | |||
return WxMpCardCreateResult.failure("会员卡对象不能为空"); | |||
} | |||
String cardType = cardCreateRequest.getCardType(); | |||
if (!StringUtils.equals(cardType, "MEMBER_CARD")) { | |||
return WxMpCardCreateResult.failure("卡券类型必须等于MEMBER_CARD"); | |||
} | |||
MemberCard memberCard = cardCreateRequest.getMemberCard(); | |||
if (StringUtils.isEmpty(memberCard.getPrerogative())) { | |||
return WxMpCardCreateResult.failure("会员卡特权说明不能为空:prerogative"); | |||
} | |||
//卡片激活规则 | |||
if (!memberCard.isAutoActivate() && !memberCard.isWxActivate() && StringUtils.isEmpty(memberCard.getActivateUrl())) { | |||
return WxMpCardCreateResult.failure("会员卡激活方式为接口激活,activate_url不能为空"); | |||
} | |||
//积分支持 | |||
// if(memberCard.isSupplyBonus() && StringUtils.isEmpty(memberCard.getBonusUrl())){ | |||
// return WxMpCardCreateResult.failure("会员卡支持积分,bonus_url不能为空"); | |||
// } | |||
// if(memberCard.isSupplyBonus() && memberCard.getBonusRule() == null){ | |||
// return WxMpCardCreateResult.failure("会员卡支持积分,bonus_rule不能为空"); | |||
// } | |||
BaseInfo baseInfo = memberCard.getBaseInfo(); | |||
if (baseInfo == null) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息对象base_info不能为空"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getLogoUrl())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的商户logo:logo_url不能为空"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getCodeType())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的条码类型:code_type不能为空"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getBrandName())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的商户名字:brand_name不能为空"); | |||
} | |||
if (StringUtils.length(baseInfo.getBrandName()) > 12) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的商户名字:brand_name长度不能大于12个汉字"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getTitle())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的卡券名称:title不能为空"); | |||
} | |||
if (StringUtils.length(baseInfo.getTitle()) > 9) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的卡券名称:title长度不能大于9个汉字"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getColor())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的卡颜色:color不能为空"); | |||
} | |||
CardColor cardColor = null; | |||
try { | |||
cardColor = CardColor.valueOf(baseInfo.getColor()); | |||
} catch (IllegalArgumentException ex) { | |||
} | |||
if (cardColor == null) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的卡颜色:" + baseInfo.getColor() + "不支持"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getNotice())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用提醒:notice不能为空"); | |||
} | |||
if (StringUtils.isBlank(baseInfo.getDescription())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用说明:description不能为空"); | |||
} | |||
if (baseInfo.getSku() == null) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的商品信息:sku不能为空"); | |||
} | |||
DateInfo dateInfo = baseInfo.getDateInfo(); | |||
if (dateInfo == null) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期:date_info不能为空"); | |||
} | |||
DateInfoType dateInfoType = null; | |||
try { | |||
dateInfoType = DateInfoType.valueOf(dateInfo.getType()); | |||
} catch (IllegalArgumentException ex) { | |||
} | |||
if (dateInfoType == null) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期类型:" + dateInfo.getType() + "不合法"); | |||
} | |||
//固定时长 | |||
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TERM && (dateInfo.getFixedTerm() == null || dateInfo.getFixedBeginTerm() == null)) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",fixedTerm和fixedBeginTerm不能为空"); | |||
} | |||
//固定期限 | |||
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TIME_RANGE && (dateInfo.getBeginTimestamp() == null || dateInfo.getEndTimestamp() == null)) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",beginTimestamp 和 endTimestamp 不能为空"); | |||
} | |||
if (dateInfoType == DateInfoType.DATE_TYPE_FIX_TIME_RANGE && (dateInfo.getBeginTimestamp() < System.currentTimeMillis() || dateInfo.getEndTimestamp() < System.currentTimeMillis() || dateInfo.getBeginTimestamp() > dateInfo.getEndTimestamp())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的使用日期为:" + dateInfoType.getDescription() + ",beginTimestamp和endTimestamp的值不合法,请检查"); | |||
} | |||
if (!baseInfo.isUseAllLocations() && StringUtils.isBlank(baseInfo.getLocationIdList())) { | |||
return WxMpCardCreateResult.failure("会员卡基本信息的门店使用范围选择指定门店,门店列表:locationIdList不能为空"); | |||
} | |||
//校验高级信息 | |||
AdvancedInfo advancedInfo = memberCard.getAdvancedInfo(); | |||
if (advancedInfo != null) { | |||
if (advancedInfo.getBusinessServiceList() != null) { | |||
for (String bs : advancedInfo.getBusinessServiceList()) { | |||
BusinessServiceType businessServiceType = null; | |||
try { | |||
businessServiceType = BusinessServiceType.valueOf(bs); | |||
} catch (IllegalArgumentException ex) { | |||
return WxMpCardCreateResult.failure("会员卡高级信息的商户服务:" + bs + " 不合法"); | |||
} | |||
} | |||
} | |||
} | |||
return WxMpCardCreateResult.success(); | |||
} | |||
/** | |||
* 会员卡激活接口 | |||
* | |||
* @param activatedMessage 激活所需参数 | |||
* @return 调用返回的JSON字符串。 | |||
* @return WxMpCardCreateResult。 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
@Override | |||
@@ -70,9 +234,10 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
public WxMpMemberCardUserInfoResult getUserInfo(String cardId, String code) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("card_id", cardId); | |||
jsonObject.addProperty("code",code); | |||
jsonObject.addProperty("code", code); | |||
String responseContent = this.getWxMpService().post(MEMBER_CARD_USER_INFO_GET, jsonObject.toString()); | |||
log.debug("{}",responseContent); | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement, | |||
new TypeToken<WxMpMemberCardUserInfoResult>() { | |||
@@ -81,7 +246,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
/** | |||
* 当会员持卡消费后,支持开发者调用该接口更新会员信息。会员卡交易后的每次信息变更需通过该接口通知微信,便于后续消息通知及其他扩展功能。 | |||
* | |||
* <p> | |||
* 1.开发者可以同时传入add_bonus和bonus解决由于同步失败带来的幂等性问题。同时传入add_bonus和bonus时 | |||
* add_bonus作为积分变动消息中的变量值,而bonus作为卡面上的总积分额度显示。余额变动同理。 | |||
* 2.开发者可以传入is_notify_bonus控制特殊的积分对账变动不发送消息,余额变动同理。 | |||
@@ -101,4 +266,18 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
new TypeToken<WxMpMemberCardUpdateResult>() { | |||
}.getType()); | |||
} | |||
/** | |||
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要) | |||
* | |||
* @param userFormRequest | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException { | |||
String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATEUSERFORM, GSON.toJson(userFormRequest)); | |||
return MemberCardActivateUserFormResult.fromJson(responseContent); | |||
} | |||
} |