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